commit 68f7b78dc1fa8e6ff3597f989dc8b9dad3f31560 Author: Julien Malik Date: Tue Jan 14 12:28:06 2014 +0100 add phpmyadmin sources 4.1.4 diff --git a/sources/CONTRIBUTING.md b/sources/CONTRIBUTING.md new file mode 100644 index 0000000..62c9798 --- /dev/null +++ b/sources/CONTRIBUTING.md @@ -0,0 +1,21 @@ +# Contributing to phpMyAdmin + +As an open source project, phpMyAdmin welcomes contributions of many forms. + +## Bug reporting + +Please report [bugs on SourceForge.net][1]. + +[1]: https://sourceforge.net/p/phpmyadmin/bugs/new/ + +## Patches submission + +Patches are welcome either as [pull requests on GitHub][2]. + +[2]: https://github.com/phpmyadmin/phpmyadmin/pulls + +## More information + +You can find more information on our website: + +http://www.phpmyadmin.net/home_page/improve.php diff --git a/sources/ChangeLog b/sources/ChangeLog new file mode 100644 index 0000000..681a37b --- /dev/null +++ b/sources/ChangeLog @@ -0,0 +1,445 @@ +phpMyAdmin - ChangeLog +====================== + +4.1.4.0 (2014-01-07) +- bug #3840 (additional fix) When exporting to gzip format, the data is compressed 2 times +- bug #4209 Missing compression in one case +- bug #4208 Can't browse tables after sorting on columns with fieldnames that have a '-' +- bug #4184 Switch to wrong page after adding an index +- bug #3885 Additional fix for this bug +- bug #4212 Table "disappears" if it has the same name as its tablegroup +- bug #4213 Datetime Quick Edit decrements by one day +- bug #4217 Current value not highlighted when browsing foreign values +- bug #4220 Incorrect key values in foreign key browser +- bug #4215 MariaDB 5.5: error in Drizzle detection + +4.1.3.0 (2013-12-31) +- bug #3938 PDFDefaultPageSize doc and easy configurability +- bug #4198 Hovering over pie chart gives fatal JS error +- bug #4200 Missing syntax highlighting +- bug #4201 Exports are not compressed +- bug #4131 Import: "number of rows to skip" is ambiguous +- bug #4205 Add a user shows additional "edit user group" link +- bug #4202 Cannot read property 'token' of undefined +- bug #4203 On refreshing designer, $.FullScreen is undefined +- bug #3920 Lost space in navigation area + +4.1.2.0 (2013-12-23) +- bug #4178 Quick edit for BIT type does not work +- bug #2760 Warn about incomplete exports +- bug #4190 Fractional seconds cause row update even if the value is not changed +- bug #4170 Overflow scroll for table grid is not a good solution +- bug #2961 Relations settings not updated on config change +- bug #4187 SQL query inline edit doesn't post changes on the first run ++ rfe #1465 Docs for connections to IPv6 only MySQL instances ++ rfe #1468 [interface] No floating for server breadcrumb menu + +4.1.1.0 (2013-12-17) +- bug #4154 Error using UNION query +- bug #4173 Transformations overview not reachable +- bug #4149 Js freezes in the management of replication +- bug #3903 Query fails when using aliases after ordering result +- bug #4181 Adding columns in table creation clears existing columns +- bug #4023 Requires wildcard EXECUTE/ALTER ROUTINE on DB to allow +Procedures to be executed by user +- bug #4186 Adding a column when creating a table does not propagate index info +- bug #4185 Unable to execute create procedure statement from query window + +4.1.0.0 (2013-12-11) ++ rfe #499 On user creation, warn if the user already exists ++ Use indeterminate check all checkbox in server privileges ++ Break server_status.php functions into smaller functions ++ PMA_DBI functions in database_interface.lib.php renamed to be compliant with PEAR standards ++ [interface] Make warning about existing config directory clearer ++ rfe #1414 Allow specifying controlport ++ PMA_DBI functions in database interface libraries renamed to be compliant with PEAR standards ++ rfe #1412 Creating a view from an empty set of results ++ Improved layout on db and table operations pages ++ rfe #1410 Added support for AES_ENCRYPT for blob fields ++ rfe #1423 Clarify option text for icon/text settings ++ [interface] Upgraded CodeMirror to 3.x series ++ rfe #1363 Improved query profiler ++ [interface] rfe #1429 Better suggestion for database name ++ rfe #1433 Support relations with ndbcluster +- bug #3962 Proper escaping of JSON export ++ rfe #1382 Optional ReCAPTCHA support ++ rfe #1434 Improvements to the table browsing navigation bar ++ rfe #1233 and rfe #1283 Improvements to Relation View interface ++ rfe #1397 Use fractional seconds in time, datetime, and timestamp ++ rfe #175 Allow cross-database relations +- [core] Dropped support for PHP 5.2. ++ rfe #487 and rfe #1405 Find and Replacing column wise ++ rfe #1373 Use same create view dialog for editing a view ++ rfe #316 Configurable menus; allow user groups with customized menus per group +- bug #4024 Editing field a record is selected by makes pma load forever +- bug #4035 Query "inline" link disappears when turning off "Explain SQL" option ++ rfe #1385 Hide tables, functions, procedures, events and views in navigation tree ++ rfe #1321 Export view as if it was a table ++ Dropped configuration directive: SQP ++ Dropped configuration directive: MySQLManual* ++ rfe #1041 and bug #2954 Improved support for SSL connections between MySQL and phpMyAdmin +- bug #4056 Language: Vague error message when adding a varchar field ++ [setup] rfe #1452 Use type="password" for server passwords ++ rfe #1451 HTML5 input tag enhancements +- bug #1193 Text field too small when editing a row longer than $cfg['LimitChars'] ++ Privileges tab for table level +- bug #4068 Headline in operations not readable in IE10 +- bug #4000 "Table does not contain unique column" message appears after adding a unique column ++ rfe #1428 add 'new database' entry to nav tree ++ rfe #1457 Stone Age icon found ++ rfe #1463 Filter tables and databases by regular expression ++ Change the proxy variable names in the config to remove the VersionCheck prefix from them ++ Added an Error Reporting Component ++ Javascript files are no longer uglified +- bug #4145 Config screen fails to validate MemoryLimit = -1 (new default) +- bug #4123 Double config including +- bug #4134 After deleting all rows on a page, it returns to a blank page ++ Dropped configuration directive: DisableIS, ShowDatabasesCommand +- bug #4152 Not possible to enter % for search in date fields +- bug #3931 IN Clause search does not permit multiple values +- bug #4086 Clicking OK from edit popup opens new tab +- bug #2983 unknown table status: TABLE_TYPE +- bug #4030 ORDER BY SUM(`field`) does not sort DESC +- bug #4133 CSV import breaks when no blank line at end of file +- bug #4153 Unable to import if newline encoding is MAC style +- bug #4096 horizontal scrollbar should not overflow on the left column +- bug #4159 bug with navigation between database and table filter +- bug #4119 Huge session data with $cfg['Error_Handler']['gather'] +- bug #4169 Table list jumps to table on click +- bug #4168 Rename multiple columns is not working + +4.0.10.0 (2013-12-04) +- bug #4150 Clicking database name in query window opens a new tab +- bug #4141 Wrong page is shown after editing; also, do not show a modal + dialog for multi-row edit +- bug #3939 PHP NavigationTree error when paging through list +- bug #4075 Support A10 Networks load balancer +- bug #4083 row deleting isn't binlogs friendly +- bug #4163 Setup script does not recognize manually-configured server +- bug #4158 Events page says no privileges with ALL PRIVILEGES + +4.0.9.0 (2013-11-04) +- bug #4104 Can't edit updatable view when searching +- bug #4108 Missing refresh by deleting databases +- bug #3995 Drizzle server charset notice +- bug #3911 Filtering database names includes empty groupings +- bug #3678 Does not display or manipulate bit(64) fields appropriately +- bug #4129 Unneeded navi panel refresh +- bug #4120 SSL redirects to port 80 +- bug #4144 DROP DATABASE displays wrong database name +- bug #4059 Running delete query asks for confirmation but says it was already executed +- bug #4147 Accessibility: Images without Alt nor title attribute + +4.0.8.0 (2013-10-06) +- bug #3988 Rename view is not working +- bug #4041 Interaction between linkified fields and grid editing +- bug #3975 Table grouping isn't implemented properly +- bug #4060 Browser tries to remember wrong password when creating new user +- bug #4002 Edit Index on big table doesn't show "Loading" or any message +- bug #4098 Default table tab is ignored +- bug #4099 Server/library difference warning: setting is ignored +- bug #4100 table tree group strategy +- bug #4102 ALTER TABLE ORDER BY and InnoDB +- bug #4103 Tracking report: cannot delete a statement +- bug #3996 Drizzle navigation doesn't expand +- bug #4074 GIS column editor: point not displayed +- bug #4109 Drizzle tables in navigation are shown as views +- bug #4095 NUL symbols added to the end of database dump file +- bug #4105 More disappears in table Structure +- bug #3992 Multi-row edit doesn't clear values when checking NULL + +4.0.7.0 (2013-09-23) +- bug #3993 Sorting in database overview with statistics doesn't work +- bug Handle the situation where PHP_SELF is not set +- bug #4080 Overwrite existing file not obeyed +- bug #3929 Database-specific privileges are not copied when cloning user +- bug #3997 Error handling in case MySQL extension is missing +- bug #4089 Moving Columns will alter column definition +- bug #4091 Insert ignore option does not work +- bug #4090 Downloading BLOB downloads page template +- bug #4092 Clicking on table name in view of information_schema redirects to wrong page +- bug #4079 Copy Table Add AUTO_INCREMENT value checkbox not working +- bug #4088 MySQL server version at index.php incorrect w/ controlhost +- bug #4001 Import error: Class 'ImportOds' not found +- bug #3986 Missing DROP VIEW button + +4.0.6.0 (2013-09-05) +- bug #4036 Call to undefined function mb_detect_encoding (clarify the doc) +- bug Missing hints when changing a column's structure +- bug #4048 Cannot select foreign value in Search +- bug #4025 gzip export is not actually compressed with mod_deflate +- bug #4054 query analysis doesn't launch in status monitor ++ Add pmahomme icon credits (FamFamFam silk icon set) +- bug #4064 Table structure statistics "Space usage" caption too small for l10n +- bug #4051 Wrong tabindex when inserting rows +- bug #4066 varchar field not truncated in table browse mode ++ rfe #1435 Opening database should expand it in the navigation menu +- (performance) Removed ShowTooltip directive +- bug #4046 Exporting huge Tables causes memory-Problems + +4.0.5.0 (2013-08-04) +- bug #3977 Not detected configuration storage +- bug #3970 Pressing enter in the filter field reloads page +- bug #3984 Cannot insert in this table (PHP < 5.4) +- bug #3989 Reloading privileges does not update the interface +- bug #3960 NavigationBarIconic config not honored +- bug #3985 Call to undefined function mb_detect_encoding +- bug #4007 Analyze option not shown for InnoDB tables +- bug #4015 Forcing a storage engine for configuration storage +- bug Incorrect Drizzle 7 detection +- bug #4019 Create database if not exists (export): add an option to the + interface to enable generating CREATE DATABASE and USE (false by default) +- bug #4012 Crash on CSV file import +- bug #4009 Statistic Monitor shows only last 3 digits in graph +- bug #3998 Non-permanent SQL history not working +- bug #3578 Transformations for text/plain on a BLOB column +- [security] Improved protection against cross framing, see PMASA-2013-10 ++ Reinstated configuration directive: AllowThirdPartyFraming + +4.0.4.2 (2013-07-28) +- [security] fix unescaped parameter, see PMASA-2013-8 +- [security] Fix stored XSS in Server status monitor, see PMASA-2013-9 +- [security] Fix stored XSS in navigation panel logo link, see PMASA-2013-9 +- [security] Fix self-XSS in setup, trusted proxies validation, see PMASA-2013-9 +- [security] Fix full path disclosure, see PMASA-2013-12 +- [security] Fix control user SQL injection in pmd_pdf.php, see PMASA-2013-15 +- [security] Fix control user SQL injection in schema_export.php, see PMASA-2013-15 +- [security] Fix self-XSS in schema export, see PMASA-2013-14 +- [security] Fix unencoded json object, see PMASA-2013-11 +- [security] Fix stored XSS in link transformation plugin, see PMASA-2013-13 + +4.0.4.1 (2013-06-30) +- [security] Global variables scope injection vulnerability (see PMASA-2013-7) + +4.0.4.0 (2013-06-17) +- bug #3959 Using DefaultTabDatabase in NavigationTree for Database Click +- bug #3961 Avoid Suhosin warning when in simulation mode +- bug #3897 Row Statistics and Space usage bugs +- bug #3966 Only display "table has no unique column" message when applicable +- bug #3965 Default language wrong with zh-TW +- bug #3921 Call to undefined function PMA_isSuperuser() if default server is +not set +- bug #3971 Ctrl/shift + click opens links in same window +- bug #3964 Import using https does not work +- bug Missing removeCRLF option in ExportCsv and ExportExcel plugins +- bug #3631 Drop not working Visio schema export. +- bug #3645 Better handling of invalid ODS documents +- bug #3976 Number of pages +- bug #3922 User privileges, database name unescaped + +4.0.3.0 (2013-06-05) +- bug #3941 Recent tables list always empty +- bug #3933 Do not translate "Open Document" in export settings +- bug #3927 List of tables is missing after expanding in the navigation frame +- bug #3942 Warnings about reserved word for many non reserved words +- bug #3912 Exporting row selection, resulted by ORDER BY query +- bug #3957 Cookies must be enabled past this point +- bug #3956 "Browse foreign values" search filter / page selector not working +- bug #3579 NOW() function incorrectly selected (partial regression) +- [security] Javascript execution vulnerability in Create view, + reported by Maxim Rupp (see PMASA-2013-6) + +4.0.2.0 (2013-05-24) +- bug #3902 Cannot browse when table name contains keyword "call" ++ center loading indicator for navigation refresh, related to bug #3920 +- bug #3925 Table sorting in navigation panel is case-sensitive +- bug #3915 Import of CSV file (Replace table data with file) with duplicate values +- bug #3907 undefined variables, function parameter problems +- bug #3898 Structure not refreshed after column drop +- bug #3926 View is not updatable +- bug #3919 PropertiesIconic not honored +- bug #3930 Databases to choose for specific privileges show up escaped +- bug #3910 Export database with empty table as a php array, does not produce valid PHP +- bug #3936 Query profiler chart not loading from SQL Query page +- bug #3946 Missing CSV import option "Do not abort on INSERT error" +- bug #3943 Missing Operations>Table options>AUTO_INCREMENT +- bug Missing CREATE DATABASE statement when exporting at database level +- bug #3924 Show warning when CSV file does not contain data for all columns +- bug #3947 Missing Sql Query after modify structure +- bug #3948 Server export problems +- bug #3917 CountTables directive is deprecated + +4.0.1.0 (2013-05-14) +- bug #3879 Import broken for CSV using LOAD DATA +- bug #3889 When login fails and error display is active, login data is displayed +- bug #3890 [import] Web server upload directory import fails +- bug #3891 [import] Server upload folder import file name missing in success message ++ rfe #1421 [auth] Add retry button on connection failure with config auth +- bug #3894 [interface] Provide feedback if no columns selected for multi-submit +- bug #3799 [interface] Incorrect select field change on ctrl key navigation in Firefox +- bug #3885 [browse] display_binary_as_hex option causes unexpected behavior +- bug #3899 Git commit links to Github missing +- bug #3900 CSP WARN in Firefox console +- bug #3901 Setup script warning for config auth (stored login data) shows link BBcode +- bug #3895 [browse] Fixed getting BLOB data +- bug #3905 [export] Custom Exporting exports all databases +- bug #3909 [import] Import of CSV FIle to selected table doesn't work +- bug #3904 Browsing an empty table should not display its Structure +- bug #3908 Calendar widget improperly redirects to home +- bug #3918 Greyed out tabs when there are no rows fixed +- bug #3916 [interface] Missing scrollbar (original theme) ++ [vendor] add tcpdf path to vendor_config.php +- bug fix compat with tcpdf >= 6.0 (tested with 6.0.012) + +4.0.0.0 (2013-05-03) ++ Patch #3481047 for rfe #3480477 Insert as new row enhancement ++ Patch #3480999 Activate codemirror in the query window +- Patch #3495284 XML Import - fix message and redirect ++ rfe #3484063 Null checkbox behavior ++ Patch #3497179 Contest-5: Add user: Allow create DB w/same name + grant u_% ++ Patch #3498201 Contest-6: Export all privileges ++ Patch #3502814 for rfe #3187077 Change password buttons should match ++ rfe #3488640 Expand table-group in non-light navigation frame if only one ++ Patch #3509360 Contest-3: Option "Truncate table" before "insert" ++ Patch #3506552 Contest-2: Show index information in the data dictionary ++ Patch #3510656 Contest-1: Ignoring foreign keys while dropping tables +- Bug #3509686 Reverting sort on joined column does not work ++ New transformation: append string ++ rfe #3507804 Session upload progress (PHP 5.4) ++ rfe #3488185 draggable columns vs copy column name ++ Patch #3507001 Contest-4: Textarea for large character columns ++ Removed the PHP version of the ENUM editor ++ Patch #3507111 Display distinct results, linked to corresponding data rows +- bug #3507917 [export] JSON has unescaped values for allegedly numeric columns ++ rfe #3516187 show tables creation, last update, last check timestamps in db_structure +- bug #3059806 Supporting running from CIFS/Samba shares +- bug #3516341 [export] Open Document Text, Word and Texy! Text show table structure twice +- bug [export] Texy! Text: Columns containing Pipe Character don't export properly ++ [export] Show triggers in Open Document Text, Word and Texy! Text +- Patch #3415061 [auth] Login screen appears under the page ++ rfe #3517354 [interface] Allow disabling CodeMirror with $cfg['CodemirrorEnable'] = false ++ rfe #3475567 [interface] New directive $cfg['HideStructureActions'] +- bug #3468272 [import] Fixed import of ODS with more paragraphs in a cell +- bug #3510196 [core] Improved redirecting with ForceSSL option ++ rfe #3518852 [edit] edit blob but not other binary, new option $cfg['ProtectBinary'] = 'noblob' ++ Hide language select box if there are no locales installed ++ Removed some directives: verbose_check, SuggestDBName, LightTabs, +VerboseMultiSubmit, ReplaceHelpImg +- Patch #3500882 Fixing checkbox behaviour while editing identical rows ++ rfe #3441722 [interface] Display description of datatypes ++ rfe #3517835 [structure] Move columns easily ++ Ajaxified "Create View" functionality ++ [import] New plugin: import mediawiki ++ New navigation system ++ Discontinued the use of a frame-based layout ++ rfe #3528994 [interface] Allow wrapping possibly long values in replication-status table ++ [interface] Autoselect username input on cookie login page +- bug #3563799 [interface] Grid editing destroying huge amount of data ++ [import] Remove support for the unactive docSQL import format +- bug #3577443 [edit] "Browse foreign values" does not show on ajax edit ++ rfe #3522109 [browse] Grid editing: action to trigger it (or disable) +- bug #3526598 [interface] SQL query not shown when creating table ++ Dropped configuration directive: AllowThirdPartyFraming ++ Dropped configuration directive: LeftFrameLight ++ Dropped configuration directive: DisplayDatabasesList ++ Dropped configuration directives: ShowTooltipAliasDB and ShowTooltipAliasTB ++ Dropped configuration directive: NaviDatabaseNameColor ++ Added configuration directive: MaxNavigationItems ++ Renamed configuration directive: LeftFrameDBTree => NavigationTreeEnableGrouping ++ Renamed configuration directive: LeftFrameDBSeparator => NavigationTreeDbSeparator ++ Renamed configuration directive: LeftFrameTableSeparator => NavigationTreeTableSeparator ++ Renamed configuration directive: LeftFrameTableLevel => NavigationTreeTableLevel ++ Renamed configuration directive: LeftPointerEnable => NavigationTreePointerEnable ++ Renamed configuration directive: LeftDefaultTabTable => NavigationTreeDefaultTabTable ++ Renamed configuration directive: LeftDisplayTableFilterMinimum => NavigationTreeDisplayTableFilterMinimum ++ Renamed configuration directive: LeftDisplayLogo => NavigationDisplayLogo ++ Renamed configuration directive: LeftLogoLink => NavigationLogoLink ++ Renamed configuration directive: LeftLogoLinkWindow => NavigationLogoLinkWindow ++ Renamed configuration directive: LeftDisplayServers => NavigationDisplayServers ++ Renamed configuration directive: LeftRecentTable => NumRecentTables ++ Renamed configuration directive: LeftDisplayDatabaseFilterMinimum => NavigationTreeDisplayDbFilterMinimum ++ Removed the "Mark row on click" feature; must now click the checkbox to mark ++ Removed the "Synchronize" feature ++ Improved layout of server variables page ++ rfe #1052091 [config] Double-underscores in PMA table names ++ Improved the "" dropdown on the table structure page ++ [interface] Added "scroll to top" link in menubar ++ [designer] Fullscreen mode for the designer ++ Upgraded jquery to v1.8.3 and jquery-ui to v1.9.2 ++ Patch #3597529 [status] Add raw value as title on server status page ++ Support MySQL 5.6 partitioning ++ Removed the AjaxEnable directive ++ rfe #3542567 Accept IPv6 ranges and IPv6 CIDR notations in $cfg['Servers'][$i]['AllowDeny']['rules'] +- Bug #3576788 Grid editing shows the value before silent truncation +- Upgraded jqPlot to 1.0.4 r1121 +- Upgraded to jquery-ui-timepicker-addon 1.1.1 ++ rfe #3599046 [interface] Added comments for indexes +- Replaced qtip with jQuery UI tooltip +- Upgraded CodeMirror to 2.37 +- bug #2951 [export] Correctly export decimal fields. +- bug #3762 [core] Make Advisor work on Windows withou COM extension. +- bug #3519 [export] Prevent infinite recursion in PDF export. +- bug #3827 Table specific privileges not displayed for db name containing +underscore +- rfe #1386 Add IF NOT EXISTS clause when copying database +- No longer package .travis.yml configuration file when creating a release. +- bug #3830 Can't export custom query because it lowercases table names +- bug #3829 Enabling query profiling crashes javascript based navigation ++ rfe #879 Reserved word warning ++ Remove the database ordering sub-feature of the only_db directive +- bug #3840 When exporting to gzip format, the data is compressed 2 times ++ rfe #1319 Permit to create index when creating foreign key +- bug #3703 Incorrect updating of the list of users +- bug #3853 Blowfish implementation might be broken (replace with phpseclib) +- bug #3865 Using like operator on each backslash needs 4 backslash protection +- bug #3860 Displayed git revision info is not set +- bug #3871 Check referential integrity broken across databases +- bug #3874 [export] No preselected option when exporting table +- bug #3873 Can't copy table to target database if table exists there +- bug #3683 Incorrect listing of records from to count +- bug #3876 [import] PHP 5.2 - unexpected T_PAAMAYIM_NEKUDOTAYIM +- [security] Local file inclusion vulnerability, reported by Janek Vind + (see PMASA-2013-4) +- [security] Global variables overwrite in export.php, reported by Janek Vind + (see PMASA-2013-5) +- bug #3892 [export] SQL Export files are empty + +3.5.8.2 (2013-07-28) +- [security] Fix self-XSS in "Showing rows", see PMASA-2013-8 +- [security] Fix self-XSS in Display chart, see PMASA-2013-9 +- [security] Fix stored XSS in Server status monitor, see PMASA-2013-9 +- [security] Fix stored XSS in navigation panel logo link, see PMASA-2013-9 +- [security] Fix self-XSS in setup, trusted proxies validation, see PMASA-2013-9 ++ [security] JSON content type header for version_check.php, see PMASA-2013-9 ++ [security] Backport fix for jQuery issue #9521 from jQuery 1.6.3, see PMASA-2013-9 ++ [security] Fix full path disclosure, see PMASA-2013-12 ++ [security] Fix control user SQL injection in pmd_pdf.php, see PMASA-2013-15 ++ [security] Fix control user SQL injection in schema_export.php, see PMASA-2013-15 +- [security] Fix self-XSS in schema export, see PMASA-2013-14 +- [security] Fix unencoded json object, see PMASA-2013-11 + +3.5.8.1 (2013-04-24) +- [security] Remote code execution (preg_replace), reported by Janek Vind + (see PMASA-2013-2) +- [security] Locally Saved SQL Dump File Multiple File Extension Remote Code + Execution, reported by Janek Vind (see PMASA-2013-3) + +3.5.8.0 (2013-04-08) +- bug #3828 MariaDB reported as MySQL +- bug #3854 Incorrect header for Safari 6.0 +- bug #3705 Attempt to open trigger for edit gives NULL +- Use HTML5 DOCTYPE +- [security] Self-XSS on GIS visualisation page, reported by Janek Vind +- bug #3800 Incorrect keyhandler behaviour #2 + +3.5.7.0 (2013-02-15) +- bug #3779 [core] Problem with backslash in enum fields +- bug #3816 Missing server_processlist.php +- bug #3821 Safari: white page +- Correct detection of the Chrome browser + +3.5.6.0 (2013-01-28) +- bug #3593604 [status] Erroneous advisor rule +- bug #3596070 [status] localStorage broken in server status monitor +- bug #3598736 [routines] Editing a procedure with special characters +- bug #3600322 [core] Visualize GIS data throws Fatal Error +- bug #3599362 [core] Double-escaped error message +- bug #3776 [cookies] Login without auth on second server + + --- Older ChangeLogs can be found on our project website --- + http://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/sources/LICENSE b/sources/LICENSE new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/sources/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, 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 Library 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 St, 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 Library General +Public License instead of this License. diff --git a/sources/README b/sources/README new file mode 100644 index 0000000..165880e --- /dev/null +++ b/sources/README @@ -0,0 +1,104 @@ +phpMyAdmin - Readme +=================== + +Version 4.1.4 + +A set of PHP-scripts to manage MySQL over the web. + +http://www.phpmyadmin.net/ + +Copyright +--------- + +Copyright (C) 1998-2000 + Tobias Ratschiller + +Copyright (C) 2001-2014 + Marc Delisle + Olivier Müller + Robin Johnson + Alexander M. Turek + Michal Čihař + Garvin Hicking + Michael Keck + Sebastian Mendel + [check documentation for more details] + +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 . + +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/jquery/ is +(MIT|GPL), a copy of each license is available in this repository (GPL +is available as LICENSE, MIT as js/jquery/MIT-LICENSE.txt). + +TCPDF which is located under libraries/tcpdf is released under GPL +version 3 and the license is available as libraries/tcpdf/LICENSE.TXT. + +DejaVu fonts which are located under libraries/tcpdf/fonts/ and their +license is documented in +libraries/tcpdf/fonts/dejavu-fonts-ttf-2.33/LICENSE. + +PHP-gettext which is located under libraries/php-gettext/ is released +under GPL version 2 license which is available in the LICENSE file. + +Requirements +------------ + +* PHP 5.3 or later +* MySQL 5.0 or later +* a web-browser (doh!) + +Summary +------- + +phpMyAdmin is intended to handle the administration of MySQL over the web. +For a summary of features, please see the documentation in the doc folder. + +Download +-------- + +You can get the newest version at http://www.phpmyadmin.net/. + +More Information +---------------- + +Please see the documentation in the doc folder. + +Support +------- + +See reference about support forums under http://www.phpmyadmin.net/ + + +Enjoy! +------ + +The phpMyAdmin Devel team + + +PS: + +Please, don't send us emails with question like "How do I compile PHP with +MySQL-support". We just don't have the time to be your free help desk. + +Please send your questions to the appropriate mailing lists / forums. Before +contacting us, please read the documentation (especially the FAQ part). + diff --git a/sources/RELEASE-DATE-4.1.4 b/sources/RELEASE-DATE-4.1.4 new file mode 100644 index 0000000..06b100c --- /dev/null +++ b/sources/RELEASE-DATE-4.1.4 @@ -0,0 +1 @@ +Tue Jan 7 15:18:49 UTC 2014 diff --git a/sources/browse_foreigners.php b/sources/browse_foreigners.php new file mode 100644 index 0000000..0384350 --- /dev/null +++ b/sources/browse_foreigners.php @@ -0,0 +1,66 @@ +getFooter()->setMinimal(); +$header = $response->getHeader(); +$header->disableMenu(); +$header->setBodyId('body_browse_foreigners'); + +/** + * Displays the frame + */ + +$cfgRelation = PMA_getRelationsParam(); +$foreigners = ($cfgRelation['relwork'] ? PMA_getForeigners($db, $table) : false); +$foreign_limit = PMA_getForeignLimit( + isset($foreign_navig) ? $foreign_navig : null +); + +$foreignData = PMA_getForeignData( + $foreigners, $_REQUEST['field'], true, + isset($_REQUEST['foreign_filter']) + ? $_REQUEST['foreign_filter'] + : '', + isset($foreign_limit) ? $foreign_limit : null +); + + +$code = PMA_getJsScriptToHandleSelectRelationalFields(); + +$header->getScripts()->addCode($code); + +// HTML output +$html = PMA_getHtmlForRelationalFieldSelection( + $db, $table, $_REQUEST['field'], $foreignData, + isset($fieldkey) ? $fieldkey : null, + isset($data) ? $data : null +); + +$response->addHtml($html); +?> diff --git a/sources/changelog.php b/sources/changelog.php new file mode 100644 index 0000000..5066ffe --- /dev/null +++ b/sources/changelog.php @@ -0,0 +1,153 @@ +disable(); + +$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 www.phpmyadmin.net for more information.'), + $filename + ); + exit; +} + +/** + * Whole changelog in variable. + */ +$changelog = htmlspecialchars($changelog); + +$tracker_url = 'https://sourceforge.net/support/tracker.php?aid=\\1'; +$tracker_url_bug = 'https://sourceforge.net/p/phpmyadmin/bugs/\\1/'; +$tracker_url_rfe = 'https://sourceforge.net/p/phpmyadmin/feature-requests/\\1/'; +$tracker_url_patch = 'https://sourceforge.net/p/phpmyadmin/patches/\\1/'; +$github_url = 'https://github.com/phpmyadmin/phpmyadmin/'; +$faq_url = 'http://docs.phpmyadmin.net/en/latest/faq.html'; + +$replaces = array( + '@(http://[./a-zA-Z0-9.-_-]*[/a-zA-Z0-9_])@' + => '\\1', + + // sourceforge users + '/([0-9]{4}-[0-9]{2}-[0-9]{2}) (.+[^ ]) +<(.*)@users.sourceforge.net>/i' + => '\\1 \\2', + '/thanks to ([^\(\r\n]+) \(([-\w]+)\)/i' + => 'thanks to \\1', + '/thanks to ([^\(\r\n]+) -\s+([-\w]+)/i' + => 'thanks to \\1', + + // mail address + '/([0-9]{4}-[0-9]{2}-[0-9]{2}) (.+[^ ]) +<(.*@.*)>/i' + => '\\1 \\2', + + // linking patches + '/patch\s*#?([0-9]{6,})/i' + => 'patch #\\1', + + // linking RFE + '/(?:rfe|feature)\s*#?([0-9]{6,})/i' + => 'RFE #\\1', + + // linking files + '/(\s+)([\\/a-z_0-9\.]+\.(?:php3?|html|pl|js|sh))/i' + => '\\1\\2', + + // FAQ entries + '/FAQ ([0-9]+)\.([0-9a-z]+)/i' + => 'FAQ \\1.\\2', + + // linking bugs + '/bug\s*#?([0-9]{6,})/i' + => 'bug #\\1', + + // all other 6+ digit numbers are treated as bugs + '/(? 'bug #\\1', + + // transitioned SF.net project bug/rfe/patch links + // by the time we reach 6-digit numbers, we can probably retire the above links + '/patch\s*#?([0-9]{4,5}) /i' + => 'patch #\\1 ', + '/(?:rfe|feature)\s*#?([0-9]{4,5}) /i' + => 'RFE #\\1 ', + '/bug\s*#?([0-9]{4,5}) /i' + => 'bug #\\1 ', + '/(? 'bug #\\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', + +); + +header('Content-type: text/html; charset=utf-8'); +?> + + + + + + phpMyAdmin - ChangeLog + + + +

phpMyAdmin - ChangeLog

+'; +echo preg_replace(array_keys($replaces), $replaces, $changelog); +echo ''; +?> + + + diff --git a/sources/chk_rel.php b/sources/chk_rel.php new file mode 100644 index 0000000..915e8b2 --- /dev/null +++ b/sources/chk_rel.php @@ -0,0 +1,15 @@ +addHTML( + PMA_getRelationsParamDiagnostic(PMA_getRelationsParam()) +); + +?> diff --git a/sources/composer.json b/sources/composer.json new file mode 100644 index 0000000..7b58ed7 --- /dev/null +++ b/sources/composer.json @@ -0,0 +1,27 @@ +{ + "name": "phpmyadmin/phpmyadmin", + "type": "application", + "description": "MySQL web administration tool", + "keywords": ["phpmyadmin","mysql","web"], + "homepage": "http://www.phpmyadmin.net/", + "license": "GPL-2.0+", + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "phpmyadmin-devel@lists.sourceforge.net", + "homepage": "http://www.phpmyadmin.net/home_page/team.php" + } + ], + "support": { + "forum": "https://sourceforge.net/p/phpmyadmin/discussion/Help", + "issues": "https://sourceforge.net/p/phpmyadmin/bugs/", + "wiki": "http://wiki.phpmyadmin.net/", + "source": "https://github.com/phpmyadmin/phpmyadmin" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "satooshi/php-coveralls": "dev-master" + } +} diff --git a/sources/config.sample.inc.php b/sources/config.sample.inc.php new file mode 100644 index 0000000..07047ac --- /dev/null +++ b/sources/config.sample.inc.php @@ -0,0 +1,152 @@ +. + * + * @package PhpMyAdmin + */ + +/* + * This is needed for cookie based authentication to encrypt password in + * cookie + */ +$cfg['blowfish_secret'] = 'a8b7c6d'; /* 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]['connect_type'] = 'tcp'; +$cfg['Servers'][$i]['compress'] = false; +/* Select mysql if your server does not have mysqli */ +$cfg['Servers'][$i]['extension'] = 'mysqli'; +$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]['designer_coords'] = 'pma__designer_coords'; +// $cfg['Servers'][$i]['userconfig'] = 'pma__userconfig'; +// $cfg['Servers'][$i]['recent'] = 'pma__recent'; +// $cfg['Servers'][$i]['users'] = 'pma__users'; +// $cfg['Servers'][$i]['usergroups'] = 'pma__usergroups'; +// $cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding'; +/* Contrib / Swekey authentication */ +// $cfg['Servers'][$i]['auth_swekey_config'] = '/etc/swekey-pma.conf'; + +/* + * End of servers configuration + */ + +/* + * Directories for saving/loading files from server + */ +$cfg['UploadDir'] = ''; +$cfg['SaveDir'] = ''; + +/** + * 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". + * default = 30 + */ +//$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'; + +/** + * default display direction (horizontal|vertical|horizontalflipped) + */ +//$cfg['DefaultDisplay'] = 'vertical'; + + +/** + * 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; + +/** + * Should error reporting be enabled for JavaScript errors + * + * default = 'ask' + */ +//$cfg['SendErrorReports'] = 'ask'; + +/* + * You can find more configuration options in the documentation + * in the doc/ folder or at . + */ +?> diff --git a/sources/db_create.php b/sources/db_create.php new file mode 100644 index 0000000..a5cd007 --- /dev/null +++ b/sources/db_create.php @@ -0,0 +1,135 @@ +tryQuery($sql_query); + +if (! $result) { + $message = PMA_Message::rawError($GLOBALS['dbi']->getError()); + // avoid displaying the not-created db name in header or navi panel + $GLOBALS['db'] = ''; + $GLOBALS['table'] = ''; + + /** + * If in an Ajax request, just display the message with {@link PMA_Response} + */ + if ($GLOBALS['is_ajax_request'] == true) { + $response = PMA_Response::getInstance(); + $response->isSuccess(false); + $response->addJSON('message', $message); + } else { + include_once 'index.php'; + } +} else { + $message = PMA_Message::success(__('Database %1$s has been created.')); + $message->addParam($_POST['new_db']); + $GLOBALS['db'] = $_POST['new_db']; + + /** + * If in an Ajax request, build the output and send it + */ + if ($GLOBALS['is_ajax_request'] == true) { + //Construct the html for the new database, so that it can be appended to + // the list of databases on server_databases.php + + /** + * Build the array to be passed to {@link PMA_URL_getCommon} + * to generate the links + * + * @global array $GLOBALS['db_url_params'] + * @name $db_url_params + */ + $db_url_params['db'] = $_POST['new_db']; + + $is_superuser = $GLOBALS['dbi']->isSuperuser(); + $column_order = PMA_getColumnOrder(); + $url_query = PMA_URL_getCommon($_POST['new_db']); + + /** + * String that will contain the output HTML + * @name $new_db_string + */ + $new_db_string = ''; + + if (empty($db_collation_for_ajax)) { + $db_collation_for_ajax = PMA_getServerCollation(); + } + + // $dbstats comes from the create table dialog + if (! empty($dbstats)) { + $current = array( + 'SCHEMA_NAME' => $_POST['new_db'], + 'DEFAULT_COLLATION_NAME' => $db_collation_for_ajax, + 'SCHEMA_TABLES' => '0', + 'SCHEMA_TABLE_ROWS' => '0', + 'SCHEMA_DATA_LENGTH' => '0', + 'SCHEMA_MAX_DATA_LENGTH' => '0', + 'SCHEMA_INDEX_LENGTH' => '0', + 'SCHEMA_LENGTH' => '0', + 'SCHEMA_DATA_FREE' => '0' + ); + } else { + $current = array( + 'SCHEMA_NAME' => $_POST['new_db'] + ); + } + + list($column_order, $generated_html) = PMA_buildHtmlForDb( + $current, $is_superuser, $url_query, + $column_order, $replication_types, $replication_info + ); + $new_db_string .= $generated_html; + + $new_db_string .= ''; + + $response = PMA_Response::getInstance(); + $response->addJSON('message', $message); + $response->addJSON('new_db_string', $new_db_string); + $response->addJSON( + 'sql_query', + PMA_Util::getMessage( + null, $sql_query, 'success' + ) + ); + } else { + include_once '' . $cfg['DefaultTabDatabase']; + } +} +?> diff --git a/sources/db_datadict.php b/sources/db_datadict.php new file mode 100644 index 0000000..f08a5c4 --- /dev/null +++ b/sources/db_datadict.php @@ -0,0 +1,291 @@ +getHeader(); +$header->enablePrintView(); + +/** + * Gets the relations settings + */ +$cfgRelation = PMA_getRelationsParam(); + +require_once 'libraries/transformations.lib.php'; +require_once 'libraries/Index.class.php'; + +/** + * Check parameters + */ +PMA_Util::checkParameters(array('db')); + +/** + * Defines the url to return to in case of error in a sql statement + */ +if (strlen($table)) { + $err_url = 'tbl_sql.php?' . PMA_URL_getCommon($db, $table); +} else { + $err_url = 'db_sql.php?' . PMA_URL_getCommon($db); +} + +if ($cfgRelation['commwork']) { + $comment = PMA_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 = PMA_getComments($db, $table); + + echo '
' . "\n"; + + echo '

' . htmlspecialchars($table) . '

' . "\n"; + + /** + * Gets table informations + */ + $show_comment = PMA_Table::sGetStatusInfo($db, $table, 'TABLE_COMMENT'); + + /** + * Gets table keys and retains them + */ + + $GLOBALS['dbi']->selectDb($db); + $indexes = $GLOBALS['dbi']->getTableIndexes($db, $table); + $primary = ''; + $indexes = array(); + $lastIndex = ''; + $indexes_info = array(); + $indexes_data = array(); + $pk_array = array(); // will be use to emphasis prim. keys in the table + // view + foreach ($indexes as $row) { + // Backups the list of primary keys + if ($row['Key_name'] == 'PRIMARY') { + $primary .= $row['Column_name'] . ', '; + $pk_array[$row['Column_name']] = 1; + } + // Retains keys informations + if ($row['Key_name'] != $lastIndex) { + $indexes[] = $row['Key_name']; + $lastIndex = $row['Key_name']; + } + $indexes_info[$row['Key_name']]['Sequences'][] = $row['Seq_in_index']; + $indexes_info[$row['Key_name']]['Non_unique'] = $row['Non_unique']; + if (isset($row['Cardinality'])) { + $indexes_info[$row['Key_name']]['Cardinality'] = $row['Cardinality']; + } + // I don't know what does following column mean.... + // $indexes_info[$row['Key_name']]['Packed'] = $row['Packed']; + + $indexes_info[$row['Key_name']]['Comment'] = $row['Comment']; + + $indexes_data[$row['Key_name']][$row['Seq_in_index']]['Column_name'] + = $row['Column_name']; + if (isset($row['Sub_part'])) { + $indexes_data[$row['Key_name']][$row['Seq_in_index']]['Sub_part'] + = $row['Sub_part']; + } + + } // end while + + /** + * Gets columns properties + */ + $columns = $GLOBALS['dbi']->getColumns($db, $table); + + if (PMA_MYSQL_INT_VERSION < 50025) { + // We need this to correctly learn if a TIMESTAMP is NOT NULL, since + // SHOW FULL COLUMNS or INFORMATION_SCHEMA incorrectly says NULL + // and SHOW CREATE TABLE says NOT NULL + // http://bugs.mysql.com/20910. + + $show_create_table_query = 'SHOW CREATE TABLE ' + . PMA_Util::backquote($db) . '.' + . PMA_Util::backquote($table); + $show_create_table = $GLOBALS['dbi']->fetchValue( + $show_create_table_query, 0, 1 + ); + $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table)); + } + + // Check if we can use Relations + if (!empty($cfgRelation['relation'])) { + // Find which tables are related with the current one and write it in + // an array + $res_rel = PMA_getForeigners($db, $table); + + if (count($res_rel) > 0) { + $have_rel = true; + } else { + $have_rel = false; + } + } else { + $have_rel = false; + } // end if + + + /** + * 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 ''; + $odd_row = true; + foreach ($columns as $row) { + + if ($row['Null'] == '') { + $row['Null'] = 'NO'; + } + $extracted_columnspec + = PMA_Util::extractColumnSpec($row['Type']); + + // reformat mysql query output + // set or enum types: slashes single quotes inside options + if ('set' == $extracted_columnspec['type'] + || 'enum' == $extracted_columnspec['type'] + ) { + $type_nowrap = ''; + + } else { + $type_nowrap = ' class="nowrap"'; + } + $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']; + + $tmp_column = $analyzed_sql[0]['create_table_fields'][$column_name]; + if (PMA_MYSQL_INT_VERSION < 50025 + && ! empty($tmp_column['type']) + && $tmp_column['type'] == 'TIMESTAMP' + && $tmp_column['timestamp_not_null'] + ) { + // here, we have a TIMESTAMP that SHOW FULL COLUMNS reports as + // having the NULL attribute, but SHOW CREATE TABLE says the + // contrary. Believe the latter. + /** + * @todo merge this logic with the one in tbl_structure.php + * or move it in a function similar to $GLOBALS['dbi']->getColumnsFull() + * but based on SHOW CREATE TABLE because information_schema + * cannot be trusted in this case (MySQL bug) + */ + $row['Null'] = 'NO'; + } + echo ''; + echo ''; + echo '' . $type . ''; + echo ''; + echo ''; + + if ($have_rel) { + echo ' ' . "\n"; + } + echo ' ' . "\n"; + if ($cfgRelation['mimework']) { + $mime_map = PMA_getMIME($db, $table, true); + + echo ' ' . "\n"; + } + echo ''; + } // end foreach + $count++; + echo '
' . __('Column') . '' . __('Type') . '' . __('Null') . '' . __('Default') . '' . __('Links to') . '' . __('Comments') . 'MIME
'; + + if (isset($pk_array[$row['Field']])) { + echo '' . htmlspecialchars($column_name) . ''; + } else { + echo htmlspecialchars($column_name); + } + echo ''; + echo (($row['Null'] == 'NO') ? __('No') : __('Yes')); + echo ''; + if (isset($row['Default'])) { + echo $row['Default']; + } + echo ''; + if (isset($res_rel[$column_name])) { + echo htmlspecialchars( + $res_rel[$column_name]['foreign_table'] + . ' -> ' + . $res_rel[$column_name]['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(PMA_Index::getFromTable($table, $db)) > 0) { + echo PMA_Index::getView($table, $db, true); + } + echo '
'; +} //ends main while + +/** + * Displays the footer + */ +echo PMA_Util::getButton(); + +?> diff --git a/sources/db_events.php b/sources/db_events.php new file mode 100644 index 0000000..917778b --- /dev/null +++ b/sources/db_events.php @@ -0,0 +1,26 @@ + diff --git a/sources/db_export.php b/sources/db_export.php new file mode 100644 index 0000000..5313e0e --- /dev/null +++ b/sources/db_export.php @@ -0,0 +1,94 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('export.js'); + +// $sub_part is also used in db_info.inc.php 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'; +require_once 'libraries/db_info.inc.php'; + +/** + * Displays the form + */ +$export_page_title = __('View dump (schema) of database'); + +// exit if no tables in db found +if ($num_tables < 1) { + PMA_Message::error(__('No tables found in database.'))->display(); + exit; +} // end if + +$multi_values = '
'; +$multi_values .= ''; +$multi_values .= __('Select All'); +$multi_values .= ''; +$multi_values .= ' / '; +$multi_values .= ''; +$multi_values .= __('Unselect All'); +$multi_values .= '
'; + +$multi_values .= '
'; + +$export_type = 'database'; +require_once 'libraries/display_export.inc.php'; + +?> diff --git a/sources/db_import.php b/sources/db_import.php new file mode 100644 index 0000000..70425b2 --- /dev/null +++ b/sources/db_import.php @@ -0,0 +1,25 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('import.js'); + +/** + * Gets tables informations and displays top links + */ +require 'libraries/db_common.inc.php'; +require 'libraries/db_info.inc.php'; + +$import_type = 'database'; +require 'libraries/display_import.inc.php'; + +?> diff --git a/sources/db_operations.php b/sources/db_operations.php new file mode 100644 index 0000000..6d0db19 --- /dev/null +++ b/sources/db_operations.php @@ -0,0 +1,300 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('db_operations.js'); + +/** + * Rename/move or copy database + */ +if (strlen($db) + && (! empty($_REQUEST['db_rename']) || ! empty($_REQUEST['db_copy'])) +) { + if (! empty($_REQUEST['db_rename'])) { + $move = true; + } else { + $move = false; + } + + if (! isset($_REQUEST['newname']) || ! strlen($_REQUEST['newname'])) { + $message = PMA_Message::error(__('The database name is empty!')); + } else { + $sql_query = ''; // in case target db exists + $_error = false; + if ($move + || (isset($_REQUEST['create_database_before_copying']) + && $_REQUEST['create_database_before_copying']) + ) { + $sql_query = PMA_getSqlQueryAndCreateDbBeforeCopy(); + } + + // 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 + PMA_runProcedureAndFunctionDefinitions($db); + + // go back to current db, just in case + $GLOBALS['dbi']->selectDb($db); + + $tables_full = $GLOBALS['dbi']->getTablesFull($db); + + include_once "libraries/plugin_interface.lib.php"; + // remove all foreign key constraints, otherwise we can get errors + $export_sql_plugin = PMA_getPlugin( + "export", + "sql", + 'libraries/plugins/export/', + array( + 'single_table' => isset($single_table), + 'export_type' => 'database' + ) + ); + $GLOBALS['sql_constraints_query_full_db'] + = PMA_getSqlConstraintsQueryForFullDb( + $tables_full, $export_sql_plugin, $move, $db + ); + + $views = PMA_getViewsAndCreateSqlViewStandIn( + $tables_full, $export_sql_plugin, $db + ); + + list($sql_query, $_error) = PMA_getSqlQueryForCopyTable( + $tables_full, $sql_query, $move, $db + ); + + // handle the views + if (! $_error) { + $_error = PMA_handleTheViews($views, $move, $db); + } + unset($views); + + // now that all tables exist, create all the accumulated constraints + if (! $_error && count($GLOBALS['sql_constraints_query_full_db']) > 0) { + PMA_createAllAccumulatedConstraints(); + } + + if (! PMA_DRIZZLE && PMA_MYSQL_INT_VERSION >= 50100) { + // here DELIMITER is not used because it's not part of the + // language; each statement is sent one by one + + PMA_runEventDefinitionsForDb($db); + } + + // go back to current db, just in case + $GLOBALS['dbi']->selectDb($db); + + // Duplicate the bookmarks for this db (done once for each db) + PMA_duplicateBookmarks($_error, $db); + + if (! $_error && $move) { + /** + * cleanup pmadb stuff for this db + */ + include_once 'libraries/relation_cleanup.lib.php'; + PMA_relationsCleanupDatabase($db); + + // if someday the RENAME DATABASE reappears, do not DROP + $local_query = 'DROP DATABASE ' . PMA_Util::backquote($db) . ';'; + $sql_query .= "\n" . $local_query; + $GLOBALS['dbi']->query($local_query); + + $message = PMA_Message::success( + __('Database %1$s has been renamed to %2$s') + ); + $message->addParam($db); + $message->addParam($_REQUEST['newname']); + } elseif (! $_error) { + $message = PMA_Message::success( + __('Database %1$s has been copied to %2$s') + ); + $message->addParam($db); + $message->addParam($_REQUEST['newname']); + } + $reload = true; + + /* Change database to be used */ + if (! $_error && $move) { + $db = $_REQUEST['newname']; + } elseif (! $_error) { + if (isset($_REQUEST['switch_to_new']) + && $_REQUEST['switch_to_new'] == 'true' + ) { + $GLOBALS['PMA_Config']->setCookie('pma_switch_to_new', 'true'); + $db = $_REQUEST['newname']; + } else { + $GLOBALS['PMA_Config']->setCookie('pma_switch_to_new', ''); + } + } + + if ($_error && ! isset($message)) { + $message = PMA_Message::error(); + } + } + + /** + * Database has been successfully renamed/moved. If in an Ajax request, + * generate the output with {@link PMA_Response} and exit + */ + if ($GLOBALS['is_ajax_request'] == true) { + $response = PMA_Response::getInstance(); + $response->isSuccess($message->isSuccess()); + $response->addJSON('message', $message); + $response->addJSON('newname', $_REQUEST['newname']); + $response->addJSON( + 'sql_query', + PMA_Util::getMessage(null, $sql_query) + ); + $response->addJSON('db', $db); + exit; + } +} + +/** + * Settings for relations stuff + */ + +$cfgRelation = PMA_getRelationsParam(); + +/** + * Check if comments were updated + * (must be done before displaying the menu tabs) + */ +if (isset($_REQUEST['comment'])) { + PMA_setDbComment($db, $_REQUEST['comment']); +} + +require 'libraries/db_common.inc.php'; +$url_query .= '&goto=db_operations.php'; + +// Gets the database structure +$sub_part = '_structure'; +require 'libraries/db_info.inc.php'; +echo "\n"; + +if (isset($message)) { + echo PMA_Util::getMessage($message, $sql_query); + unset($message); +} + +$_REQUEST['db_collation'] = PMA_getDbCollation($db); +$is_information_schema = $GLOBALS['dbi']->isSystemSchema($db); + +$response->addHTML('
'); + +if (!$is_information_schema) { + if ($cfgRelation['commwork']) { + /** + * database comment + */ + $response->addHTML(PMA_getHtmlForDatabaseComment($db)); + } + + $response->addHTML('
'); + ob_start(); + include 'libraries/display_create_table.lib.php'; + $content = ob_get_contents(); + ob_end_clean(); + $response->addHTML($content); + $response->addHTML('
'); + + /** + * rename database + */ + if ($db != 'mysql') { + $response->addHTML(PMA_getHtmlForRenameDatabase($db)); + } + + // 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 (($is_superuser || $GLOBALS['cfg']['AllowUserDropDatabase']) + && ! $db_is_information_schema + && (PMA_DRIZZLE || $db != 'mysql') + ) { + $response->addHTML(PMA_getHtmlForDropDatabaseLink($db)); + } + /** + * Copy database + */ + $response->addHTML(PMA_getHtmlForCopyDatabase($db)); + + /** + * Change database charset + */ + $response->addHTML(PMA_getHtmlForChangeDatabaseCharset($db, $table)); + + if ($num_tables > 0 + && ! $cfgRelation['allworks'] + && $cfg['PmaNoRelation_DisableWarning'] == false + ) { + $message = PMA_Message::notice( + __('The phpMyAdmin configuration storage has been deactivated. To find out why click %shere%s.') + ); + $message->addParam( + '', + false + ); + $message->addParam('', false); + /* Show error if user has configured something, notice elsewhere */ + if (!empty($cfg['Servers'][$server]['pmadb'])) { + $message->isError(true); + } + $response->addHTML('
'); + $response->addHTML($message->getDisplay()); + $response->addHTML('
'); + } // end if +} // end if (!$is_information_schema) + +$response->addHTML('
'); + +// not sure about displaying the PDF dialog in case db is information_schema +if ($cfgRelation['pdfwork'] && $num_tables > 0) { + // We only show this if we find something in the new pdf_pages table + $test_query = ' + SELECT * + FROM ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . PMA_Util::backquote($cfgRelation['pdf_pages']) . ' + WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''; + $test_rs = PMA_queryAsControlUser( + $test_query, + null, + PMA_DatabaseInterface::QUERY_STORE + ); + + /* + * Export Relational Schema View + */ + $response->addHTML(PMA_getHtmlForExportRelationalSchemaView($url_query)); +} // end if + +?> diff --git a/sources/db_printview.php b/sources/db_printview.php new file mode 100644 index 0000000..92f7002 --- /dev/null +++ b/sources/db_printview.php @@ -0,0 +1,178 @@ +getHeader(); +$header->enablePrintView(); + +PMA_Util::checkParameters(array('db')); + +/** + * Defines the url to return to in case of error in a sql statement + */ +$err_url = 'db_sql.php?' . PMA_URL_getCommon($db); + +/** + * Settings for relations stuff + */ +$cfgRelation = PMA_getRelationsParam(); + +/** + * If there is at least one table, displays the printer friendly view, else + * an error message + */ +$tables = $GLOBALS['dbi']->getTablesFull($db); +$num_tables = count($tables); + +echo '
'; + +// 1. No table +if ($num_tables == 0) { + echo __('No tables found in database.'); +} else { + // 2. Shows table information + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + if ($cfg['ShowStats']) { + echo ''; + } + echo ''; + echo ''; + echo ''; + echo ''; + $sum_entries = $sum_size = 0; + $odd_row = true; + foreach ($tables as $sts_data) { + if (PMA_Table::isMerge($db, $sts_data['TABLE_NAME']) + || strtoupper($sts_data['ENGINE']) == 'FEDERATED' + ) { + $merged_size = true; + } else { + $merged_size = false; + } + $sum_entries += $sts_data['TABLE_ROWS']; + echo ''; + echo ''; + + if (isset($sts_data['TABLE_ROWS'])) { + echo ''; + echo ''; + if ($cfg['ShowStats']) { + $tblsize = $sts_data['Data_length'] + $sts_data['Index_length']; + $sum_size += $tblsize; + list($formated_size, $unit) + = PMA_Util::formatByteDown($tblsize, 3, 1); + echo ''; + } // end if + } else { + echo ''; + } + echo ''; + echo ''; + } + echo ''; + echo ''; + echo ''; + echo ''; + if ($cfg['ShowStats']) { + list($sum_formated, $unit) + = PMA_Util::formatByteDown($sum_size, 3, 1); + echo ''; + } + echo ''; + echo ''; + echo ''; + echo '
' . __('Table') . '' . __('Rows') . '' . __('Type') . '' . __('Size') . '' . __('Comments') . '
'; + echo htmlspecialchars($sts_data['TABLE_NAME']); + echo ''; + if ($merged_size) { + echo ''; + echo PMA_Util::formatNumber($sts_data['TABLE_ROWS'], 0); + echo ''; + } else { + echo PMA_Util::formatNumber($sts_data['TABLE_ROWS'], 0); + } + echo ''; + echo $sts_data['ENGINE']; + echo ''; + echo $formated_size . ' ' . $unit; + echo ''; + echo __('in use'); + echo ''; + if (! empty($sts_data['Comment'])) { + echo htmlspecialchars($sts_data['Comment']); + $needs_break = '
'; + } else { + $needs_break = ''; + } + + if (! empty($sts_data['Create_time']) + || ! empty($sts_data['Update_time']) + || ! empty($sts_data['Check_time']) + ) { + echo $needs_break; + echo ''; + + if (! empty($sts_data['Create_time'])) { + echo ''; + echo ''; + echo ''; + echo ''; + } + + if (! empty($sts_data['Update_time'])) { + echo ''; + echo ''; + echo ''; + echo ''; + } + + if (! empty($sts_data['Check_time'])) { + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
' . __('Creation:') . ''; + echo PMA_Util::localisedDate(strtotime($sts_data['Create_time'])); + echo '
' . __('Last update:') . ''; + echo PMA_Util::localisedDate(strtotime($sts_data['Update_time'])); + echo '
' . __('Last check:') . ''; + echo PMA_Util::localisedDate(strtotime($sts_data['Check_time'])); + echo '
'; + } + echo '
'; + printf( + _ngettext('%s table', '%s tables', $num_tables), + PMA_Util::formatNumber($num_tables, 0) + ); + echo ''; + echo PMA_Util::formatNumber($sum_entries, 0); + echo ''; + echo '--'; + echo ''; + echo $sum_formated . ' ' . $unit; + echo '
'; +} + +/** + * Displays the footer + */ +echo PMA_Util::getButton(); + +echo "
\n"; +?> diff --git a/sources/db_qbe.php b/sources/db_qbe.php new file mode 100644 index 0000000..7fad34d --- /dev/null +++ b/sources/db_qbe.php @@ -0,0 +1,78 @@ + (maybe) execute it + */ +$message_to_display = false; +if (isset($_REQUEST['submit_sql']) && ! empty($sql_query)) { + if (! preg_match('@^SELECT@i', $sql_query)) { + $message_to_display = true; + } else { + $goto = 'db_sql.php'; + + // Parse and analyze the query + include_once 'libraries/parse_analyze.inc.php'; + + PMA_executeQueryAndSendQueryResponse( + $analyzed_sql_results, false, $_REQUEST['db'], null, null, null, null, + false, null, null, null, null, $goto, $pmaThemeImage, null, null, null, + $sql_query, null, null + ); + } +} + +$sub_part = '_qbe'; +require 'libraries/db_common.inc.php'; +$url_query .= '&goto=db_qbe.php'; +$url_params['goto'] = 'db_qbe.php'; +require 'libraries/db_info.inc.php'; + +if ($message_to_display) { + PMA_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 PMA_DBQbe($GLOBALS['db']); + +/** + * Displays the Query by example form + */ +if ($cfgRelation['designerwork']) { + $url = 'pmd_general.php' . PMA_URL_getCommon( + array_merge( + $url_params, + array('query' => 1) + ) + ); + $response->addHTML( + PMA_Message::notice( + sprintf( + __('Switch to %svisual builder%s'), + '', + '' + ) + ) + ); +} +$response->addHTML($db_qbe->getSelectionForm($cfgRelation)); +?> diff --git a/sources/db_routines.php b/sources/db_routines.php new file mode 100644 index 0000000..a58d4c0 --- /dev/null +++ b/sources/db_routines.php @@ -0,0 +1,27 @@ + diff --git a/sources/db_search.php b/sources/db_search.php new file mode 100644 index 0000000..acfec8e --- /dev/null +++ b/sources/db_search.php @@ -0,0 +1,57 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('db_search.js'); +$scripts->addFile('sql.js'); +$scripts->addFile('makegrid.js'); +$scripts->addFile('jquery/jquery-ui-timepicker-addon.js'); + +require 'libraries/db_common.inc.php'; + +// If config variable $GLOBALS['cfg']['Usedbsearch'] is on false : exit. +if (! $GLOBALS['cfg']['UseDbSearch']) { + PMA_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 PMA_DbSearch($GLOBALS['db']); + +// Display top links if we are not in an Ajax request +if ( $GLOBALS['is_ajax_request'] != true) { + include 'libraries/db_info.inc.php'; +} +$response->addHTML('
'); + +// Main search form has been submitted, get results +if (isset($_REQUEST['submit_search'])) { + $response->addHTML($db_search->getSearchResults()); +} + +// If we are in an Ajax request, we need to exit after displaying all the HTML +if ($GLOBALS['is_ajax_request'] == true && empty($_REQUEST['ajax_page_request'])) { + exit; +} + +// Display the search form +$response->addHTML($db_search->getSelectionForm($url_params)); +?> diff --git a/sources/db_sql.php b/sources/db_sql.php new file mode 100644 index 0000000..67bbbb5 --- /dev/null +++ b/sources/db_sql.php @@ -0,0 +1,44 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('functions.js'); +$scripts->addFile('makegrid.js'); +$scripts->addFile('sql.js'); + +require 'libraries/db_common.inc.php'; +require_once 'libraries/sql_query_form.lib.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( + PMA_getHtmlForSqlQueryForm( + true, false, + isset($_REQUEST['delimiter']) + ? htmlspecialchars($_REQUEST['delimiter']) + : ';' + ) +); + +?> diff --git a/sources/db_structure.php b/sources/db_structure.php new file mode 100644 index 0000000..0eed9ad --- /dev/null +++ b/sources/db_structure.php @@ -0,0 +1,317 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('db_structure.js'); +$scripts->addFile('tbl_change.js'); +$scripts->addFile('jquery/jquery-ui-timepicker-addon.js'); + +// Drops/deletes/etc. multiple tables if required +if ((!empty($_POST['submit_mult']) && isset($_POST['selected_tbl'])) + || isset($_POST['mult_btn']) +) { + $action = 'db_structure.php'; + $err_url = 'db_structure.php?'. PMA_URL_getCommon($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'] = PMA_Message::success(); + } +} +require 'libraries/db_common.inc.php'; +$url_query .= '&goto=db_structure.php'; + +// Gets the database structure +$sub_part = '_structure'; +require 'libraries/db_info.inc.php'; + +if (!PMA_DRIZZLE) { + include_once 'libraries/replication.inc.php'; +} else { + $server_slave_status = false; +} + +require_once 'libraries/bookmark.lib.php'; + +require_once 'libraries/mysql_charsets.inc.php'; +$db_collation = PMA_getDbCollation($db); + +$titles = PMA_Util::buildActionTitles(); + +// 1. No tables + +if ($num_tables == 0) { + $response->addHTML( + '

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

' . "\n" + ); + if (empty($db_is_information_schema)) { + ob_start(); + include 'libraries/display_create_table.lib.php'; + $content = ob_get_contents(); + ob_end_clean(); + $response->addHTML($content); + unset($content); + } // end if (Create Table dialog) + exit; +} + +// else +// 2. Shows table informations + +/** + * Displays the tables list + */ +$response->addHTML('
'); +$_url_params = array( + 'pos' => $pos, + 'db' => $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']; +} + +$response->addHTML( + PMA_Util::getListNavigator( + $total_num_tables, $pos, $_url_params, 'db_structure.php', + 'frame_content', $GLOBALS['cfg']['MaxTableList'] + ) +); + +// tables form +$response->addHTML( + '
' +); + +$response->addHTML(PMA_URL_getHiddenInputs($db)); + +$response->addHTML( + PMA_tableHeader($db_is_information_schema, $server_slave_status) +); + +$i = $sum_entries = 0; +$overhead_check = ''; +$create_time_all = ''; +$update_time_all = ''; +$check_time_all = ''; +$num_columns = $cfg['PropertiesNumColumns'] > 1 + ? ceil($num_tables / $cfg['PropertiesNumColumns']) + 1 + : 0; +$row_count = 0; +$sum_size = (double) 0; +$overhead_size = (double) 0; + +$hidden_fields = array(); +$odd_row = true; +$sum_row_count_pre = ''; + +foreach ($tables as $keyname => $current_table) { + // Get valid statistics whatever is the table type + + $drop_query = ''; + $drop_message = ''; + $overhead = ''; + + $table_is_view = false; + $table_encoded = urlencode($current_table['TABLE_NAME']); + // Sets parameters for links + $tbl_url_query = $url_query . '&table=' . $table_encoded; + // 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) + = PMA_getStuffForEngineTypeTable( + $current_table, $db_is_information_schema, + $is_show_stats, $table_is_view, $sum_size, $overhead_size + ); + + if (! PMA_Table::isMerge($db, $current_table['TABLE_NAME'])) { + $sum_entries += $current_table['TABLE_ROWS']; + } + + if (isset($current_table['Collation'])) { + $collation = '' + . $current_table['Collation'] . ''; + } else { + $collation = '---'; + } + + if ($is_show_stats) { + if ($formatted_overhead != '') { + $overhead = '' + . '' . $formatted_overhead . '' + . '' . $overhead_unit . '' + . '' . "\n"; + $overhead_check .= + "markAllRows('row_tbl_" . ($i + 1) . "');"; + } else { + $overhead = '-'; + } + } // end if + + unset($showtable); + + if ($GLOBALS['cfg']['ShowDbStructureCreation']) { + list($create_time, $create_time_all) = PMA_getTimeForCreateUpdateCheck( + $current_table, 'Create_time', $create_time_all + ); + } + + if ($GLOBALS['cfg']['ShowDbStructureLastUpdate']) { + // $showtable might already be set from ShowDbStructureCreation, see above + list($update_time, $update_time_all) = PMA_getTimeForCreateUpdateCheck( + $current_table, 'Update_time', $update_time_all + ); + } + + if ($GLOBALS['cfg']['ShowDbStructureLastCheck']) { + // $showtable might already be set from ShowDbStructureCreation, see above + list($check_time, $check_time_all) = PMA_getTimeForCreateUpdateCheck( + $current_table, 'Check_time', $check_time_all + ); + } + + list($alias, $truename) = PMA_getAliasAndTrueName( + $tooltip_aliasname, $current_table, $tooltip_truename + ); + + $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. + */ + list($browse_table, $search_table, $browse_table_label, $empty_table, + $tracking_icon) = PMA_getHtmlForActionLinks( + $current_table, $table_is_view, $tbl_url_query, + $titles, $truename, $db_is_information_schema, $url_query + ); + + if (! $db_is_information_schema) { + list($drop_query, $drop_message) + = PMA_getTableDropQueryAndMessage($table_is_view, $current_table); + } + + if ($num_columns > 0 + && $num_tables > $num_columns + && ($row_count % $num_columns) == 0 + ) { + $row_count = 1; + $odd_row = true; + + $response->addHTML( + '' + ); + + $response->addHTML(PMA_tableHeader(false, $server_slave_status)); + } + + list($do, $ignored) = PMA_getServerSlaveStatus( + $server_slave_status, $truename + ); + + list($html_output, $odd_row) = PMA_getHtmlForStructureTableRow( + $i, $odd_row, $table_is_view, $current_table, + $browse_table_label, $tracking_icon, $server_slave_status, + $browse_table, $tbl_url_query, $search_table, $db_is_information_schema, + $titles, $empty_table, $drop_query, $drop_message, $collation, + $formatted_size, $unit, $overhead, + (isset ($create_time) ? $create_time : ''), + (isset ($update_time) ? $update_time : ''), + (isset ($check_time) ? $check_time : ''), + $is_show_stats, $ignored, $do, $colspan_for_structure + ); + $response->addHTML($html_output); + +} // end foreach + +// Show Summary +$response->addHTML(''); +$response->addHTML( + PMA_getHtmlBodyForTableSummary( + $num_tables, $server_slave_status, $db_is_information_schema, $sum_entries, + $db_collation, $is_show_stats, $sum_size, $overhead_size, $create_time_all, + $update_time_all, $check_time_all, $sum_row_count_pre + ) +); +$response->addHTML(''); +//check all +$response->addHTML( + PMA_getHtmlForCheckAllTables( + $pmaThemeImage, $text_dir, $overhead_check, + $db_is_information_schema, $hidden_fields + ) +); +$response->addHTML('
'); //end of form + +// display again the table list navigator +$response->addHTML( + PMA_Util::getListNavigator( + $total_num_tables, $pos, $_url_params, 'db_structure.php', + 'frame_content', $GLOBALS['cfg']['MaxTableList'] + ) +); + +$response->addHTML('

'); + +/** + * Work on the database + */ +/* DATABASE WORK */ +/* Printable view of a table */ +$response->addHTML( + PMA_getHtmlForTablePrintViewLink($url_query) + . PMA_getHtmlForDataDictionaryLink($url_query) +); + +if (empty($db_is_information_schema)) { + ob_start(); + include 'libraries/display_create_table.lib.php'; + $content = ob_get_contents(); + ob_end_clean(); + $response->addHTML($content); +} // end if (Create Table dialog) + +?> diff --git a/sources/db_tracking.php b/sources/db_tracking.php new file mode 100644 index 0000000..bcd552c --- /dev/null +++ b/sources/db_tracking.php @@ -0,0 +1,252 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('db_structure.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'; + +// Get the database structure +$sub_part = '_structure'; +require 'libraries/db_info.inc.php'; + +// Work to do? +// (here, do not use $_REQUEST['db] as it can be crafted) +if (isset($_REQUEST['delete_tracking']) && isset($_REQUEST['table'])) { + PMA_Tracker::deleteTracking($GLOBALS['db'], $_REQUEST['table']); + + /** + * If in an Ajax request, generate the success message and use + * {@link PMA_Response()} to send the output + */ + if ($GLOBALS['is_ajax_request'] == true) { + $response = PMA_Response::getInstance(); + $response->addJSON('message', PMA_Message::success()); + exit; + } +} + +// Get tracked data about the database +$data = PMA_Tracker::getTrackedData($_REQUEST['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_information_schema)) { + include 'libraries/display_create_table.lib.php'; + } + exit; +} + +// --------------------------------------------------------------------------- + +// Prepare statement to get HEAD version +$all_tables_query = ' SELECT table_name, MAX(version) as version FROM ' . + PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) . '.' . + PMA_Util::backquote($GLOBALS['cfg']['Server']['tracking']) . + ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($_REQUEST['db']) . '\' ' . + ' GROUP BY table_name' . + ' ORDER BY table_name ASC'; + +$all_tables_result = PMA_queryAsControlUser($all_tables_query); + +// If a HEAD version exists +if ($GLOBALS['dbi']->numRows($all_tables_result) > 0) { + ?> +
+

+ + + + + + + + + + + + + + + + fetchArray($all_tables_result)) { + list($table_name, $version_number) = $one_result; + $table_query = ' SELECT * FROM ' . + PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) . '.' . + PMA_Util::backquote($GLOBALS['cfg']['Server']['tracking']) . + ' WHERE `db_name` = \'' . PMA_Util::sqlAddSlashes($_REQUEST['db']) + . '\' AND `table_name` = \'' . PMA_Util::sqlAddSlashes($table_name) + . '\' AND `version` = \'' . $version_number . '\''; + + $table_result = PMA_queryAsControlUser($table_query); + $version_data = $GLOBALS['dbi']->fetchArray($table_result); + + if ($version_data['tracking_active'] == 1) { + $version_status = __('active'); + } else { + $version_status = __('not active'); + } + $tmp_link = 'tbl_tracking.php?' . $url_query . '&table=' + . htmlspecialchars($version_data['table_name']); + $delete_link = 'db_tracking.php?' . $url_query . '&table=' + . htmlspecialchars($version_data['table_name']) + . '&delete_tracking=true&'; + ?> + + + + + + + + ' + . ''; + if ($style == 'even') { + $style = 'odd'; + } else { + $style = 'even'; + } + } + unset($tmp_link); + ?> + +
+ + + ' + . '' + . '' . __('Versions') . '' + . ' | ' + . '' . __('Tracking report') . '' + . ' | ' + . '' . __('Structure snapshot') + . '' + . '
+
+ $value) { + // If $value is a table group. + if (array_key_exists(('is' . $sep . 'group'), $value) + && $value['is' . $sep . 'group'] + ) { + foreach ($value as $temp_table) { + // If $temp_table is a table with the value for 'Name' is set, + // rather than a propery of the table group. + if (is_array($temp_table) + && array_key_exists('Name', $temp_table) + ) { + $tracking_version = PMA_Tracker::getVersion( + $GLOBALS['db'], + $temp_table['Name'] + ); + if ($tracking_version == -1) { + $my_tables[] = $temp_table['Name']; + } + } + } + } else { // If $value is a table. + if (PMA_Tracker::getVersion($GLOBALS['db'], $value['Name']) == -1) { + $my_tables[] = $value['Name']; + } + } +} + +// If untracked tables exist +if (isset($my_tables)) { + ?> +

+ + + + + + + + + + $tablename) { + if (PMA_Tracker::getVersion($GLOBALS['db'], $tablename) == -1) { + $my_link = ''; + $my_link .= PMA_Util::getIcon('eye.png', __('Track table')); + $my_link .= ''; + ?> + + + + + + +
+ 0) { + $log = ''; + foreach ($data['ddlog'] as $entry) { + $log .= '# ' . $entry['date'] . ' ' . $entry['username'] . "\n" + . $entry['statement'] . "\n"; + } + echo PMA_Util::getMessage(__('Database Log'), $log); +} + +?> diff --git a/sources/db_triggers.php b/sources/db_triggers.php new file mode 100644 index 0000000..d1a2a8c --- /dev/null +++ b/sources/db_triggers.php @@ -0,0 +1,25 @@ + diff --git a/sources/doc/Makefile b/sources/doc/Makefile new file mode 100644 index 0000000..1ee62c8 --- /dev/null +++ b/sources/doc/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = . + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/phpMyAdmin.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/phpMyAdmin.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/phpMyAdmin" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/phpMyAdmin" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/sources/doc/_ext/configext.py b/sources/doc/_ext/configext.py new file mode 100644 index 0000000..fdf3314 --- /dev/null +++ b/sources/doc/_ext/configext.py @@ -0,0 +1,189 @@ +from sphinx.locale import l_, _ +from sphinx.domains import Domain, ObjType +from sphinx.roles import XRefRole +from sphinx.domains.std import GenericObject, StandardDomain +from sphinx.directives import ObjectDescription +from sphinx.util.nodes import clean_astext, make_refnode +from sphinx.util import ws_re +from sphinx import addnodes +from sphinx.util.docfields import Field +from docutils import nodes + +def get_id_from_cfg(text): + ''' + Formats anchor ID from config option. + ''' + if text[:6] == '$cfg[\'': + text = text[6:] + if text[-2:] == '\']': + text = text[:-2] + text = text.replace('[$i]', '') + parts = text.split("']['") + return parts + + +class ConfigOption(ObjectDescription): + indextemplate = l_('configuration option; %s') + parse_node = None + + has_arguments = True + + doc_field_types = [ + Field('default', label=l_('Default value'), has_arg=False, + names=('default', )), + Field('type', label=l_('Type'), has_arg=False, + names=('type',)), + ] + + + def handle_signature(self, sig, signode): + signode.clear() + signode += addnodes.desc_name(sig, sig) + # normalize whitespace like XRefRole does + name = ws_re.sub('', sig) + return name + + def add_target_and_index(self, name, sig, signode): + targetparts = get_id_from_cfg(name) + targetname = 'cfg_%s' % '_'.join(targetparts) + signode['ids'].append(targetname) + self.state.document.note_explicit_target(signode) + indextype = 'single' + + # Generic index entries + indexentry = self.indextemplate % (name,) + self.indexnode['entries'].append((indextype, indexentry, + targetname, targetname)) + self.indexnode['entries'].append((indextype, name, + targetname, targetname)) + + # Server section + if targetparts[0] == 'Servers' and len(targetparts) > 1: + indexname = ', '.join(targetparts[1:]) + self.indexnode['entries'].append((indextype, l_('server configuration; %s') % indexname, + targetname, targetname)) + self.indexnode['entries'].append((indextype, indexname, + targetname, targetname)) + else: + indexname = ', '.join(targetparts) + self.indexnode['entries'].append((indextype, indexname, + targetname, targetname)) + + self.env.domaindata['config']['objects'][self.objtype, name] = \ + self.env.docname, targetname + + +class ConfigSectionXRefRole(XRefRole): + """ + Cross-referencing role for configuration sections (adds an index entry). + """ + + def result_nodes(self, document, env, node, is_ref): + if not is_ref: + return [node], [] + varname = node['reftarget'] + tgtid = 'index-%s' % env.new_serialno('index') + indexnode = addnodes.index() + indexnode['entries'] = [ + ('single', varname, tgtid, varname), + ('single', _('configuration section; %s') % varname, tgtid, varname) + ] + targetnode = nodes.target('', '', ids=[tgtid]) + document.note_explicit_target(targetnode) + return [indexnode, targetnode, node], [] + +class ConfigSection(ObjectDescription): + indextemplate = l_('configuration section; %s') + parse_node = None + + def handle_signature(self, sig, signode): + if self.parse_node: + name = self.parse_node(self.env, sig, signode) + else: + signode.clear() + signode += addnodes.desc_name(sig, sig) + # normalize whitespace like XRefRole does + name = ws_re.sub('', sig) + return name + + def add_target_and_index(self, name, sig, signode): + targetname = '%s-%s' % (self.objtype, name) + signode['ids'].append(targetname) + self.state.document.note_explicit_target(signode) + if self.indextemplate: + colon = self.indextemplate.find(':') + if colon != -1: + indextype = self.indextemplate[:colon].strip() + indexentry = self.indextemplate[colon+1:].strip() % (name,) + else: + indextype = 'single' + indexentry = self.indextemplate % (name,) + self.indexnode['entries'].append((indextype, indexentry, + targetname, targetname)) + self.env.domaindata['config']['objects'][self.objtype, name] = \ + self.env.docname, targetname + + +class ConfigOptionXRefRole(XRefRole): + """ + Cross-referencing role for configuration options (adds an index entry). + """ + + def result_nodes(self, document, env, node, is_ref): + if not is_ref: + return [node], [] + varname = node['reftarget'] + tgtid = 'index-%s' % env.new_serialno('index') + indexnode = addnodes.index() + indexnode['entries'] = [ + ('single', varname, tgtid, varname), + ('single', _('configuration option; %s') % varname, tgtid, varname) + ] + targetnode = nodes.target('', '', ids=[tgtid]) + document.note_explicit_target(targetnode) + return [indexnode, targetnode, node], [] + + +class ConfigFileDomain(Domain): + name = 'config' + label = 'Config' + + object_types = { + 'option': ObjType(l_('config option'), 'option'), + 'section': ObjType(l_('config section'), 'section'), + } + directives = { + 'option': ConfigOption, + 'section': ConfigSection, + } + roles = { + 'option': ConfigOptionXRefRole(), + 'section': ConfigSectionXRefRole(), + } + + initial_data = { + 'objects': {}, # (type, name) -> docname, labelid + } + + def clear_doc(self, docname): + for key, (fn, _) in self.data['objects'].items(): + if fn == docname: + del self.data['objects'][key] + + def resolve_xref(self, env, fromdocname, builder, + typ, target, node, contnode): + docname, labelid = self.data['objects'].get((typ, target), ('', '')) + if not docname: + return None + else: + return make_refnode(builder, fromdocname, docname, + labelid, contnode) + + def get_objects(self): + for (type, name), info in self.data['objects'].iteritems(): + yield (name, name, type, info[0], info[1], + self.object_types[type].attrs['searchprio']) + +def setup(app): + app.add_domain(ConfigFileDomain) + diff --git a/sources/doc/conf.py b/sources/doc/conf.py new file mode 100644 index 0000000..479aa2e --- /dev/null +++ b/sources/doc/conf.py @@ -0,0 +1,292 @@ +# -*- coding: utf-8 -*- +# +# phpMyAdmin documentation build configuration file, created by +# sphinx-quickstart on Wed Sep 26 14:04:48 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "_ext"))) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['configext'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'phpMyAdmin' +copyright = u'2012 - 2013, The phpMyAdmin devel team' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '4.1.4' +# The full version, including alpha/beta/rc tags. +release = version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build', 'html', 'doctrees'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'phpMyAdmindoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'phpMyAdmin.tex', u'phpMyAdmin Documentation', + u'The phpMyAdmin devel team', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'phpmyadmin', u'phpMyAdmin Documentation', + [u'The phpMyAdmin devel team'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'phpMyAdmin', u'phpMyAdmin Documentation', + u'The phpMyAdmin devel team', 'phpMyAdmin', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'phpMyAdmin' +epub_author = u'The phpMyAdmin devel team' +epub_publisher = u'The phpMyAdmin devel team' +epub_copyright = copyright + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True + +# Highlight PHP without starting `_). + + 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` + +.. 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['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['McryptDisableWarning'] + + :type: boolean + :default: false + + Disable the default warning that is displayed if mcrypt is missing for + cookie authentication. + + You can set this parameter to ``true`` to stop this message from appearing. + +.. config:option:: $cfg['ServerLibraryDifference_DisableWarning'] + + :type: boolean + :default: false + + 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['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 bellow + + 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'`` + * dot - ``'.'``, i.e., use named pipes on windows systems + * empty - ``''``, disables this server + + .. note:: + + phpMyAdmin supports connecting to MySQL servers reachable via IPv6 only. + To connect to an IPv6 MySQL server, enter its IPv6 address in this field. + +.. 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']`. + +.. 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. + +.. config:option:: $cfg['Servers'][$i]['ssl'] + + :type: boolean + :default: false + + Whether to enable SSL for the connection between phpMyAdmin and the MySQL server. + + When using :config:option:`$cfg['Servers'][$i]['extension']` = ``'mysql'``, + none of the remaining ``'ssl...'`` configuration options apply. + + We strongly recommend using :config:option:`$cfg['Servers'][$i]['extension']` = ``'mysqli'`` + when using this option. + +.. config:option:: $cfg['Servers'][$i]['ssl_key'] + + :type: string + :default: NULL + + Path to the key file when using SSL for connecting to the MySQL server. + + For example: + + .. code-block:: php + + $cfg['Servers'][$i]['ssl_key'] = '/etc/mysql/server-key.pem'; + +.. config:option:: $cfg['Servers'][$i]['ssl_cert'] + + :type: string + :default: NULL + + Path to the cert file when using SSL for connecting to the MySQL server. + +.. 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. + +.. config:option:: $cfg['Servers'][$i]['ssl_ca_path'] + + :type: string + :default: NULL + + Directory containing trusted SSL CA certificates in PEM format. + +.. config:option:: $cfg['Servers'][$i]['ssl_ciphers'] + + :type: string + :default: NULL + + List of allowable ciphers for SSL connections to the MySQL server. + +.. config:option:: $cfg['Servers'][$i]['connect_type'] + + :type: string + :default: ``'tcp'`` + + 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]['extension'] + + :type: string + :default: ``'mysqli'`` + + What php MySQL extension to use for the connection. Valid options are: + + ``mysql`` + The classic MySQL extension. + + ``mysqli`` + The improved MySQL extension. This extension became available with PHP + 5.0.0 and is the recommended way to connect to a server running MySQL + 4.1.x or newer. + +.. 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. + +.. _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. + +.. _controluser: +.. config:option:: $cfg['Servers'][$i]['controluser'] + + :type: string + :default: ``''`` + +.. config:option:: $cfg['Servers'][$i]['controlpass'] + + :type: string + :default: ``''`` + + This special account is used for 2 distinct purposes: to make possible all + relational features (see :config:option:`$cfg['Servers'][$i]['pmadb']`) and, + for a MySQL server running with ``--skip-show-database``, to enable a + multi-user installation (:term:`HTTP` or cookie + authentication mode). + + When using :term:`HTTP` or + cookie authentication modes (or 'config' authentication mode since phpMyAdmin + 2.2.1), you need to supply the details of a MySQL account that has ``SELECT`` + privilege on the *mysql.user (all columns except "Password")*, *mysql.db (all + columns)* and *mysql.tables\_priv (all columns except "Grantor" and + "Timestamp")* tables. This account is used to check what databases the user + will see at login. + + .. versionchanged:: 2.2.5 + those were called ``stduser`` and ``stdpass`` + + .. seealso:: :ref:`setup`, :ref:`authentication_modes` + +.. 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'``) as + introduced in 2.2.3 allows you to log in as any valid MySQL user with + the help of cookies. Username and password are stored in cookies + during the session and password is deleted when it ends. This can also + allow you to log in in arbitrary server if :config:option:`$cfg['AllowArbitraryServer']` enabled. + * 'http' authentication (was + called 'advanced' in previous versions and can be written also as + 'http') (``$auth_type = 'http';'``) as introduced in 1.3.0 allows you to log in as any + valid MySQL user via HTTP-Auth. + * 'signon' authentication mode (``$auth_type = 'signon'``) as + introduced in 2.10.0 allows you to log in from prepared PHP session + data or using supplied PHP script. This is useful for implementing + single signon from another application. Sample way how to seed session + is in signon example: :file:`examples/signon.php`. There is also + alternative example using OpenID - :file:`examples/openid.php` and example + for scripts based solution - :file:`examples/signon-script.php`. You need + to configure :config:option:`$cfg['Servers'][$i]['SignonSession']` or + :config:option:`$cfg['Servers'][$i]['SignonScript']` and + :config:option:`$cfg['Servers'][$i]['SignonURL']` to use this authentication + method. + + .. 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_auth_swekey_config: +.. config:option:: $cfg['Servers'][$i]['auth_swekey_config'] + + :type: string + :default: ``''`` + + The name of the file containing :ref:`swekey` ids and login names for hardware + authentication. Leave empty to deactivate this feature. + +.. _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 + + 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]['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 + :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']` + + +.. _relation: +.. config:option:: $cfg['Servers'][$i]['relation'] + + :type: string + :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. + + .. 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 + :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. + + .. seealso:: :ref:`faqdisplay` + +.. _table_coords: +.. config:option:: $cfg['Servers'][$i]['table_coords'] + + :type: string + :default: ``''`` + +.. config:option:: $cfg['Servers'][$i]['pdf_pages'] + + :type: string + :default: ``''`` + + Since release 2.3.0 you can have phpMyAdmin create :term:`PDF` pages + showing the relations between your tables. 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']` + + .. seealso:: :ref:`faqpdf`. + +.. _col_com: +.. config:option:: $cfg['Servers'][$i]['column_info'] + + :type: string + :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'. + + + 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 + :config:option:`$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; + +.. _history: +.. config:option:: $cfg['Servers'][$i]['history'] + + :type: string + :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``) + +.. _recent: +.. config:option:: $cfg['Servers'][$i]['recent'] + + :type: string + :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``) + +.. _table_uiprefs: +.. config:option:: $cfg['Servers'][$i]['table_uiprefs'] + + :type: string + :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``) + +.. _configurablemenus: +.. config:option:: $cfg['Servers'][$i]['users'] + + :type: string + :default: ``''`` + +.. config:option:: $cfg['Servers'][$i]['usergroups'] + + :type: string + :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``) + +.. _navigationhiding: +.. config:option:: $cfg['Servers'][$i]['navigationhiding'] + + :type: string + :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``) + +.. _tracking: +.. config:option:: $cfg['Servers'][$i]['tracking'] + + :type: string + :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``) + + +.. _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 + :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']` + + + +.. _designer_coords: +.. config:option:: $cfg['Servers'][$i]['designer_coords'] + + :type: string + :default: ``''`` + + Since release 2.10.0 a Designer interface is available; it permits to + visually manage the relations. + + 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\_coords']` + (e.g. ``pma__designer_coords``) + + + +.. 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]['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) + +.. config:option:: $cfg['Servers'][$i]['SignonScript'] + + :type: string + :default: ``''`` + + Name of PHP script to be sourced and executed to obtain login + credentials. This is alternative approach to session based single + signon. The script needs to provide function + ``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. + +.. 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. + +.. 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. + +.. 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. + +.. config:option:: $cfg['Servers'][$i]['StatusCacheDatabases'] + + :type: array of strings + :default: array() + + Enables caching of ``TABLE STATUS`` outputs for specific databases on + this server (in some cases ``TABLE STATUS`` can be very slow, so you + may want to cache it). APC is used (if the PHP extension is available, + if not, this setting is ignored silently). You have to provide + :config:option:`$cfg['Servers'][$i]['StatusCacheLifetime']`. + +.. config:option:: $cfg['Servers'][$i]['StatusCacheLifetime'] + + :type: integer + :default: 0 + + Lifetime in seconds of the ``TABLE STATUS`` cache if + :config:option:`$cfg['Servers'][$i]['StatusCacheDatabases']` is used. + +Generic settings +---------------- + +.. 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 main phpMyAdmin + page. + + .. 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 + intenet 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). This limit is also enforced in the + navigation panel when in Light mode. + +.. 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 + + Whether to force using https while accessing phpMyAdmin. + + .. 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 + `_). + +.. 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. + + This setting is used while importing/exporting dump files and at some other + places in phpMyAdmin 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['AllowUserDropDatabase'] + + :type: boolean + :default: false + + 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. + + .. note:: + + 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 blowfish 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 + blowfish algorithm: you won’t be prompted for this passphrase. There + is no maximum length for this secret. + + .. 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. + +.. config:option:: $cfg['CaptchaLoginPublicKey'] + + :type: string + :default: ``''`` + + The public key for the reCaptcha service that can be obtain from + http://www.google.com/recaptcha. + + 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 + http://www.google.com/recaptcha. + + reCaptcha will be then used in :ref:`cookie`. + +Navigation panel setup +---------------------- + +.. config:option:: $cfg['MaxNavigationItems'] + + :type: integer + :default: 250 + + The number of items 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 or array + :default: ``'_'`` + + The string used to separate the parts of the database name when + showing them in a tree. Alternatively you can specify more strings in + an array and all of them will be used as a separator. + +.. 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['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. + +.. 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: ``'tbl_structure.php'`` + + Defines the tab displayed by default when clicking the small icon next + to each table name in the navigation panel. Possible values: + + * ``tbl_structure.php`` + * ``tbl_sql.php`` + * ``tbl_select.php`` + * ``tbl_change.php`` + * ``sql.php`` + +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 + +.. config:option:: $cfg['ShowChgPassword'] + + :type: boolean + :default: true + +.. config:option:: $cfg['ShowCreateDb'] + + :type: boolean + :default: true + + Defines whether to display the :guilabel:`PHP information` and + :guilabel:`Change password` links and form for creating database or not at + the starting main (right) frame. This setting does not check MySQL commands + entered directly. + + 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() + + Also 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. + +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. + +Browse mode +----------- + +.. config:option:: $cfg['TableNavigationLinksMode'] + + :type: string + :default: ``'icons'`` + + Defines whether the table navigation links contain ``'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. + +.. config:option:: $cfg['DisplayBinaryAsHex'] + + :type: boolean + :default: true + + Defines whether the "Show binary contents as HEX" browse option is + ticked by default. + +.. config:option:: $cfg['GridEditing'] + + :type: string + :default: ``'double-click'`` + + Defines which action (``double-click`` or ``click``) triggers grid + editing. Can be deactived with the ``disabled`` value. + +.. 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 maximum number of concurrent entries for the Insert page. + +.. 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']['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['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. + + +Tabs display settings +--------------------- + +.. config:option:: $cfg['TabsMode'] + + :type: string + :default: ``'both'`` + + Defines whether the menu tabs 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['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: ``'index.php'`` + + Defines the tab displayed by default on server view. Possible values: + + * ``main.php`` (recommended for multi-user setups) + * ``server_databases.php``, + * ``server_status.php`` + * ``server_variables.php`` + * ``server_privileges.php`` + +.. config:option:: $cfg['DefaultTabDatabase'] + + :type: string + :default: ``'db_structure.php'`` + + Defines the tab displayed by default on database view. Possible + values: + + * ``db_structure.php`` + * ``db_sql.php`` + * ``db_search.php``. + +.. config:option:: $cfg['DefaultTabTable'] + + :type: string + :default: ``'sql.php'`` + + Defines the tab displayed by default on table view. Possible values: + + * ``tbl_structure.php`` + * ``tbl_sql.php`` + * ``tbl_select.php`` + * ``tbl_change.php`` + * ``sql.php`` + +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: ``'utf8_general_ci'`` + + Defines the default connection collation to use, if not user-defined. + See the `MySQL documentation `_ for list of possible values. This setting is + ignored when connected to Drizzle server. + +.. 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 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 +-------------- + +.. config:option:: $cfg['NaviWidth'] + + :type: integer + :default: + + Navigation panel width in pixels. See + :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['NaviBackground'] + + :type: string [CSS color for background] + :default: + +.. config:option:: $cfg['MainBackground'] + + :type: string [CSS color for background] + :default: + + The background styles used for both the frames. See + :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['NaviPointerBackground'] + + :type: string [CSS color for background] + :default: + +.. config:option:: $cfg['NaviPointerColor'] + + :type: string [CSS color] + :default: + + The style used for the pointer in the navigation panel. See + :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['Border'] + + :type: integer + :default: + + The size of a table's border. See :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['ThBackground'] + + :type: string [CSS color for background] + :default: + +.. config:option:: $cfg['ThColor'] + + :type: string [CSS color] + :default: + + The style used for table headers. See + :file:`themes/themename/layout.inc.php`. + +.. _cfg_BgcolorOne: +.. config:option:: $cfg['BgOne'] + + :type: string [CSS color] + :default: + + The color (HTML) #1 for table rows. See + :file:`themes/themename/layout.inc.php`. + +.. _cfg_BgcolorTwo: +.. config:option:: $cfg['BgTwo'] + + :type: string [CSS color] + :default: + + The color (HTML) #2 for table rows. See + :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['BrowsePointerBackground'] + + :type: string [CSS color] + :default: + +.. config:option:: $cfg['BrowsePointerColor'] + + :type: string [CSS color] + :default: + +.. config:option:: $cfg['BrowseMarkerBackground'] + + :type: string [CSS color] + :default: + +.. config:option:: $cfg['BrowseMarkerColor'] + + :type: string [CSS color] + :default: + + The colors (HTML) uses for the pointer and the marker in browse mode. + The former feature highlights the row over which your mouse is passing + and the latter lets you visually mark/unmark rows by clicking on the + corresponding checkbox. Highlighting / marking a column is done by + hovering over / clicking the column's header (outside of the text). + See :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['FontFamily'] + + :type: string + :default: + + You put here a valid CSS font family value, for example ``arial, sans- + serif``. See :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['FontFamilyFixed'] + + :type: string + :default: + + You put here a valid CSS font family value, for example ``monospace``. + This one is used in textarea. See :file:`themes/themename/layout.inc.php`. + +Design customization +-------------------- + +.. config:option:: $cfg['NavigationTreePointerEnable'] + + :type: boolean + :default: true + + A value of ``true`` activates the navi pointer. + +.. config:option:: $cfg['BrowsePointerEnable'] + + :type: boolean + :default: true + + Whether to activate the browse pointer or not. + +.. config:option:: $cfg['BrowseMarkerEnable'] + + :type: boolean + :default: true + + Whether to activate the browse marker or not. + +.. 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). "left" and "right" + are parsed as "top" and "bottom" with vertical display mode. + +.. config:option:: $cfg['DefaultDisplay'] + + :type: string + :default: ``'horizonta'`` + + There are 3 display modes: horizontal, horizontalflipped and vertical. + Define which one is displayed by default. The first mode displays each + row on a horizontal line, the second rotates the headers by 90 + degrees, so you can use descriptive headers even though columns only + contain small values and still print them out. The vertical mode sorts + each row on a vertical lineup. + +.. config:option:: $cfg['RememberSorting'] + + :type: boolean + :default: true + + If enabled, remember the sorting of each table when browsing them. + +.. config:option:: $cfg['HeaderFlipType'] + + :type: string + :default: ``'auto'`` + + The HeaderFlipType can be set to 'auto', 'css' or 'fake'. When using + 'css' the rotation of the header for horizontalflipped is done via + CSS. The CSS transformation currently works only in Internet + Explorer.If set to 'fake' PHP does the transformation for you, but of + course this does not look as good as CSS. The 'auto' option enables + CSS transformation when browser supports it and use PHP based one + otherwise. + +.. 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. 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. + + +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']['Validate'] + + :type: boolean + :default: false + + Whether to display a link to validate a query in any SQL Query box. + + .. seealso:: :config:option:`$cfg['SQLValidator']` + +.. config:option:: $cfg['SQLQuery']['Refresh'] + + :type: boolean + :default: true + + Whether to display a link to refresh a query in any SQL Query box. + +Web server upload/save/import directories +----------------------------------------- + +.. 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. + + .. note:: + + If PHP is running in safe mode, this directory must be owned by the same + user as the owner of the phpMyAdmin scripts. See also :ref:`faq1_16` for + alternatives. + +.. config:option:: $cfg['SaveDir'] + + :type: string + :default: ``''`` + + The name of the directory where dumps can be saved. + + If you want different directory for each user, %u will be replaced with + username. + + Please note that the directory must exist and has to be writable for + the user running webserver. + + .. note:: + + If PHP is running in safe mode, this directory must be owned by the same + user as the owner of the phpMyAdmin scripts. + +.. config:option:: $cfg['TempDir'] + + :type: string + :default: ``''`` + + The name of the directory where temporary files can be stored. + + This is needed for importing ESRI Shapefiles, see :ref:`faq6_30` and to + work around limitations of ``open_basedir`` for uploaded files, see + :ref:`faq1_11`. + + 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 web server. + However for security reasons, this directory should be outside the + tree published by webserver. If you cannot avoid having this directory + published by webserver, place at least an empty :file:`index.html` file + there, so that directory listing is not possible. + + 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. + +Various display setting +----------------------- + +.. config:option:: $cfg['ShowDisplayDirection'] + + :type: boolean + :default: false + + Defines whether or not type display direction option is shown when + browsing a table. + +.. config:option:: $cfg['RepeatCells'] + + :type: integer + :default: 100 + + Repeat the headers every X cells, or 0 to deactivate. + +.. config:option:: $cfg['EditInWindow'] + + :type: boolean + :default: true + +.. config:option:: $cfg['QueryWindowWidth'] + + :type: integer + :default: 550 + +.. config:option:: $cfg['QueryWindowHeight'] + + :type: integer + :default: 310 + +.. config:option:: $cfg['QueryHistoryDB'] + + :type: boolean + :default: false + +.. config:option:: $cfg['QueryWindowDefTab'] + + :type: string + :default: ``'sql'`` + +.. config:option:: $cfg['QueryHistoryMax'] + + :type: integer + :default: 25 + + All those variables affect the query window feature. A :term:`SQL` link or + icon is always displayed in the navigation panel. If JavaScript is enabled + in your browser, a click on this opens a distinct query window, which is a + direct interface to enter :term:`SQL` queries. Otherwise, the right panel + changes to display a query box. + + The size of this query window can be customized with + :config:option:`$cfg['QueryWindowWidth']` and + :config:option:`$cfg['QueryWindowHeight']` - both integers for the size in + pixels. Note that normally, those parameters will be modified in + :file:`layout.inc.php`` for the theme you are using. + + If :config:option:`$cfg['EditInWindow']` is set to true, a click on [Edit] + from the results page (in the :guilabel:`Showing Rows` section) opens the + query window and puts the current query inside it. If set to false, + clicking on the link puts the :term:`SQL` query + in the right panel's query box. + + 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']`. + + The query window also has a custom tabbed look to group the features. + Using the variable :config:option:`$cfg['QueryWindowDefTab']` you can + specify the default tab to be used when opening the query window. It can be + set to either ``sql``, ``files``, ``history`` or ``full``. + +.. config:option:: $cfg['BrowseMIME'] + + :type: boolean + :default: true + + Enable :ref:`transformations`. + +.. config:option:: $cfg['MaxExactCount'] + + :type: integer + :default: 0 + + 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. + +.. 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 :file:`libraries/config/user_preferences.forms.php`. + +.. 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['ThemePath'] + + :type: string + :default: ``'./themes'`` + + If theme manager is active, use this as the path of the subdirectory + containing all the themes. + +.. 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 :config:option:`$cfg['ThemePath']`). + +.. config:option:: $cfg['ThemePerServer'] + + :type: boolean + :default: false + + Whether to allow different theme for each server. + +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`. + +SQL validator settings +---------------------- + +.. config:option:: $cfg['SQLValidator'] + + :type: array + :default: array(...) + + + +.. config:option:: $cfg['SQLValidator']['use'] + + :type: boolean + :default: false + + phpMyAdmin now supports use of the `Mimer SQL Validator + `_ service, as originally + published on `Slashdot + `_. For + help in setting up your system to use the service, see the + :ref:`faqsqlvalidator`. + +.. config:option:: $cfg['SQLValidator']['username'] + + :type: string + :default: ``''`` + +.. config:option:: $cfg['SQLValidator']['password'] + + :type: string + :default: ``''`` + + The SOAP service allows you to log in with ``anonymous`` and any password, + so we use those by default. Instead, if you have an account with them, you + can put your login details here, and it will be used in place of the + anonymous login. + +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. + + +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 bottom of main page (right frame). + +.. config:option:: $cfg['DBG']['demo'] + + :type: boolean + :default: false + + Enable to let server present itself as demo server. + This is used for . + +.. config:option:: $cfg['Error_Handler']['display'] + + :type: boolean + :default: false + + Whether to display errors from PHP or not. + diff --git a/sources/doc/copyright.rst b/sources/doc/copyright.rst new file mode 100644 index 0000000..cfcb863 --- /dev/null +++ b/sources/doc/copyright.rst @@ -0,0 +1,30 @@ +.. _copyright: + +Copyright +========= + +.. code-block:: none + + Copyright (C) 1998-2000 Tobias Ratschiller + Copyright (C) 2001-2013 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 `http://www.gnu.org/licenses/ +`_. diff --git a/sources/doc/credits.rst b/sources/doc/credits.rst new file mode 100644 index 0000000..61c7758 --- /dev/null +++ b/sources/doc/credits.rst @@ -0,0 +1,626 @@ +.. _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 + + * :term:`SQL` analyser (most of it) + + * current project maintainer + + * maintains content on our website + +* 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" + (http://sf.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 (see ) 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 (`http://www.tcpdf.org `_) + +* 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 + + +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: + +* Arabic + + * Abdullah Al-Saedi + +* Bulgarian + + * stoyanster + +* Catalan + + * Xavier Navarro + +* Czech + + * Michal Čihař + +* Danish + + * opensource + * Jørgen Thomsen + +* German + + * mrbendig + * torsten.funck + * Sven Strickroth + * typo3 + * Jo Michael + +* Greek + + * Panagiotis Papazoglou + +* English (United Kingdom) + + * Robert Readman + +* Spanish + + * Matías Bellone + +* French + + * Marc Delisle + +* Hindi + + * u4663530 + * rsedwardian + +* Hungarian + + * gergo314 + +* Italian + + * Rouslan Placella + +* Japanese + + * Yuichiro + +* Lithuanian + + * Kęstutis + +* Norwegian Bokmål + + * Sven-Erik Andersen + +* Dutch + + * Dieter Adriaenssens + * Herman van Rink + +* Polish + + * Stanisław Krukowski + * Marcin Kozioł + +* Portuguese + + * JoaoTMDias + +* Portuguese (Brazil) + + * wiltave + * emerson4br + +* Romanian + + * alexukf + +* Russian + + * Victor Volkov + +* Sinhala + + * Madhura Jayaratne + +* Slovak + + * Martin Lacina + +* Slovenian + + * Domen + +* Swedish + + * stefan + +* Tamil + + * ysajeepan + +* Telugu + + * veeven + +* Thai + + * kanitchet + +* Turkish + + * Burak Yavuz + +* Uighur + + * gheni + +* Ukrainian + + * typim + * oleg-ilnytskyi + +* Urdu + + * Mehbooob Khan + +* Simplified Chinese + + * shanyan baishui + +* Traditional Chinese + + * star + +Documentation translators ++++++++++++++++++++++++++ + +Following people have contributed to translation of phpMyAdmin documentation: + +* Czech + + * Michal Čihař + +* Greek + + * Panagiotis Papazoglou + +* English (United Kingdom) + + * Robert Readman + +* French + + * Cédric Corazza + +* Japanese + + * Yuichiro Takahashi + +* Polish + + * Stanisław Krukowski + +* Portuguese (Brazil) + + * mjaning + +* Slovenian + + * Domen + +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/sources/doc/developers.rst b/sources/doc/developers.rst new file mode 100644 index 0000000..5574527 --- /dev/null +++ b/sources/doc/developers.rst @@ -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 +`_. \ No newline at end of file diff --git a/sources/doc/doctrees/config.doctree b/sources/doc/doctrees/config.doctree new file mode 100644 index 0000000..3925cc8 Binary files /dev/null and b/sources/doc/doctrees/config.doctree differ diff --git a/sources/doc/doctrees/copyright.doctree b/sources/doc/doctrees/copyright.doctree new file mode 100644 index 0000000..7d3e190 Binary files /dev/null and b/sources/doc/doctrees/copyright.doctree differ diff --git a/sources/doc/doctrees/credits.doctree b/sources/doc/doctrees/credits.doctree new file mode 100644 index 0000000..e01addb Binary files /dev/null and b/sources/doc/doctrees/credits.doctree differ diff --git a/sources/doc/doctrees/developers.doctree b/sources/doc/doctrees/developers.doctree new file mode 100644 index 0000000..4c3b363 Binary files /dev/null and b/sources/doc/doctrees/developers.doctree differ diff --git a/sources/doc/doctrees/environment.pickle b/sources/doc/doctrees/environment.pickle new file mode 100644 index 0000000..3597c96 Binary files /dev/null and b/sources/doc/doctrees/environment.pickle differ diff --git a/sources/doc/doctrees/faq.doctree b/sources/doc/doctrees/faq.doctree new file mode 100644 index 0000000..b9629fb Binary files /dev/null and b/sources/doc/doctrees/faq.doctree differ diff --git a/sources/doc/doctrees/glossary.doctree b/sources/doc/doctrees/glossary.doctree new file mode 100644 index 0000000..cc505f8 Binary files /dev/null and b/sources/doc/doctrees/glossary.doctree differ diff --git a/sources/doc/doctrees/index.doctree b/sources/doc/doctrees/index.doctree new file mode 100644 index 0000000..ffcb610 Binary files /dev/null and b/sources/doc/doctrees/index.doctree differ diff --git a/sources/doc/doctrees/intro.doctree b/sources/doc/doctrees/intro.doctree new file mode 100644 index 0000000..e24fe55 Binary files /dev/null and b/sources/doc/doctrees/intro.doctree differ diff --git a/sources/doc/doctrees/other.doctree b/sources/doc/doctrees/other.doctree new file mode 100644 index 0000000..5d7a434 Binary files /dev/null and b/sources/doc/doctrees/other.doctree differ diff --git a/sources/doc/doctrees/privileges.doctree b/sources/doc/doctrees/privileges.doctree new file mode 100644 index 0000000..4ab7a3c Binary files /dev/null and b/sources/doc/doctrees/privileges.doctree differ diff --git a/sources/doc/doctrees/require.doctree b/sources/doc/doctrees/require.doctree new file mode 100644 index 0000000..ef740b8 Binary files /dev/null and b/sources/doc/doctrees/require.doctree differ diff --git a/sources/doc/doctrees/setup.doctree b/sources/doc/doctrees/setup.doctree new file mode 100644 index 0000000..d1d6f70 Binary files /dev/null and b/sources/doc/doctrees/setup.doctree differ diff --git a/sources/doc/doctrees/transformations.doctree b/sources/doc/doctrees/transformations.doctree new file mode 100644 index 0000000..6cba2d9 Binary files /dev/null and b/sources/doc/doctrees/transformations.doctree differ diff --git a/sources/doc/doctrees/user.doctree b/sources/doc/doctrees/user.doctree new file mode 100644 index 0000000..a316229 Binary files /dev/null and b/sources/doc/doctrees/user.doctree differ diff --git a/sources/doc/doctrees/vendors.doctree b/sources/doc/doctrees/vendors.doctree new file mode 100644 index 0000000..b35aac0 Binary files /dev/null and b/sources/doc/doctrees/vendors.doctree differ diff --git a/sources/doc/faq.rst b/sources/doc/faq.rst new file mode 100644 index 0000000..1fa3768 --- /dev/null +++ b/sources/doc/faq.rst @@ -0,0 +1,2015 @@ +.. _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 or Bzip a dump or a CSV export? It does not seem to work. +---------------------------------------------------------------------------- + +These features are based on the ``gzencode()`` and ``bzcompress()`` +PHP functions to be more independent of the platform (Unix/Windows, +Safe Mode or not, and so on). So, you must have Zlib/Bzip2 support +(``--with-zlib`` and ``--with-bz2``). + +.. _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 query box. +------------------------------------------------------------------------------------ + +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? +------------------------------------------------------- + +The MySQL manual explains how to `reset the permissions +`_. + +.. _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 ``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. One user also said that ``post_max_size`` and +``memory_limit`` need 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 MySQL versions does phpMyAdmin support? +-------------------------------------------------- + +Since phpMyAdmin 3.0.x, only MySQL 5.0.1 and newer are supported. For +older MySQL versions, you need to use the latest 2.x branch. +phpMyAdmin can connect to your MySQL server using PHP's classic `MySQL +extension `_ as well as the `improved MySQL +extension (MySQLi) `_ that is available in PHP +5.0. The latter one should be used unless you have a good reason not +to do so. When compiling PHP, we strongly recommend that you manually +link the MySQL extension of your choice to a MySQL client library of +at least the same minor version since the one that is bundled with +some PHP distributions is rather old and might cause problems see +:ref:`faq1_17a`. `MariaDB `_ is also supported +(versions 5.1 and 5.2 were tested). + +.. versionchanged:: 3.5 + Since phpMyAdmin 3.5 `Drizzle `_ is 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. Your chosen extension is specified in +:config:option:`$cfg['Servers'][$i]['extension']`. 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 the error "cannot load MySQL extension, please check PHP Configuration". +--------------------------------------------------------------------------------------- + +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 *mysql.so* or *php\_mysql.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`` and you can change which of then is being used by +:config:option:`$cfg['Servers'][$i]['extension']`. + +.. _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 + +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'; + +.. _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 Does phpMyAdmin support PHP 5? +----------------------------------- + +Yes. + +Since release 4.1 phpMyAdmin supports only PHP 5.3 and newer. For PHP 5.2 you +can use 4.0.x releases. + +.. _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 FollowSymLinks`` 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 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: + +* `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 + +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? +------------------------------------------------------------------------------------------------------------------------------------ + +Be sure that you have enabled ``SSLOptions`` and ``StdEnvVars`` in +your Apache 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://host/~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:: + +.. _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`. + +.. _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. We also got a report +from a user under :term:`IIS`, that used a zipped distribution kit: the file +:file:`libraries/Config.class.php` contained an end-of-line character (hex 0A) +at the end; removing this character cleared his errors. + +.. _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? +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +For RedHat users, Harald Legner suggests this on the mailing list: + +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. + +Here is a fix suggested by Brad Ummer: + +* First, you need to determine what socket is being used by MySQL. To do + this, telnet 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). +* Then, you need to tell PHP to use this socket. To do this in + phpMyAdmin, you need to complete the socket information 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 (i.e. + '0755'). + +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 the value you set for the :config:option:`$cfg['PmaAbsoluteUri']` directive in the phpMyAdmin +configuration file. + +.. _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 +----------------------------- + +Themes are configured with :config:option:`$cfg['ThemePath']`, +:config:option:`$cfg['ThemeManager']` and :config:option:`$cfg['ThemeDefault']`. +Under :config:option:`$cfg['ThemePath']`, 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. + +To create a theme: + +* make a new subdirectory (for example "your\_theme\_name") under :config:option:`$cfg['ThemePath']` (by + default ``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:`info.inc.php` in "your\_theme\_name" to contain your chosen + theme name, that will be visible in user interface +* make a new screenshot of your theme and save it under + "your\_theme\_name/screen.png" + +In theme directory there is file :file:`info.inc.php` which contains theme +verbose name, theme generation and theme version. These versions and +generations are enumerated from 1 and do not have any direct +dependence on phpMyAdmin version. Themes within same generation should +be backwards compatible - theme with version 2 should work in +phpMyAdmin requiring version 1. Themes with different generation are +incompatible. + +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``). + +.. _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 `Hardened-PHP `_, 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. + +.. _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 What is currently not supported in phpMyAdmin about InnoDB? +--------------------------------------------------------------- + +In Relation view, having more than one index column in the foreign key. In Query-by- +example (Query), automatic generation of the query LEFT JOIN from the +foreign table. + +.. _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 primary key nor an unique one, 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 or 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 `Bug report +#1013 `_. + +.. _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 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. + +.. _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. + +.. _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``, ``pma_username``, +``pma_password`` 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. Using +``pma_username`` and ``pma_password`` has been tested along with the +usage of 'cookie' ``auth_type``. + +.. _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'. +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 In Internet Explorer 5.0, I get JavaScript errors when browsing my rows. +---------------------------------------------------------------------------- + +Upgrade to at least Internet Explorer 5.5 SP2. + +.. _faq5_6: + +5.6 In Internet Explorer 5.0, 5.5 or 6.0, I get an error (like "Page not found") when trying to modify a row in a table with many columns, or with a text column. +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Your table neither have a primary key nor an unique one, so we must use a long +:term:`URL` to identify this row. There is a limit on the length of the +:term:`URL` in those browsers, and this not happen in Netscape, for example. +The workaround is to create a primary or unique key, or use another browser. + +.. _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 With Netscape 4.75 I get empty rows between each row of data in a CSV exported file. +----------------------------------------------------------------------------------------- + +This is a known Netscape 4.75 bug: it adds some line feeds when +exporting data in octet-stream mode. Since we can't detect the +specific Netscape version, we cannot workaround this bug. + +.. _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 With Internet Explorer 5.5 or 6, and HTTP authentication type, I cannot manage two servers: I log in to the first one, then the other one, but if I switch back to the first, I have to log in on each operation. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +This is a bug in Internet Explorer, other browsers do not behave this +way. + +.. _faq5_14: + +5.14 Using Opera6, I can manage to get to the authentication, but nothing happens after that, only a blank screen. +------------------------------------------------------------------------------------------------------------------ + +Please upgrade to Opera7 at least. + +.. _faq5_15: + +5.15 I have display problems with Safari. +----------------------------------------- + +Please upgrade to at least version 1.2.3. + +.. _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 With Konqueror 4.2.x an invalid ``LIMIT`` clause is generated when I browse a table. +----------------------------------------------------------------------------------------- + +This happens only when both of these conditions are met: using the +``http`` authentication mode and ``register_globals`` being set to +``On`` on the server. It seems to be a browser-specific problem; +meanwhile use the ``cookie`` authentication mode. + +.. _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. + +.. _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. + +.. _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) + ) TYPE=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) + ) TYPE=MyISAM; + + INSERT INTO REL_persons VALUES (11, 'Marc', 'S', ''); + 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) + ) TYPE=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. + +.. _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 How do I set up the SQL Validator? +--------------------------------------- + +To use SQL Validator, you need PHP with :term:`XML`, :term:`PCRE` and +:term:`PEAR` support. In addition you need a :term:`SOAP` support, either as a +PHP extension or as a PEAR SOAP module. + +To install :term:`PEAR` :term:`SOAP` module, run :command:`pear install +Net_Socket Net_URL HTTP_Request Mail_Mime Net_DIME SOAP` to get the necessary +:term:`PEAR` modules for usage. + +If you use the Validator, you should be aware that any :term:`SQL` statement +you submit will be stored anonymously (database/table/column names, strings, +numbers replaced with generic values). The Mimer :term:`SQL` Validator itself, +is © 2001 Upright Database Technology. We utilize it as free SOAP service. + +.. _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! WTF is this feature then useful for? +----------------------------------------------------------------------------------------- + +Slow down :). 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 is this variable for? +-------------------------------------------------------------------------------------------------------------------------- + +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. + +You can also have, inside the query, a placeholder for a variable. +This is done by inserting into the query a SQL comment between ``/*`` and +``*/``. Inside the comment, the special string ``[VARIABLE]`` is used. +Be aware that the whole query minus the SQL comment must be +valid by itself, otherwise you won't be able to store it as a bookmark. + +When you execute the bookmark, everything typed into the *value* +input box on the query box page will replace the string ``/*[VARIABLE]*/`` in +your stored query. + +Also remember, that everything else inside the ``/*[VARIABLE]*/`` string for +your query will remain the way it is, but will be stripped of the ``/**/`` +chars. So you can use: + +.. code-block:: mysql + + /*, [VARIABLE] AS myname */ + +which will be expanded to + +.. code-block:: mysql + + , VARIABLE as myname + +in your query, where VARIABLE is the string you entered in the input box. If an +empty string is provided, no replacements are made. + +A more complex example. Say you have stored +this query: + +.. code-block:: mysql + + SELECT Name, Address FROM addresses WHERE 1 /* AND Name LIKE '%[VARIABLE]%' */ + +Say, you now enter "phpMyAdmin" as the variable for the stored query, the full +query will be: + +.. code-block:: mysql + + SELECT Name, Address FROM addresses WHERE 1 AND Name LIKE '%phpMyAdmin%' + +You can use multiple occurrences of ``/*[VARIABLE]*/`` in a single query +(that is, multiple occurrences of the *same* variable). + +**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. + +Your initial query which is going to be stored as a bookmark has to yield at +least one result row so you can store the bookmark. You may have that to work +around using well positioned ``/**/`` comments. + +.. _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. + +.. _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 the `wiki `_. + +.. _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 +certail 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. + +.. _faqproject: + +phpMyAdmin project +++++++++++++++++++ + +.. _faq7_1: + +7.1 I have found a bug. How do I inform developers? +--------------------------------------------------- + +Our Bug Tracker is located at under the +Bugs section. But please first discuss your bug with other users: +. + +.. _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. + +.. _faqsynchronization: + +Synchronization ++++++++++++++++ + +.. _faq9_1: + +9.1 (withdrawn). +---------------- + +.. _faq9_2: + +9.2 (withdrawn). +---------------- + diff --git a/sources/doc/glossary.rst b/sources/doc/glossary.rst new file mode 100644 index 0000000..d558b44 --- /dev/null +++ b/sources/doc/glossary.rst @@ -0,0 +1,406 @@ +.. _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:: + + FPDF + the free :term:`PDF` library + + .. 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:: + + 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:: + + 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:: + + 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 + handlers for different table types + + .. seealso:: + + SOAP + Simple Object Access Protocol is a protocol specification for exchanging + structured information in the implementation of Web Services in computer + networks. + + .. 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 + Rewrite of :term:`UFPDF` with various improvements. + + .. seealso:: + + trigger + a procedural code that is automatically executed in response to certain events on a particular table or view in a database + + .. seealso:: + + UFPDF + Unicode/UTF-8 extension for :term:`FPDF` + + .. seealso:: + + 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/sources/doc/html/.buildinfo b/sources/doc/html/.buildinfo new file mode 100644 index 0000000..433f8ae --- /dev/null +++ b/sources/doc/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 63fac88aa6eec411c60843b02aaf0295 +tags: fbb0d17656682115ca4d033fb2f83ba1 diff --git a/sources/doc/html/_sources/config.txt b/sources/doc/html/_sources/config.txt new file mode 100644 index 0000000..c8c04b2 --- /dev/null +++ b/sources/doc/html/_sources/config.txt @@ -0,0 +1,2813 @@ +.. index:: config.inc.php + +.. _config: + +Configuration +============= + +Almost all configurable data is placed in :file:`config.inc.php`. 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`. + +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: ``''`` + + Sets here the complete :term:`URL` (with full path) to your phpMyAdmin + installation's directory. E.g. + ``http://www.example.net/path_to_your_phpMyAdmin_directory/``. Note also + that the :term:`URL` on most of web servers are case–sensitive. 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 will need to set :config:option:`$cfg['PmaAbsoluteUri']` + (`more info `_). + + 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` + +.. 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['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['McryptDisableWarning'] + + :type: boolean + :default: false + + Disable the default warning that is displayed if mcrypt is missing for + cookie authentication. + + You can set this parameter to ``true`` to stop this message from appearing. + +.. config:option:: $cfg['ServerLibraryDifference_DisableWarning'] + + :type: boolean + :default: false + + 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['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 bellow + + 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'`` + * dot - ``'.'``, i.e., use named pipes on windows systems + * empty - ``''``, disables this server + + .. note:: + + phpMyAdmin supports connecting to MySQL servers reachable via IPv6 only. + To connect to an IPv6 MySQL server, enter its IPv6 address in this field. + +.. 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']`. + +.. 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. + +.. config:option:: $cfg['Servers'][$i]['ssl'] + + :type: boolean + :default: false + + Whether to enable SSL for the connection between phpMyAdmin and the MySQL server. + + When using :config:option:`$cfg['Servers'][$i]['extension']` = ``'mysql'``, + none of the remaining ``'ssl...'`` configuration options apply. + + We strongly recommend using :config:option:`$cfg['Servers'][$i]['extension']` = ``'mysqli'`` + when using this option. + +.. config:option:: $cfg['Servers'][$i]['ssl_key'] + + :type: string + :default: NULL + + Path to the key file when using SSL for connecting to the MySQL server. + + For example: + + .. code-block:: php + + $cfg['Servers'][$i]['ssl_key'] = '/etc/mysql/server-key.pem'; + +.. config:option:: $cfg['Servers'][$i]['ssl_cert'] + + :type: string + :default: NULL + + Path to the cert file when using SSL for connecting to the MySQL server. + +.. 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. + +.. config:option:: $cfg['Servers'][$i]['ssl_ca_path'] + + :type: string + :default: NULL + + Directory containing trusted SSL CA certificates in PEM format. + +.. config:option:: $cfg['Servers'][$i]['ssl_ciphers'] + + :type: string + :default: NULL + + List of allowable ciphers for SSL connections to the MySQL server. + +.. config:option:: $cfg['Servers'][$i]['connect_type'] + + :type: string + :default: ``'tcp'`` + + 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]['extension'] + + :type: string + :default: ``'mysqli'`` + + What php MySQL extension to use for the connection. Valid options are: + + ``mysql`` + The classic MySQL extension. + + ``mysqli`` + The improved MySQL extension. This extension became available with PHP + 5.0.0 and is the recommended way to connect to a server running MySQL + 4.1.x or newer. + +.. 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. + +.. _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. + +.. _controluser: +.. config:option:: $cfg['Servers'][$i]['controluser'] + + :type: string + :default: ``''`` + +.. config:option:: $cfg['Servers'][$i]['controlpass'] + + :type: string + :default: ``''`` + + This special account is used for 2 distinct purposes: to make possible all + relational features (see :config:option:`$cfg['Servers'][$i]['pmadb']`) and, + for a MySQL server running with ``--skip-show-database``, to enable a + multi-user installation (:term:`HTTP` or cookie + authentication mode). + + When using :term:`HTTP` or + cookie authentication modes (or 'config' authentication mode since phpMyAdmin + 2.2.1), you need to supply the details of a MySQL account that has ``SELECT`` + privilege on the *mysql.user (all columns except "Password")*, *mysql.db (all + columns)* and *mysql.tables\_priv (all columns except "Grantor" and + "Timestamp")* tables. This account is used to check what databases the user + will see at login. + + .. versionchanged:: 2.2.5 + those were called ``stduser`` and ``stdpass`` + + .. seealso:: :ref:`setup`, :ref:`authentication_modes` + +.. 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'``) as + introduced in 2.2.3 allows you to log in as any valid MySQL user with + the help of cookies. Username and password are stored in cookies + during the session and password is deleted when it ends. This can also + allow you to log in in arbitrary server if :config:option:`$cfg['AllowArbitraryServer']` enabled. + * 'http' authentication (was + called 'advanced' in previous versions and can be written also as + 'http') (``$auth_type = 'http';'``) as introduced in 1.3.0 allows you to log in as any + valid MySQL user via HTTP-Auth. + * 'signon' authentication mode (``$auth_type = 'signon'``) as + introduced in 2.10.0 allows you to log in from prepared PHP session + data or using supplied PHP script. This is useful for implementing + single signon from another application. Sample way how to seed session + is in signon example: :file:`examples/signon.php`. There is also + alternative example using OpenID - :file:`examples/openid.php` and example + for scripts based solution - :file:`examples/signon-script.php`. You need + to configure :config:option:`$cfg['Servers'][$i]['SignonSession']` or + :config:option:`$cfg['Servers'][$i]['SignonScript']` and + :config:option:`$cfg['Servers'][$i]['SignonURL']` to use this authentication + method. + + .. 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_auth_swekey_config: +.. config:option:: $cfg['Servers'][$i]['auth_swekey_config'] + + :type: string + :default: ``''`` + + The name of the file containing :ref:`swekey` ids and login names for hardware + authentication. Leave empty to deactivate this feature. + +.. _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 + + 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]['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 + :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']` + + +.. _relation: +.. config:option:: $cfg['Servers'][$i]['relation'] + + :type: string + :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. + + .. 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 + :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. + + .. seealso:: :ref:`faqdisplay` + +.. _table_coords: +.. config:option:: $cfg['Servers'][$i]['table_coords'] + + :type: string + :default: ``''`` + +.. config:option:: $cfg['Servers'][$i]['pdf_pages'] + + :type: string + :default: ``''`` + + Since release 2.3.0 you can have phpMyAdmin create :term:`PDF` pages + showing the relations between your tables. 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']` + + .. seealso:: :ref:`faqpdf`. + +.. _col_com: +.. config:option:: $cfg['Servers'][$i]['column_info'] + + :type: string + :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'. + + + 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 + :config:option:`$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; + +.. _history: +.. config:option:: $cfg['Servers'][$i]['history'] + + :type: string + :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``) + +.. _recent: +.. config:option:: $cfg['Servers'][$i]['recent'] + + :type: string + :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``) + +.. _table_uiprefs: +.. config:option:: $cfg['Servers'][$i]['table_uiprefs'] + + :type: string + :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``) + +.. _configurablemenus: +.. config:option:: $cfg['Servers'][$i]['users'] + + :type: string + :default: ``''`` + +.. config:option:: $cfg['Servers'][$i]['usergroups'] + + :type: string + :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``) + +.. _navigationhiding: +.. config:option:: $cfg['Servers'][$i]['navigationhiding'] + + :type: string + :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``) + +.. _tracking: +.. config:option:: $cfg['Servers'][$i]['tracking'] + + :type: string + :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``) + + +.. _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 + :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']` + + + +.. _designer_coords: +.. config:option:: $cfg['Servers'][$i]['designer_coords'] + + :type: string + :default: ``''`` + + Since release 2.10.0 a Designer interface is available; it permits to + visually manage the relations. + + 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\_coords']` + (e.g. ``pma__designer_coords``) + + + +.. 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]['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) + +.. config:option:: $cfg['Servers'][$i]['SignonScript'] + + :type: string + :default: ``''`` + + Name of PHP script to be sourced and executed to obtain login + credentials. This is alternative approach to session based single + signon. The script needs to provide function + ``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. + +.. 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. + +.. 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. + +.. 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. + +.. config:option:: $cfg['Servers'][$i]['StatusCacheDatabases'] + + :type: array of strings + :default: array() + + Enables caching of ``TABLE STATUS`` outputs for specific databases on + this server (in some cases ``TABLE STATUS`` can be very slow, so you + may want to cache it). APC is used (if the PHP extension is available, + if not, this setting is ignored silently). You have to provide + :config:option:`$cfg['Servers'][$i]['StatusCacheLifetime']`. + +.. config:option:: $cfg['Servers'][$i]['StatusCacheLifetime'] + + :type: integer + :default: 0 + + Lifetime in seconds of the ``TABLE STATUS`` cache if + :config:option:`$cfg['Servers'][$i]['StatusCacheDatabases']` is used. + +Generic settings +---------------- + +.. 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 main phpMyAdmin + page. + + .. 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 + intenet 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). This limit is also enforced in the + navigation panel when in Light mode. + +.. 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 + + Whether to force using https while accessing phpMyAdmin. + + .. 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 + `_). + +.. 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. + + This setting is used while importing/exporting dump files and at some other + places in phpMyAdmin 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['AllowUserDropDatabase'] + + :type: boolean + :default: false + + 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. + + .. note:: + + 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 blowfish 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 + blowfish algorithm: you won’t be prompted for this passphrase. There + is no maximum length for this secret. + + .. 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. + +.. config:option:: $cfg['CaptchaLoginPublicKey'] + + :type: string + :default: ``''`` + + The public key for the reCaptcha service that can be obtain from + http://www.google.com/recaptcha. + + 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 + http://www.google.com/recaptcha. + + reCaptcha will be then used in :ref:`cookie`. + +Navigation panel setup +---------------------- + +.. config:option:: $cfg['MaxNavigationItems'] + + :type: integer + :default: 250 + + The number of items 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 or array + :default: ``'_'`` + + The string used to separate the parts of the database name when + showing them in a tree. Alternatively you can specify more strings in + an array and all of them will be used as a separator. + +.. 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['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. + +.. 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: ``'tbl_structure.php'`` + + Defines the tab displayed by default when clicking the small icon next + to each table name in the navigation panel. Possible values: + + * ``tbl_structure.php`` + * ``tbl_sql.php`` + * ``tbl_select.php`` + * ``tbl_change.php`` + * ``sql.php`` + +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 + +.. config:option:: $cfg['ShowChgPassword'] + + :type: boolean + :default: true + +.. config:option:: $cfg['ShowCreateDb'] + + :type: boolean + :default: true + + Defines whether to display the :guilabel:`PHP information` and + :guilabel:`Change password` links and form for creating database or not at + the starting main (right) frame. This setting does not check MySQL commands + entered directly. + + 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() + + Also 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. + +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. + +Browse mode +----------- + +.. config:option:: $cfg['TableNavigationLinksMode'] + + :type: string + :default: ``'icons'`` + + Defines whether the table navigation links contain ``'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. + +.. config:option:: $cfg['DisplayBinaryAsHex'] + + :type: boolean + :default: true + + Defines whether the "Show binary contents as HEX" browse option is + ticked by default. + +.. config:option:: $cfg['GridEditing'] + + :type: string + :default: ``'double-click'`` + + Defines which action (``double-click`` or ``click``) triggers grid + editing. Can be deactived with the ``disabled`` value. + +.. 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 maximum number of concurrent entries for the Insert page. + +.. 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']['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['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. + + +Tabs display settings +--------------------- + +.. config:option:: $cfg['TabsMode'] + + :type: string + :default: ``'both'`` + + Defines whether the menu tabs 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['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: ``'index.php'`` + + Defines the tab displayed by default on server view. Possible values: + + * ``main.php`` (recommended for multi-user setups) + * ``server_databases.php``, + * ``server_status.php`` + * ``server_variables.php`` + * ``server_privileges.php`` + +.. config:option:: $cfg['DefaultTabDatabase'] + + :type: string + :default: ``'db_structure.php'`` + + Defines the tab displayed by default on database view. Possible + values: + + * ``db_structure.php`` + * ``db_sql.php`` + * ``db_search.php``. + +.. config:option:: $cfg['DefaultTabTable'] + + :type: string + :default: ``'sql.php'`` + + Defines the tab displayed by default on table view. Possible values: + + * ``tbl_structure.php`` + * ``tbl_sql.php`` + * ``tbl_select.php`` + * ``tbl_change.php`` + * ``sql.php`` + +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: ``'utf8_general_ci'`` + + Defines the default connection collation to use, if not user-defined. + See the `MySQL documentation `_ for list of possible values. This setting is + ignored when connected to Drizzle server. + +.. 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 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 +-------------- + +.. config:option:: $cfg['NaviWidth'] + + :type: integer + :default: + + Navigation panel width in pixels. See + :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['NaviBackground'] + + :type: string [CSS color for background] + :default: + +.. config:option:: $cfg['MainBackground'] + + :type: string [CSS color for background] + :default: + + The background styles used for both the frames. See + :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['NaviPointerBackground'] + + :type: string [CSS color for background] + :default: + +.. config:option:: $cfg['NaviPointerColor'] + + :type: string [CSS color] + :default: + + The style used for the pointer in the navigation panel. See + :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['Border'] + + :type: integer + :default: + + The size of a table's border. See :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['ThBackground'] + + :type: string [CSS color for background] + :default: + +.. config:option:: $cfg['ThColor'] + + :type: string [CSS color] + :default: + + The style used for table headers. See + :file:`themes/themename/layout.inc.php`. + +.. _cfg_BgcolorOne: +.. config:option:: $cfg['BgOne'] + + :type: string [CSS color] + :default: + + The color (HTML) #1 for table rows. See + :file:`themes/themename/layout.inc.php`. + +.. _cfg_BgcolorTwo: +.. config:option:: $cfg['BgTwo'] + + :type: string [CSS color] + :default: + + The color (HTML) #2 for table rows. See + :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['BrowsePointerBackground'] + + :type: string [CSS color] + :default: + +.. config:option:: $cfg['BrowsePointerColor'] + + :type: string [CSS color] + :default: + +.. config:option:: $cfg['BrowseMarkerBackground'] + + :type: string [CSS color] + :default: + +.. config:option:: $cfg['BrowseMarkerColor'] + + :type: string [CSS color] + :default: + + The colors (HTML) uses for the pointer and the marker in browse mode. + The former feature highlights the row over which your mouse is passing + and the latter lets you visually mark/unmark rows by clicking on the + corresponding checkbox. Highlighting / marking a column is done by + hovering over / clicking the column's header (outside of the text). + See :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['FontFamily'] + + :type: string + :default: + + You put here a valid CSS font family value, for example ``arial, sans- + serif``. See :file:`themes/themename/layout.inc.php`. + +.. config:option:: $cfg['FontFamilyFixed'] + + :type: string + :default: + + You put here a valid CSS font family value, for example ``monospace``. + This one is used in textarea. See :file:`themes/themename/layout.inc.php`. + +Design customization +-------------------- + +.. config:option:: $cfg['NavigationTreePointerEnable'] + + :type: boolean + :default: true + + A value of ``true`` activates the navi pointer. + +.. config:option:: $cfg['BrowsePointerEnable'] + + :type: boolean + :default: true + + Whether to activate the browse pointer or not. + +.. config:option:: $cfg['BrowseMarkerEnable'] + + :type: boolean + :default: true + + Whether to activate the browse marker or not. + +.. 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). "left" and "right" + are parsed as "top" and "bottom" with vertical display mode. + +.. config:option:: $cfg['DefaultDisplay'] + + :type: string + :default: ``'horizonta'`` + + There are 3 display modes: horizontal, horizontalflipped and vertical. + Define which one is displayed by default. The first mode displays each + row on a horizontal line, the second rotates the headers by 90 + degrees, so you can use descriptive headers even though columns only + contain small values and still print them out. The vertical mode sorts + each row on a vertical lineup. + +.. config:option:: $cfg['RememberSorting'] + + :type: boolean + :default: true + + If enabled, remember the sorting of each table when browsing them. + +.. config:option:: $cfg['HeaderFlipType'] + + :type: string + :default: ``'auto'`` + + The HeaderFlipType can be set to 'auto', 'css' or 'fake'. When using + 'css' the rotation of the header for horizontalflipped is done via + CSS. The CSS transformation currently works only in Internet + Explorer.If set to 'fake' PHP does the transformation for you, but of + course this does not look as good as CSS. The 'auto' option enables + CSS transformation when browser supports it and use PHP based one + otherwise. + +.. 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. 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. + + +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']['Validate'] + + :type: boolean + :default: false + + Whether to display a link to validate a query in any SQL Query box. + + .. seealso:: :config:option:`$cfg['SQLValidator']` + +.. config:option:: $cfg['SQLQuery']['Refresh'] + + :type: boolean + :default: true + + Whether to display a link to refresh a query in any SQL Query box. + +Web server upload/save/import directories +----------------------------------------- + +.. 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. + + .. note:: + + If PHP is running in safe mode, this directory must be owned by the same + user as the owner of the phpMyAdmin scripts. See also :ref:`faq1_16` for + alternatives. + +.. config:option:: $cfg['SaveDir'] + + :type: string + :default: ``''`` + + The name of the directory where dumps can be saved. + + If you want different directory for each user, %u will be replaced with + username. + + Please note that the directory must exist and has to be writable for + the user running webserver. + + .. note:: + + If PHP is running in safe mode, this directory must be owned by the same + user as the owner of the phpMyAdmin scripts. + +.. config:option:: $cfg['TempDir'] + + :type: string + :default: ``''`` + + The name of the directory where temporary files can be stored. + + This is needed for importing ESRI Shapefiles, see :ref:`faq6_30` and to + work around limitations of ``open_basedir`` for uploaded files, see + :ref:`faq1_11`. + + 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 web server. + However for security reasons, this directory should be outside the + tree published by webserver. If you cannot avoid having this directory + published by webserver, place at least an empty :file:`index.html` file + there, so that directory listing is not possible. + + 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. + +Various display setting +----------------------- + +.. config:option:: $cfg['ShowDisplayDirection'] + + :type: boolean + :default: false + + Defines whether or not type display direction option is shown when + browsing a table. + +.. config:option:: $cfg['RepeatCells'] + + :type: integer + :default: 100 + + Repeat the headers every X cells, or 0 to deactivate. + +.. config:option:: $cfg['EditInWindow'] + + :type: boolean + :default: true + +.. config:option:: $cfg['QueryWindowWidth'] + + :type: integer + :default: 550 + +.. config:option:: $cfg['QueryWindowHeight'] + + :type: integer + :default: 310 + +.. config:option:: $cfg['QueryHistoryDB'] + + :type: boolean + :default: false + +.. config:option:: $cfg['QueryWindowDefTab'] + + :type: string + :default: ``'sql'`` + +.. config:option:: $cfg['QueryHistoryMax'] + + :type: integer + :default: 25 + + All those variables affect the query window feature. A :term:`SQL` link or + icon is always displayed in the navigation panel. If JavaScript is enabled + in your browser, a click on this opens a distinct query window, which is a + direct interface to enter :term:`SQL` queries. Otherwise, the right panel + changes to display a query box. + + The size of this query window can be customized with + :config:option:`$cfg['QueryWindowWidth']` and + :config:option:`$cfg['QueryWindowHeight']` - both integers for the size in + pixels. Note that normally, those parameters will be modified in + :file:`layout.inc.php`` for the theme you are using. + + If :config:option:`$cfg['EditInWindow']` is set to true, a click on [Edit] + from the results page (in the :guilabel:`Showing Rows` section) opens the + query window and puts the current query inside it. If set to false, + clicking on the link puts the :term:`SQL` query + in the right panel's query box. + + 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']`. + + The query window also has a custom tabbed look to group the features. + Using the variable :config:option:`$cfg['QueryWindowDefTab']` you can + specify the default tab to be used when opening the query window. It can be + set to either ``sql``, ``files``, ``history`` or ``full``. + +.. config:option:: $cfg['BrowseMIME'] + + :type: boolean + :default: true + + Enable :ref:`transformations`. + +.. config:option:: $cfg['MaxExactCount'] + + :type: integer + :default: 0 + + 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. + +.. 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 :file:`libraries/config/user_preferences.forms.php`. + +.. 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['ThemePath'] + + :type: string + :default: ``'./themes'`` + + If theme manager is active, use this as the path of the subdirectory + containing all the themes. + +.. 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 :config:option:`$cfg['ThemePath']`). + +.. config:option:: $cfg['ThemePerServer'] + + :type: boolean + :default: false + + Whether to allow different theme for each server. + +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`. + +SQL validator settings +---------------------- + +.. config:option:: $cfg['SQLValidator'] + + :type: array + :default: array(...) + + + +.. config:option:: $cfg['SQLValidator']['use'] + + :type: boolean + :default: false + + phpMyAdmin now supports use of the `Mimer SQL Validator + `_ service, as originally + published on `Slashdot + `_. For + help in setting up your system to use the service, see the + :ref:`faqsqlvalidator`. + +.. config:option:: $cfg['SQLValidator']['username'] + + :type: string + :default: ``''`` + +.. config:option:: $cfg['SQLValidator']['password'] + + :type: string + :default: ``''`` + + The SOAP service allows you to log in with ``anonymous`` and any password, + so we use those by default. Instead, if you have an account with them, you + can put your login details here, and it will be used in place of the + anonymous login. + +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. + + +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 bottom of main page (right frame). + +.. config:option:: $cfg['DBG']['demo'] + + :type: boolean + :default: false + + Enable to let server present itself as demo server. + This is used for . + +.. config:option:: $cfg['Error_Handler']['display'] + + :type: boolean + :default: false + + Whether to display errors from PHP or not. + diff --git a/sources/doc/html/_sources/copyright.txt b/sources/doc/html/_sources/copyright.txt new file mode 100644 index 0000000..cfcb863 --- /dev/null +++ b/sources/doc/html/_sources/copyright.txt @@ -0,0 +1,30 @@ +.. _copyright: + +Copyright +========= + +.. code-block:: none + + Copyright (C) 1998-2000 Tobias Ratschiller + Copyright (C) 2001-2013 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 `http://www.gnu.org/licenses/ +`_. diff --git a/sources/doc/html/_sources/credits.txt b/sources/doc/html/_sources/credits.txt new file mode 100644 index 0000000..61c7758 --- /dev/null +++ b/sources/doc/html/_sources/credits.txt @@ -0,0 +1,626 @@ +.. _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 + + * :term:`SQL` analyser (most of it) + + * current project maintainer + + * maintains content on our website + +* 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" + (http://sf.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 (see ) 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 (`http://www.tcpdf.org `_) + +* 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 + + +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: + +* Arabic + + * Abdullah Al-Saedi + +* Bulgarian + + * stoyanster + +* Catalan + + * Xavier Navarro + +* Czech + + * Michal Čihař + +* Danish + + * opensource + * Jørgen Thomsen + +* German + + * mrbendig + * torsten.funck + * Sven Strickroth + * typo3 + * Jo Michael + +* Greek + + * Panagiotis Papazoglou + +* English (United Kingdom) + + * Robert Readman + +* Spanish + + * Matías Bellone + +* French + + * Marc Delisle + +* Hindi + + * u4663530 + * rsedwardian + +* Hungarian + + * gergo314 + +* Italian + + * Rouslan Placella + +* Japanese + + * Yuichiro + +* Lithuanian + + * Kęstutis + +* Norwegian Bokmål + + * Sven-Erik Andersen + +* Dutch + + * Dieter Adriaenssens + * Herman van Rink + +* Polish + + * Stanisław Krukowski + * Marcin Kozioł + +* Portuguese + + * JoaoTMDias + +* Portuguese (Brazil) + + * wiltave + * emerson4br + +* Romanian + + * alexukf + +* Russian + + * Victor Volkov + +* Sinhala + + * Madhura Jayaratne + +* Slovak + + * Martin Lacina + +* Slovenian + + * Domen + +* Swedish + + * stefan + +* Tamil + + * ysajeepan + +* Telugu + + * veeven + +* Thai + + * kanitchet + +* Turkish + + * Burak Yavuz + +* Uighur + + * gheni + +* Ukrainian + + * typim + * oleg-ilnytskyi + +* Urdu + + * Mehbooob Khan + +* Simplified Chinese + + * shanyan baishui + +* Traditional Chinese + + * star + +Documentation translators ++++++++++++++++++++++++++ + +Following people have contributed to translation of phpMyAdmin documentation: + +* Czech + + * Michal Čihař + +* Greek + + * Panagiotis Papazoglou + +* English (United Kingdom) + + * Robert Readman + +* French + + * Cédric Corazza + +* Japanese + + * Yuichiro Takahashi + +* Polish + + * Stanisław Krukowski + +* Portuguese (Brazil) + + * mjaning + +* Slovenian + + * Domen + +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/sources/doc/html/_sources/developers.txt b/sources/doc/html/_sources/developers.txt new file mode 100644 index 0000000..5574527 --- /dev/null +++ b/sources/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 +`_. \ No newline at end of file diff --git a/sources/doc/html/_sources/faq.txt b/sources/doc/html/_sources/faq.txt new file mode 100644 index 0000000..1fa3768 --- /dev/null +++ b/sources/doc/html/_sources/faq.txt @@ -0,0 +1,2015 @@ +.. _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 or Bzip a dump or a CSV export? It does not seem to work. +---------------------------------------------------------------------------- + +These features are based on the ``gzencode()`` and ``bzcompress()`` +PHP functions to be more independent of the platform (Unix/Windows, +Safe Mode or not, and so on). So, you must have Zlib/Bzip2 support +(``--with-zlib`` and ``--with-bz2``). + +.. _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 query box. +------------------------------------------------------------------------------------ + +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? +------------------------------------------------------- + +The MySQL manual explains how to `reset the permissions +`_. + +.. _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 ``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. One user also said that ``post_max_size`` and +``memory_limit`` need 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 MySQL versions does phpMyAdmin support? +-------------------------------------------------- + +Since phpMyAdmin 3.0.x, only MySQL 5.0.1 and newer are supported. For +older MySQL versions, you need to use the latest 2.x branch. +phpMyAdmin can connect to your MySQL server using PHP's classic `MySQL +extension `_ as well as the `improved MySQL +extension (MySQLi) `_ that is available in PHP +5.0. The latter one should be used unless you have a good reason not +to do so. When compiling PHP, we strongly recommend that you manually +link the MySQL extension of your choice to a MySQL client library of +at least the same minor version since the one that is bundled with +some PHP distributions is rather old and might cause problems see +:ref:`faq1_17a`. `MariaDB `_ is also supported +(versions 5.1 and 5.2 were tested). + +.. versionchanged:: 3.5 + Since phpMyAdmin 3.5 `Drizzle `_ is 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. Your chosen extension is specified in +:config:option:`$cfg['Servers'][$i]['extension']`. 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 the error "cannot load MySQL extension, please check PHP Configuration". +--------------------------------------------------------------------------------------- + +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 *mysql.so* or *php\_mysql.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`` and you can change which of then is being used by +:config:option:`$cfg['Servers'][$i]['extension']`. + +.. _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 + +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'; + +.. _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 Does phpMyAdmin support PHP 5? +----------------------------------- + +Yes. + +Since release 4.1 phpMyAdmin supports only PHP 5.3 and newer. For PHP 5.2 you +can use 4.0.x releases. + +.. _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 FollowSymLinks`` 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 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: + +* `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 + +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? +------------------------------------------------------------------------------------------------------------------------------------ + +Be sure that you have enabled ``SSLOptions`` and ``StdEnvVars`` in +your Apache 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://host/~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:: + +.. _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`. + +.. _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. We also got a report +from a user under :term:`IIS`, that used a zipped distribution kit: the file +:file:`libraries/Config.class.php` contained an end-of-line character (hex 0A) +at the end; removing this character cleared his errors. + +.. _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? +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +For RedHat users, Harald Legner suggests this on the mailing list: + +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. + +Here is a fix suggested by Brad Ummer: + +* First, you need to determine what socket is being used by MySQL. To do + this, telnet 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). +* Then, you need to tell PHP to use this socket. To do this in + phpMyAdmin, you need to complete the socket information 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 (i.e. + '0755'). + +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 the value you set for the :config:option:`$cfg['PmaAbsoluteUri']` directive in the phpMyAdmin +configuration file. + +.. _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 +----------------------------- + +Themes are configured with :config:option:`$cfg['ThemePath']`, +:config:option:`$cfg['ThemeManager']` and :config:option:`$cfg['ThemeDefault']`. +Under :config:option:`$cfg['ThemePath']`, 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. + +To create a theme: + +* make a new subdirectory (for example "your\_theme\_name") under :config:option:`$cfg['ThemePath']` (by + default ``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:`info.inc.php` in "your\_theme\_name" to contain your chosen + theme name, that will be visible in user interface +* make a new screenshot of your theme and save it under + "your\_theme\_name/screen.png" + +In theme directory there is file :file:`info.inc.php` which contains theme +verbose name, theme generation and theme version. These versions and +generations are enumerated from 1 and do not have any direct +dependence on phpMyAdmin version. Themes within same generation should +be backwards compatible - theme with version 2 should work in +phpMyAdmin requiring version 1. Themes with different generation are +incompatible. + +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``). + +.. _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 `Hardened-PHP `_, 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. + +.. _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 What is currently not supported in phpMyAdmin about InnoDB? +--------------------------------------------------------------- + +In Relation view, having more than one index column in the foreign key. In Query-by- +example (Query), automatic generation of the query LEFT JOIN from the +foreign table. + +.. _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 primary key nor an unique one, 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 or 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 `Bug report +#1013 `_. + +.. _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 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. + +.. _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. + +.. _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``, ``pma_username``, +``pma_password`` 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. Using +``pma_username`` and ``pma_password`` has been tested along with the +usage of 'cookie' ``auth_type``. + +.. _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'. +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 In Internet Explorer 5.0, I get JavaScript errors when browsing my rows. +---------------------------------------------------------------------------- + +Upgrade to at least Internet Explorer 5.5 SP2. + +.. _faq5_6: + +5.6 In Internet Explorer 5.0, 5.5 or 6.0, I get an error (like "Page not found") when trying to modify a row in a table with many columns, or with a text column. +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Your table neither have a primary key nor an unique one, so we must use a long +:term:`URL` to identify this row. There is a limit on the length of the +:term:`URL` in those browsers, and this not happen in Netscape, for example. +The workaround is to create a primary or unique key, or use another browser. + +.. _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 With Netscape 4.75 I get empty rows between each row of data in a CSV exported file. +----------------------------------------------------------------------------------------- + +This is a known Netscape 4.75 bug: it adds some line feeds when +exporting data in octet-stream mode. Since we can't detect the +specific Netscape version, we cannot workaround this bug. + +.. _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 With Internet Explorer 5.5 or 6, and HTTP authentication type, I cannot manage two servers: I log in to the first one, then the other one, but if I switch back to the first, I have to log in on each operation. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +This is a bug in Internet Explorer, other browsers do not behave this +way. + +.. _faq5_14: + +5.14 Using Opera6, I can manage to get to the authentication, but nothing happens after that, only a blank screen. +------------------------------------------------------------------------------------------------------------------ + +Please upgrade to Opera7 at least. + +.. _faq5_15: + +5.15 I have display problems with Safari. +----------------------------------------- + +Please upgrade to at least version 1.2.3. + +.. _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 With Konqueror 4.2.x an invalid ``LIMIT`` clause is generated when I browse a table. +----------------------------------------------------------------------------------------- + +This happens only when both of these conditions are met: using the +``http`` authentication mode and ``register_globals`` being set to +``On`` on the server. It seems to be a browser-specific problem; +meanwhile use the ``cookie`` authentication mode. + +.. _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. + +.. _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. + +.. _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) + ) TYPE=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) + ) TYPE=MyISAM; + + INSERT INTO REL_persons VALUES (11, 'Marc', 'S', ''); + 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) + ) TYPE=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. + +.. _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 How do I set up the SQL Validator? +--------------------------------------- + +To use SQL Validator, you need PHP with :term:`XML`, :term:`PCRE` and +:term:`PEAR` support. In addition you need a :term:`SOAP` support, either as a +PHP extension or as a PEAR SOAP module. + +To install :term:`PEAR` :term:`SOAP` module, run :command:`pear install +Net_Socket Net_URL HTTP_Request Mail_Mime Net_DIME SOAP` to get the necessary +:term:`PEAR` modules for usage. + +If you use the Validator, you should be aware that any :term:`SQL` statement +you submit will be stored anonymously (database/table/column names, strings, +numbers replaced with generic values). The Mimer :term:`SQL` Validator itself, +is © 2001 Upright Database Technology. We utilize it as free SOAP service. + +.. _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! WTF is this feature then useful for? +----------------------------------------------------------------------------------------- + +Slow down :). 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 is this variable for? +-------------------------------------------------------------------------------------------------------------------------- + +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. + +You can also have, inside the query, a placeholder for a variable. +This is done by inserting into the query a SQL comment between ``/*`` and +``*/``. Inside the comment, the special string ``[VARIABLE]`` is used. +Be aware that the whole query minus the SQL comment must be +valid by itself, otherwise you won't be able to store it as a bookmark. + +When you execute the bookmark, everything typed into the *value* +input box on the query box page will replace the string ``/*[VARIABLE]*/`` in +your stored query. + +Also remember, that everything else inside the ``/*[VARIABLE]*/`` string for +your query will remain the way it is, but will be stripped of the ``/**/`` +chars. So you can use: + +.. code-block:: mysql + + /*, [VARIABLE] AS myname */ + +which will be expanded to + +.. code-block:: mysql + + , VARIABLE as myname + +in your query, where VARIABLE is the string you entered in the input box. If an +empty string is provided, no replacements are made. + +A more complex example. Say you have stored +this query: + +.. code-block:: mysql + + SELECT Name, Address FROM addresses WHERE 1 /* AND Name LIKE '%[VARIABLE]%' */ + +Say, you now enter "phpMyAdmin" as the variable for the stored query, the full +query will be: + +.. code-block:: mysql + + SELECT Name, Address FROM addresses WHERE 1 AND Name LIKE '%phpMyAdmin%' + +You can use multiple occurrences of ``/*[VARIABLE]*/`` in a single query +(that is, multiple occurrences of the *same* variable). + +**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. + +Your initial query which is going to be stored as a bookmark has to yield at +least one result row so you can store the bookmark. You may have that to work +around using well positioned ``/**/`` comments. + +.. _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. + +.. _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 the `wiki `_. + +.. _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 +certail 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. + +.. _faqproject: + +phpMyAdmin project +++++++++++++++++++ + +.. _faq7_1: + +7.1 I have found a bug. How do I inform developers? +--------------------------------------------------- + +Our Bug Tracker is located at under the +Bugs section. But please first discuss your bug with other users: +. + +.. _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. + +.. _faqsynchronization: + +Synchronization ++++++++++++++++ + +.. _faq9_1: + +9.1 (withdrawn). +---------------- + +.. _faq9_2: + +9.2 (withdrawn). +---------------- + diff --git a/sources/doc/html/_sources/glossary.txt b/sources/doc/html/_sources/glossary.txt new file mode 100644 index 0000000..d558b44 --- /dev/null +++ b/sources/doc/html/_sources/glossary.txt @@ -0,0 +1,406 @@ +.. _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:: + + FPDF + the free :term:`PDF` library + + .. 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:: + + 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:: + + 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:: + + 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 + handlers for different table types + + .. seealso:: + + SOAP + Simple Object Access Protocol is a protocol specification for exchanging + structured information in the implementation of Web Services in computer + networks. + + .. 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 + Rewrite of :term:`UFPDF` with various improvements. + + .. seealso:: + + trigger + a procedural code that is automatically executed in response to certain events on a particular table or view in a database + + .. seealso:: + + UFPDF + Unicode/UTF-8 extension for :term:`FPDF` + + .. seealso:: + + 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/sources/doc/html/_sources/index.txt b/sources/doc/html/_sources/index.txt new file mode 100644 index 0000000..917ddf8 --- /dev/null +++ b/sources/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 + vendors + copyright + credits + glossary + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` +* :ref:`glossary` diff --git a/sources/doc/html/_sources/intro.txt b/sources/doc/html/_sources/intro.txt new file mode 100644 index 0000000..9d81e44 --- /dev/null +++ b/sources/doc/html/_sources/intro.txt @@ -0,0 +1,68 @@ +.. _intro: + +Introduction +============ + +phpMyAdmin can manage a whole MySQL server (needs a super-user) as +well as a single database. To accomplish the latter you'll need a +properly set up MySQL user who can read/write only the desired +database. It's up to you to look up the appropriate part in the MySQL +manual. + + +Supported features +------------------ + +Currently phpMyAdmin can: + +* browse 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 +* manage MySQL users 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 see :ref:`faq3_6` +* 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 `62 different languages + `_ + + +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` :term:`RFC 1952` or + :term:`Bzip2` formats) dumps and :term:`CSV` exports if you use PHP with + :term:`Zlib` support (``--with-zlib``) and/or :term:`Bzip2` support + (``--with-bz2``). Proper support may also need changes in :file:`php.ini`. diff --git a/sources/doc/html/_sources/other.txt b/sources/doc/html/_sources/other.txt new file mode 100644 index 0000000..aa95451 --- /dev/null +++ b/sources/doc/html/_sources/other.txt @@ -0,0 +1,18 @@ +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: http://www.phpmyadmin.net/home_page/docs.php?books + +Tutorials +--------- + +Third party tutorials and articles are listed on our `wiki page`_. + +.. _wiki page: http://wiki.phpmyadmin.net/pma/Articles diff --git a/sources/doc/html/_sources/privileges.txt b/sources/doc/html/_sources/privileges.txt new file mode 100644 index 0000000..0c12932 --- /dev/null +++ b/sources/doc/html/_sources/privileges.txt @@ -0,0 +1,50 @@ +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:`Users` link on the home page) not from within the :guilabel:`Users` +link under the table. 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. diff --git a/sources/doc/html/_sources/require.txt b/sources/doc/html/_sources/require.txt new file mode 100644 index 0000000..4582d72 --- /dev/null +++ b/sources/doc/html/_sources/require.txt @@ -0,0 +1,53 @@ +.. _require: + +Requirements +============ + +Web server +---------- + +Since, phpMyAdmin's interface is based entirely in your browser, you'll need a +web server (such as Apache, :term:`IIS`) to install phpMyAdmin's files into. + +PHP +--- + +* You need PHP 5.3.0 or newer, with ``session`` support, the Standard PHP Library + (SPL) extension, JSON support, and the ``mbstring`` extension. + +* 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 `mcrypt + `_ extension is strongly suggested for most + users and is **required** for 64–bit machines. Not using mcrypt will + cause phpMyAdmin to load pages significantly slower. + +* To support upload progress bars, see :ref:`faq2_9`. + +* To support XML and Open Document Spreadsheet importing, you need the + `libxml `_ extension. + +* Performance suggestion: install the ``ctype`` extension. + +.. seealso:: :ref:`faq1_31`, :ref:`authentication_modes` + +Database +-------- + +phpMyAdmin supports MySQL-compatible databases. + +* MySQL 5.5 or newer +* MariaDB 5.5 or newer +* Drizzle + +.. seealso:: :ref:`faq1_17` + +Web browser +----------- + +To access phpMyAdmin you need a web browser with cookies and javascript +enabled. + diff --git a/sources/doc/html/_sources/setup.txt b/sources/doc/html/_sources/setup.txt new file mode 100644 index 0000000..783cffd --- /dev/null +++ b/sources/doc/html/_sources/setup.txt @@ -0,0 +1,493 @@ +.. _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 +------ + +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. + +OpenSUSE +-------- + +OpenSUSE already comes with phpMyAdmin package, just install packages from +the `openSUSE Build Service `_. + +Ubuntu +------ + +Ubuntu ships phpMyAdmin package, however if you want to use recent version, you +can use packages from +`PPA for Michal Čihař `_. + +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 `_. + + +.. _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``. +#. 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 +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 need 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 + + + + +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 the `Setup +Script `_. First you must manually create a folder ``config`` +in the phpMyAdmin directory. This is a security measure. On a +Linux/Unix system you can use the following commands: + +.. code-block:: sh + + + cd phpMyAdmin + mkdir config # create directory for saving + chmod o+rw config # give it world writable permissions + +And to edit an existing configuration, copy it over first: + +.. code-block:: sh + + + cp config.inc.php config/ # copy current configuration for editing + chmod o+w config/config.inc.php # give it world writable permissions + +On other platforms, simply create the folder and ensure that your web +server has read and write access to it. :ref:`faq1_26` can help with +this. + +Next, open ``setup/`` in your browser. If you have an existing configuration, +use the ``Load`` button to bring its content inside the setup panel. +Note that **changes are not saved to disk until explicitly choose ``Save``** +from the *Configuration* area of the screen. Normally the script saves the new +:file:`config.inc.php` to the ``config/`` directory, but if the webserver does +not have the proper permissions you may see the error "Cannot load or +save configuration." Ensure that the ``config/`` directory exists and +has the proper permissions - or use the ``Download`` link to save the +config file locally and upload it (via FTP or some similar means) to the +proper location. + +Once the file has been saved, it must be moved out of the ``config/`` +directory and the permissions must be reset, again as a security +measure: + +.. code-block:: sh + + + mv config/config.inc.php . # move file to current directory + chmod o-rw config.inc.php # remove world read and write permissions + rm -rf config # remove not needed directory + +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. +#. You should deny access to the ``./libraries`` and ``./setup/lib`` + subfolders in your webserver configuration. For Apache you can use + supplied :term:`.htaccess` file in that folder, for other webservers, you should + configure this yourself. Such configuration prevents from possible + path exposure and cross side scripting vulnerabilities that might + happen to be found in that code. +#. It is generally good idea to protect 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`. + +.. index:: + single: Configuration storage + single: phpMyAdmin configuration storage + single: pmadb + +.. _linked-tables: + +phpMyAdmin configuration storage +++++++++++++++++++++++++++++++++ + +For a whole set of new features (bookmarks, comments, :term:`SQL`-history, +tracking mechanism, :term:`PDF`-generation, column contents transformation, +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). + +Please look at your ``./examples/`` 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:`examples/upgrade_tables_mysql_4_1_2+.sql` +and then create new tables by importing +:file:`examples/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:`examples/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 with the proper rights to those tables (see section +:ref:`authentication_modes` below). + +.. _upgrading: + +Upgrading from an older version ++++++++++++++++++++++++++++++++ + +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 6, 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. + +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:`examples/upgrade_tables_mysql_4_1_2+.sql`. + +.. 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 blowfish algorithm, in a temporary cookie. + +.. note: + + This section is only applicable if your MySQL server is running + with ``--skip-show-database``. + +For ':term:`HTTP`' and 'cookie' modes, phpMyAdmin needs a controluser that has +**only** the ``SELECT`` privilege on the *`mysql`.`user` (all columns except +`Password`)*, *`mysql`.`db` (all columns)*, *`mysql`.`host` (all columns)* and +*`mysql`.`tables\_priv` (all columns except `Grantor` and `Timestamp`)* tables. +You must specify the details for the controluser in the :file:`config.inc.php` +file under the :config:option:`$cfg['Servers'][$i]['controluser']` and +:config:option:`$cfg['Servers'][$i]['controlpass']` settings. The following +example assumes you want to use ``pma`` as the controluser and ``pmapass`` as +the controlpass, but **this is only an example: use something else in your +file!** Input these statements from the phpMyAdmin :term:`SQL` Query window or +mysql command–line client. Of course you have to replace ``localhost`` with the +webserver's host if it's not the same as the MySQL server's one. + +.. code-block:: mysql + + GRANT USAGE ON mysql.* TO 'pma'@'localhost' IDENTIFIED BY 'pmapass'; + GRANT SELECT ( + Host, User, Select_priv, Insert_priv, Update_priv, Delete_priv, + Create_priv, Drop_priv, Reload_priv, Shutdown_priv, Process_priv, + File_priv, Grant_priv, References_priv, Index_priv, Alter_priv, + Show_db_priv, Super_priv, Create_tmp_table_priv, Lock_tables_priv, + Execute_priv, Repl_slave_priv, Repl_client_priv + ) ON mysql.user TO 'pma'@'localhost'; + GRANT SELECT ON mysql.db TO 'pma'@'localhost'; + GRANT SELECT ON mysql.host TO 'pma'@'localhost'; + GRANT SELECT (Host, Db, User, Table_name, Table_priv, Column_priv) + ON mysql.tables_priv TO 'pma'@'localhost'; + +If you want to use the many new relation and bookmark features: + +.. code-block:: mysql + + GRANT SELECT, INSERT, UPDATE, DELETE ON .* TO 'pma'@'localhost'; + +(this of course requires that your :ref:`linked-tables` be set up). + +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']`. + +.. index:: pair: HTTP; Authentication mode + +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`. +* See also :ref:`faq4_4` about not using the :term:`.htaccess` mechanism along with + ':term:`HTTP`' authentication mode. + +.. index:: pair: Cookie; Authentication mode + +.. _cookie: + +Cookie authentication mode +-------------------------- + +* You can use this method as a replacement for the :term:`HTTP` authentication + (for example, if you're running :term:`IIS`). +* Obviously, the user must enable cookies in the browser, but this is + now a requirement for all authentication modes. +* With this mode, the user can truly log out of phpMyAdmin and log in + back with the same username. +* If you want to log in to arbitrary server see :config:option:`$cfg['AllowArbitraryServer']` directive. +* As mentioned in the :ref:`require` section, having the ``mcrypt`` extension will + speed up access considerably, but is not required. + +.. index:: pair: Signon; Authentication mode + +Signon authentication mode +-------------------------- + +* This mode is a convenient way of using credentials from another + application to authenticate to phpMyAdmin. +* The other application has to store login information into session + data. + +.. seealso:: + :config:option:`$cfg['Servers'][$i]['auth_type']`, + :config:option:`$cfg['Servers'][$i]['SignonSession']`, + :config:option:`$cfg['Servers'][$i]['SignonScript']`, + :config:option:`$cfg['Servers'][$i]['SignonURL']` + + +.. index:: pair: Config; Authentication mode + +Config authentication mode +-------------------------- + +* This mode is 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). But you don't need to setup + a "controluser" here: using the :config:option:`$cfg['Servers'][$i]['only_db']` might be enough. +* 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). + +.. index:: pair: Swekey; Authentication mode + +.. _swekey: + +Swekey authentication mode +-------------------------- + +The Swekey is a low cost authentication USB key that can be used in +web applications. When Swekey authentication is activated, phpMyAdmin +requires the users's Swekey to be plugged before entering the login +page (currently supported for cookie authentication mode only). Swekey +Authentication is disabled by default. To enable it, add the following +line to :file:`config.inc.php`: + +.. code-block:: php + + $cfg['Servers'][$i]['auth_swekey_config'] = '/etc/swekey.conf'; + +You then have to create the ``swekey.conf`` file that will associate +each user with their Swekey Id. It is important to place this file +outside of your web server's document root (in the example, it is +located in ``/etc``). A self documented sample file is provided in the +``examples`` directory. Feel free to use it with your own users' +information. If you want to purchase a Swekey please visit +`http://phpmyadmin.net/auth\_key `_ +since this link provides funding for phpMyAdmin. + +.. seealso:: :config:option:`$cfg['Servers'][$i]['auth_swekey_config']` + + +Securing your phpMyAdmin installation ++++++++++++++++++++++++++++++++++++++ + +The phpMyAdmin team tries hardly to make the application secure, however there +are always ways to make your installation more secure: + +* remove ``setup`` directory from phpMyAdmin, you will probably not + use it after initial setup +* properly choose authentication method - :ref:`cookie` + is probably the best choice for shared hosting +* 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 +* consider hiding phpMyAdmin behind authentication proxy, so that + MySQL credentials are not all users need to login +* if you are afraid of automated attacks, enabling Captcha by + :config:option:`$cfg['CaptchaLoginPublicKey']` and + :config:option:`$cfg['CaptchaLoginPrivateKey']` might be an option. diff --git a/sources/doc/html/_sources/transformations.txt b/sources/doc/html/_sources/transformations.txt new file mode 100644 index 0000000..4c3ce46 --- /dev/null +++ b/sources/doc/html/_sources/transformations.txt @@ -0,0 +1,138 @@ +.. _transformations: + +Transformations +=============== + +.. _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'". + +.. _transformationsfiles: + +File structure +++++++++++++++ + +All specific transformations for mimetypes are defined through class +files in the directory 'libraries/plugins/transformations/'. Each of +them extends a certain transformation abstract class declared in +libraries/plugins/transformations/abstract. + +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/plugins/transformations/TEMPLATE and +libraries/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:`libraries/plugins/transformations/generator_plugin.sh` or +:file:`libraries/plugins/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/sources/doc/html/_sources/user.txt b/sources/doc/html/_sources/user.txt new file mode 100644 index 0000000..ceaf723 --- /dev/null +++ b/sources/doc/html/_sources/user.txt @@ -0,0 +1,9 @@ +User Guide +========== + +.. toctree:: + :maxdepth: 2 + + transformations + privileges + other diff --git a/sources/doc/html/_sources/vendors.txt b/sources/doc/html/_sources/vendors.txt new file mode 100644 index 0000000..13d7e55 --- /dev/null +++ b/sources/doc/html/_sources/vendors.txt @@ -0,0 +1,34 @@ +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. + +libraries/php-gettext + php-gettext library +libraries/tcpdf + tcpdf library, stripped down of not needed files + diff --git a/sources/doc/html/_static/ajax-loader.gif b/sources/doc/html/_static/ajax-loader.gif new file mode 100644 index 0000000..61faf8c Binary files /dev/null and b/sources/doc/html/_static/ajax-loader.gif differ diff --git a/sources/doc/html/_static/basic.css b/sources/doc/html/_static/basic.css new file mode 100644 index 0000000..43e8baf --- /dev/null +++ b/sources/doc/html/_static/basic.css @@ -0,0 +1,540 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2011 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%; +} + +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; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + width: 30px; +} + +img { + border: 0; +} + +/* -- 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 --------------------------------------------------- */ + +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 { + 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.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; +} + +/* -- 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; +} + +.refcount { + color: #060; +} + +.optional { + font-size: 1.3em; +} + +.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 */ +} + +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; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + 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/sources/doc/html/_static/comment-bright.png b/sources/doc/html/_static/comment-bright.png new file mode 100644 index 0000000..551517b Binary files /dev/null and b/sources/doc/html/_static/comment-bright.png differ diff --git a/sources/doc/html/_static/comment-close.png b/sources/doc/html/_static/comment-close.png new file mode 100644 index 0000000..09b54be Binary files /dev/null and b/sources/doc/html/_static/comment-close.png differ diff --git a/sources/doc/html/_static/comment.png b/sources/doc/html/_static/comment.png new file mode 100644 index 0000000..92feb52 Binary files /dev/null and b/sources/doc/html/_static/comment.png differ diff --git a/sources/doc/html/_static/default.css b/sources/doc/html/_static/default.css new file mode 100644 index 0000000..21f3f50 --- /dev/null +++ b/sources/doc/html/_static/default.css @@ -0,0 +1,256 @@ +/* + * default.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- default theme. + * + * :copyright: Copyright 2007-2011 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 { + 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; +} + +tt { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th { + background-color: #ede; +} + +.warning tt { + background: #efc2c2; +} + +.note tt { + 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; +} \ No newline at end of file diff --git a/sources/doc/html/_static/doctools.js b/sources/doc/html/_static/doctools.js new file mode 100644 index 0000000..d4619fd --- /dev/null +++ b/sources/doc/html/_static/doctools.js @@ -0,0 +1,247 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2011 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; +}; + +/** + * small function to check if an array contains + * a given item. + */ +jQuery.contains = function(arr, item) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] == item) + return true; + } + return false; +}; + +/** + * 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); + }); +}; + +/** + * 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 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + 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'); + 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); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/sources/doc/html/_static/down-pressed.png b/sources/doc/html/_static/down-pressed.png new file mode 100644 index 0000000..6f7ad78 Binary files /dev/null and b/sources/doc/html/_static/down-pressed.png differ diff --git a/sources/doc/html/_static/down.png b/sources/doc/html/_static/down.png new file mode 100644 index 0000000..3003a88 Binary files /dev/null and b/sources/doc/html/_static/down.png differ diff --git a/sources/doc/html/_static/file.png b/sources/doc/html/_static/file.png new file mode 100644 index 0000000..d18082e Binary files /dev/null and b/sources/doc/html/_static/file.png differ diff --git a/sources/doc/html/_static/jquery.js b/sources/doc/html/_static/jquery.js new file mode 100644 index 0000000..7c24308 --- /dev/null +++ b/sources/doc/html/_static/jquery.js @@ -0,0 +1,154 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, +Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& +(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, +a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== +"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, +function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a"; +var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, +parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= +false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= +s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, +applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; +else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, +a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== +w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, +cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, +function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); +k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), +C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= +e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& +f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; +if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", +e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, +"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, +d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); +t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| +g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== +"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, +serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), +function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, +global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& +e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? +"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== +false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= +false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", +c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| +d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); +g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== +1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== +"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; +if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== +"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| +c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; +this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= +this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, +e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
"; +a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); +c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, +d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- +f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": +"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in +e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); diff --git a/sources/doc/html/_static/minus.png b/sources/doc/html/_static/minus.png new file mode 100644 index 0000000..da1c562 Binary files /dev/null and b/sources/doc/html/_static/minus.png differ diff --git a/sources/doc/html/_static/plus.png b/sources/doc/html/_static/plus.png new file mode 100644 index 0000000..b3cb374 Binary files /dev/null and b/sources/doc/html/_static/plus.png differ diff --git a/sources/doc/html/_static/pygments.css b/sources/doc/html/_static/pygments.css new file mode 100644 index 0000000..1a14f2a --- /dev/null +++ b/sources/doc/html/_static/pygments.css @@ -0,0 +1,62 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #303030 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0040D0 } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/sources/doc/html/_static/searchtools.js b/sources/doc/html/_static/searchtools.js new file mode 100644 index 0000000..663be4c --- /dev/null +++ b/sources/doc/html/_static/searchtools.js @@ -0,0 +1,560 @@ +/* + * searchtools.js_t + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilties for the full-text search. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words, hlwords is the list of normal, unstemmed + * words. the first one is used to find the occurance, the + * latter for highlighting it. + */ + +jQuery.makeSearchSummary = function(text, keywords, hlwords) { + var textLower = text.toLowerCase(); + var start = 0; + $.each(keywords, function() { + var i = textLower.indexOf(this.toLowerCase()); + if (i > -1) + start = i; + }); + start = Math.max(start - 120, 0); + var excerpt = ((start > 0) ? '...' : '') + + $.trim(text.substr(start, 240)) + + ((start + 240 - text.length) ? '...' : ''); + var rv = $('
').text(excerpt); + $.each(hlwords, function() { + rv = rv.highlightText(this, 'highlighted'); + }); + return rv; +} + + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + + +/** + * Search Module + */ +var Search = { + + _index : null, + _queued_query : null, + _pulse_status : -1, + + init : function() { + var params = $.getQueryParameters(); + if (params.q) { + var query = params.q[0]; + $('input[name="q"]')[0].value = query; + this.performSearch(query); + } + }, + + loadIndex : function(url) { + $.ajax({type: "GET", url: url, data: null, success: null, + dataType: "script", cache: true}); + }, + + setIndex : function(index) { + var q; + this._index = index; + if ((q = this._queued_query) !== null) { + this._queued_query = null; + Search.query(q); + } + }, + + hasIndex : function() { + return this._index !== null; + }, + + deferQuery : function(query) { + this._queued_query = query; + }, + + stopPulse : function() { + this._pulse_status = 0; + }, + + startPulse : function() { + if (this._pulse_status >= 0) + return; + function pulse() { + Search._pulse_status = (Search._pulse_status + 1) % 4; + var dotString = ''; + for (var i = 0; i < Search._pulse_status; i++) + dotString += '.'; + Search.dots.text(dotString); + if (Search._pulse_status > -1) + window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something + */ + performSearch : function(query) { + // create the required interface elements + this.out = $('#search-results'); + this.title = $('

' + _('Searching') + '

').appendTo(this.out); + this.dots = $('').appendTo(this.title); + this.status = $('

').appendTo(this.out); + this.output = $('
'); + } + // Prettify the comment rating. + comment.pretty_rating = comment.rating + ' point' + + (comment.rating == 1 ? '' : 's'); + // Make a class (for displaying not yet moderated comments differently) + comment.css_class = comment.displayed ? '' : ' moderate'; + // Create a div for this comment. + var context = $.extend({}, opts, comment); + var div = $(renderTemplate(commentTemplate, context)); + + // If the user has voted on this comment, highlight the correct arrow. + if (comment.vote) { + var direction = (comment.vote == 1) ? 'u' : 'd'; + div.find('#' + direction + 'v' + comment.id).hide(); + div.find('#' + direction + 'u' + comment.id).show(); + } + + if (opts.moderator || comment.text != '[deleted]') { + div.find('a.reply').show(); + if (comment.proposal_diff) + div.find('#sp' + comment.id).show(); + if (opts.moderator && !comment.displayed) + div.find('#cm' + comment.id).show(); + if (opts.moderator || (opts.username == comment.username)) + div.find('#dc' + comment.id).show(); + } + return div; + } + + /** + * A simple template renderer. Placeholders such as <%id%> are replaced + * by context['id'] with items being escaped. Placeholders such as <#id#> + * are not escaped. + */ + function renderTemplate(template, context) { + var esc = $(document.createElement('div')); + + function handle(ph, escape) { + var cur = context; + $.each(ph.split('.'), function() { + cur = cur[this]; + }); + return escape ? esc.text(cur || "").html() : cur; + } + + return template.replace(/<([%#])([\w\.]*)\1>/g, function() { + return handle(arguments[2], arguments[1] == '%' ? true : false); + }); + } + + /** Flash an error message briefly. */ + function showError(message) { + $(document.createElement('div')).attr({'class': 'popup-error'}) + .append($(document.createElement('div')) + .attr({'class': 'error-message'}).text(message)) + .appendTo('body') + .fadeIn("slow") + .delay(2000) + .fadeOut("slow"); + } + + /** Add a link the user uses to open the comments popup. */ + $.fn.comment = function() { + return this.each(function() { + var id = $(this).attr('id').substring(1); + var count = COMMENT_METADATA[id]; + var title = count + ' comment' + (count == 1 ? '' : 's'); + var image = count > 0 ? opts.commentBrightImage : opts.commentImage; + var addcls = count == 0 ? ' nocomment' : ''; + $(this) + .append( + $(document.createElement('a')).attr({ + href: '#', + 'class': 'sphinx-comment-open' + addcls, + id: 'ao' + id + }) + .append($(document.createElement('img')).attr({ + src: image, + alt: 'comment', + title: title + })) + .click(function(event) { + event.preventDefault(); + show($(this).attr('id').substring(2)); + }) + ) + .append( + $(document.createElement('a')).attr({ + href: '#', + 'class': 'sphinx-comment-close hidden', + id: 'ah' + id + }) + .append($(document.createElement('img')).attr({ + src: opts.closeCommentImage, + alt: 'close', + title: 'close' + })) + .click(function(event) { + event.preventDefault(); + hide($(this).attr('id').substring(2)); + }) + ); + }); + }; + + var opts = { + processVoteURL: '/_process_vote', + addCommentURL: '/_add_comment', + getCommentsURL: '/_get_comments', + acceptCommentURL: '/_accept_comment', + deleteCommentURL: '/_delete_comment', + commentImage: '/static/_static/comment.png', + closeCommentImage: '/static/_static/comment-close.png', + loadingImage: '/static/_static/ajax-loader.gif', + commentBrightImage: '/static/_static/comment-bright.png', + upArrow: '/static/_static/up.png', + downArrow: '/static/_static/down.png', + upArrowPressed: '/static/_static/up-pressed.png', + downArrowPressed: '/static/_static/down-pressed.png', + voting: false, + moderator: false + }; + + if (typeof COMMENT_OPTIONS != "undefined") { + opts = jQuery.extend(opts, COMMENT_OPTIONS); + } + + var popupTemplate = '\ +
\ +

\ + Sort by:\ + best rated\ + newest\ + oldest\ +

\ +
Comments
\ +
\ + loading comments...
\ +
    \ +
    \ +

    Add a comment\ + (markup):

    \ +
    \ + reStructured text markup: *emph*, **strong**, \ + ``code``, \ + code blocks: :: and an indented block after blank line
    \ +
    \ + \ +

    \ + \ + Propose a change ▹\ + \ + \ + Propose a change ▿\ + \ +

    \ + \ + \ + \ + \ +
    \ +
    \ +
    '; + + var commentTemplate = '\ +
    \ +
    \ +
    \ + \ + \ + \ + \ + \ + \ +
    \ +
    \ + \ + \ + \ + \ + \ + \ +
    \ +
    \ +
    \ +

    \ + <%username%>\ + <%pretty_rating%>\ + <%time.delta%>\ +

    \ +
    <#text#>
    \ +

    \ + \ + reply ▿\ + proposal ▹\ + proposal ▿\ + \ + \ +

    \ +
    \
    +<#proposal_diff#>\
    +        
    \ +
      \ +
      \ +
      \ +
      \ + '; + + var replyTemplate = '\ +
    • \ +
      \ +
      \ + \ + \ + \ + \ + \ +
      \ +
      \ +
    • '; + + $(document).ready(function() { + init(); + }); +})(jQuery); + +$(document).ready(function() { + // add comment anchors for all paragraphs that are commentable + $('.sphinx-has-comment').comment(); + + // highlight search words in search results + $("div.context").each(function() { + var params = $.getQueryParameters(); + var terms = (params.q) ? params.q[0].split(/\s+/) : []; + var result = $(this); + $.each(terms, function() { + result.highlightText(this.toLowerCase(), 'highlighted'); + }); + }); + + // directly open comment window if requested + var anchor = document.location.hash; + if (anchor.substring(0, 9) == '#comment-') { + $('#ao' + anchor.substring(9)).click(); + document.location.hash = '#s' + anchor.substring(9); + } +}); diff --git a/sources/doc/html/config.html b/sources/doc/html/config.html new file mode 100644 index 0000000..c7acdf0 --- /dev/null +++ b/sources/doc/html/config.html @@ -0,0 +1,5015 @@ + + + + + + + + + + Configuration — phpMyAdmin 4.1.4 documentation + + + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +

      Configuration

      +

      Almost all configurable data is placed in config.inc.php. 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.

      +

      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:''
      +

      Sets here the complete URL (with full path) to your phpMyAdmin +installation’s directory. E.g. +http://www.example.net/path_to_your_phpMyAdmin_directory/. Note also +that the URL on most of web servers are case–sensitive. 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 will need to set $cfg['PmaAbsoluteUri'] +(more info).

      +

      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['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['McryptDisableWarning']
      +
      +++ + + + + + +
      Type:boolean
      Default value:false
      +

      Disable the default warning that is displayed if mcrypt is missing for +cookie authentication.

      +

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

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

      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['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 bellow
      +

      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'
      • +
      • dot - '.', i.e., use named pipes on windows systems
      • +
      • empty - '', disables this server
      • +
      +
      +

      Note

      +
      +
      phpMyAdmin supports connecting to MySQL servers reachable via IPv6 only.
      +
      To connect to an IPv6 MySQL server, enter its IPv6 address in this field.
      +
      +
      +
      + +
      +
      +$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.

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

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

      +

      When using $cfg['Servers'][$i]['extension'] = 'mysql', +none of the remaining 'ssl...' configuration options apply.

      +

      We strongly recommend using $cfg['Servers'][$i]['extension'] = 'mysqli' +when using this option.

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

      Path to the key file when using SSL for connecting to the MySQL 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 cert file when using SSL for connecting to the MySQL 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]['connect_type']
      +
      +++ + + + + + +
      Type:string
      Default value:'tcp'
      +

      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]['extension']
      +
      +++ + + + + + +
      Type:string
      Default value:'mysqli'
      +

      What php MySQL extension to use for the connection. Valid options are:

      +
      +
      mysql
      +
      The classic MySQL extension.
      +
      mysqli
      +
      The improved MySQL extension. This extension became available with PHP +5.0.0 and is the recommended way to connect to a server running MySQL +4.1.x or newer.
      +
      +
      + +
      +
      +$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 for 2 distinct purposes: to make possible all +relational features (see $cfg['Servers'][$i]['pmadb']) and, +for a MySQL server running with --skip-show-database, to enable a +multi-user installation (HTTP or cookie +authentication mode).

      +

      When using HTTP or +cookie authentication modes (or ‘config’ authentication mode since phpMyAdmin +2.2.1), you need to supply the details of a MySQL account that has SELECT +privilege on the mysql.user (all columns except “Password”), mysql.db (all +columns) and mysql.tables_priv (all columns except “Grantor” and +“Timestamp”) tables. This account is used to check what databases the user +will see at login.

      +

      +Changed in version 2.2.5: those were called stduser and stdpass

      + +
      + +
      +
      +$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') as +introduced in 2.2.3 allows you to log in as any valid MySQL user with +the help of cookies. Username and password are stored in cookies +during the session and password is deleted when it ends. This can also +allow you to log in in arbitrary server if $cfg['AllowArbitraryServer'] enabled.
      • +
      • ‘http’ authentication (was +called ‘advanced’ in previous versions and can be written also as +‘http’) ($auth_type = 'http';') as introduced in 1.3.0 allows you to log in as any +valid MySQL user via HTTP-Auth.
      • +
      • ‘signon’ authentication mode ($auth_type = 'signon') as +introduced in 2.10.0 allows you to log in from prepared PHP session +data or using supplied PHP script. This is useful for implementing +single signon from another application. Sample way how to seed session +is in signon example: examples/signon.php. There is also +alternative example using OpenID - examples/openid.php and example +for scripts based solution - examples/signon-script.php. You need +to configure $cfg['Servers'][$i]['SignonSession'] or +$cfg['Servers'][$i]['SignonScript'] and +$cfg['Servers'][$i]['SignonURL'] to use this authentication +method.
      • +
      + +
      + +
      +
      +$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]['auth_swekey_config']
      +
      +++ + + + + + +
      Type:string
      Default value:''
      +

      The name of the file containing Swekey authentication mode ids and login names for hardware +authentication. Leave empty to deactivate this feature.

      +
      + +
      +
      +$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
      +

      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]['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
      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:

      + +
      + +
      +
      +$cfg['Servers'][$i]['relation']
      +
      +++ + + + + + +
      Type:string
      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.
      • +
      +
      +

      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
      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:

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

      Since release 2.3.0 you can have phpMyAdmin create PDF pages +showing the relations between your tables. 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:

      + + +
      + +
      +
      +$cfg['Servers'][$i]['column_info']
      +
      +++ + + + + + +
      Type:string
      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’.

      +

      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;
        +
        +
        +
      • +
      +
      + +
      +
      +$cfg['Servers'][$i]['history']
      +
      +++ + + + + + +
      Type:string
      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:

      + +
      + +
      +
      +$cfg['Servers'][$i]['recent']
      +
      +++ + + + + + +
      Type:string
      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:

      + +
      + +
      +
      +$cfg['Servers'][$i]['table_uiprefs']
      +
      +++ + + + + + +
      Type:string
      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:

      + +
      + +
      +
      +$cfg['Servers'][$i]['users']
      +
      +++ + + + + + +
      Type:string
      Default value:''
      +
      + +
      +
      +$cfg['Servers'][$i]['usergroups']
      +
      +++ + + + + + +
      Type:string
      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:

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

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

      +

      To allow the usage of this functionality:

      + +
      + +
      +
      +$cfg['Servers'][$i]['tracking']
      +
      +++ + + + + + +
      Type:string
      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:

      + +
      + +
      +
      +$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
      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:

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

      Since release 2.10.0 a Designer interface is available; it permits to +visually manage the relations.

      +

      To allow the usage of this functionality:

      + +
      + +
      +
      +$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]['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)
      • +
      +
      + +
      +
      +$cfg['Servers'][$i]['SignonScript']
      +
      +++ + + + + + +
      Type:string
      Default value:''
      +

      Name of PHP script to be sourced and executed to obtain login +credentials. This is alternative approach to session based single +signon. The script needs to provide function +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.

      +
      + +
      +
      +$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]['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.

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

      Enables caching of TABLE STATUS outputs for specific databases on +this server (in some cases TABLE STATUS can be very slow, so you +may want to cache it). APC is used (if the PHP extension is available, +if not, this setting is ignored silently). You have to provide +$cfg['Servers'][$i]['StatusCacheLifetime'].

      +
      + +
      +
      +$cfg['Servers'][$i]['StatusCacheLifetime']
      +
      +++ + + + + + +
      Type:integer
      Default value:0
      +

      Lifetime in seconds of the TABLE STATUS cache if +$cfg['Servers'][$i]['StatusCacheDatabases'] is used.

      +
      + +
      +
      +

      Generic settings

      +
      +
      +$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 main phpMyAdmin +page.

      +
      +

      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 +intenet 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). This limit is also enforced in the +navigation panel when in Light mode.

      +
      + +
      +
      +$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
      +

      Whether to force using https while accessing phpMyAdmin.

      +
      +

      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).

      +
      + +
      +
      +$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.

      +

      This setting is used while importing/exporting dump files and at some other +places in phpMyAdmin 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['AllowUserDropDatabase']
      +
      +++ + + + + + +
      Type:boolean
      Default value:false
      +

      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.

      +
      +

      Note

      +

      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
      +
      + +
      +
      +$cfg['ShowChgPassword']
      +
      +++ + + + + + +
      Type:boolean
      Default value:true
      +
      + +
      +
      +$cfg['ShowCreateDb']
      +
      +++ + + + + + +
      Type:boolean
      Default value:true
      +

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

      +

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

      +
      disable_functions = phpinfo()
      +
      +
      +

      Also 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.

      +
      + +
      +
      +

      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.

      +
      + +
      +
      +

      Browse mode

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

      Defines whether the table navigation links contain '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.

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

      Defines whether the “Show binary contents as HEX” browse option is +ticked by default.

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

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

      +
      + +
      +
      +$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 maximum number of concurrent entries for the Insert page.

      +
      + +
      +
      +$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']['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['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.

      +
      + +
      +
      +

      Tabs display settings

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

      Defines whether the menu tabs 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['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:'index.php'
      +

      Defines the tab displayed by default on server view. Possible values:

      +
        +
      • main.php (recommended for multi-user setups)
      • +
      • server_databases.php,
      • +
      • server_status.php
      • +
      • server_variables.php
      • +
      • server_privileges.php
      • +
      +
      + +
      +
      +$cfg['DefaultTabDatabase']
      +
      +++ + + + + + +
      Type:string
      Default value:'db_structure.php'
      +

      Defines the tab displayed by default on database view. Possible +values:

      +
        +
      • db_structure.php
      • +
      • db_sql.php
      • +
      • db_search.php.
      • +
      +
      + +
      +
      +$cfg['DefaultTabTable']
      +
      +++ + + + + + +
      Type:string
      Default value:'sql.php'
      +

      Defines the tab displayed by default on table view. Possible values:

      +
        +
      • tbl_structure.php
      • +
      • tbl_sql.php
      • +
      • tbl_select.php
      • +
      • tbl_change.php
      • +
      • sql.php
      • +
      +
      + +
      +
      +

      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:'utf8_general_ci'
      +

      Defines the default connection collation to use, if not user-defined. +See the MySQL documentation for list of possible values. This setting is +ignored when connected to Drizzle server.

      +
      + +
      +
      +$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

      +
      +
      +$cfg['NaviWidth']
      +
      +++ + + + + + +
      Type:integer
      Default value:
      +

      Navigation panel width in pixels. See +themes/themename/layout.inc.php.

      +
      + +
      +
      +$cfg['NaviBackground']
      +
      +++ + + + + + +
      Type:string [CSS color for background]
      Default value:
      +
      + +
      +
      +$cfg['MainBackground']
      +
      +++ + + + + + +
      Type:string [CSS color for background]
      Default value:
      +

      The background styles used for both the frames. See +themes/themename/layout.inc.php.

      +
      + +
      +
      +$cfg['NaviPointerBackground']
      +
      +++ + + + + + +
      Type:string [CSS color for background]
      Default value:
      +
      + +
      +
      +$cfg['NaviPointerColor']
      +
      +++ + + + + + +
      Type:string [CSS color]
      Default value:
      +

      The style used for the pointer in the navigation panel. See +themes/themename/layout.inc.php.

      +
      + +
      +
      +$cfg['Border']
      +
      +++ + + + + + +
      Type:integer
      Default value:
      +

      The size of a table’s border. See themes/themename/layout.inc.php.

      +
      + +
      +
      +$cfg['ThBackground']
      +
      +++ + + + + + +
      Type:string [CSS color for background]
      Default value:
      +
      + +
      +
      +$cfg['ThColor']
      +
      +++ + + + + + +
      Type:string [CSS color]
      Default value:
      +

      The style used for table headers. See +themes/themename/layout.inc.php.

      +
      + +
      +
      +$cfg['BgOne']
      +
      +++ + + + + + +
      Type:string [CSS color]
      Default value:
      +

      The color (HTML) #1 for table rows. See +themes/themename/layout.inc.php.

      +
      + +
      +
      +$cfg['BgTwo']
      +
      +++ + + + + + +
      Type:string [CSS color]
      Default value:
      +

      The color (HTML) #2 for table rows. See +themes/themename/layout.inc.php.

      +
      + +
      +
      +$cfg['BrowsePointerBackground']
      +
      +++ + + + + + +
      Type:string [CSS color]
      Default value:
      +
      + +
      +
      +$cfg['BrowsePointerColor']
      +
      +++ + + + + + +
      Type:string [CSS color]
      Default value:
      +
      + +
      +
      +$cfg['BrowseMarkerBackground']
      +
      +++ + + + + + +
      Type:string [CSS color]
      Default value:
      +
      + +
      +
      +$cfg['BrowseMarkerColor']
      +
      +++ + + + + + +
      Type:string [CSS color]
      Default value:
      +

      The colors (HTML) uses for the pointer and the marker in browse mode. +The former feature highlights the row over which your mouse is passing +and the latter lets you visually mark/unmark rows by clicking on the +corresponding checkbox. Highlighting / marking a column is done by +hovering over / clicking the column’s header (outside of the text). +See themes/themename/layout.inc.php.

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

      You put here a valid CSS font family value, for example arial, sans- +serif. See themes/themename/layout.inc.php.

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

      You put here a valid CSS font family value, for example monospace. +This one is used in textarea. See themes/themename/layout.inc.php.

      +
      + +
      +
      +

      Design customization

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

      A value of true activates the navi pointer.

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

      Whether to activate the browse pointer or not.

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

      Whether to activate the browse marker or not.

      +
      + +
      +
      +$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). “left” and “right” +are parsed as “top” and “bottom” with vertical display mode.

      +
      + +
      +
      +$cfg['DefaultDisplay']
      +
      +++ + + + + + +
      Type:string
      Default value:'horizonta'
      +

      There are 3 display modes: horizontal, horizontalflipped and vertical. +Define which one is displayed by default. The first mode displays each +row on a horizontal line, the second rotates the headers by 90 +degrees, so you can use descriptive headers even though columns only +contain small values and still print them out. The vertical mode sorts +each row on a vertical lineup.

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

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

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

      The HeaderFlipType can be set to ‘auto’, ‘css’ or ‘fake’. When using +‘css’ the rotation of the header for horizontalflipped is done via +CSS. The CSS transformation currently works only in Internet +Explorer.If set to ‘fake’ PHP does the transformation for you, but of +course this does not look as good as CSS. The ‘auto’ option enables +CSS transformation when browser supports it and use PHP based one +otherwise.

      +
      + +
      +
      +$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. 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.

      +
      + +
      +
      +

      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']['Validate']
      +
      +++ + + + + + +
      Type:boolean
      Default value:false
      +

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

      +
      +

      See also

      +

      $cfg['SQLValidator']

      +
      +
      + +
      +
      +$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

      +
      +
      +$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.

      +
      +

      Note

      +

      If PHP is running in safe mode, this directory must be owned by the same +user as the owner of the phpMyAdmin scripts. See also 1.16 I cannot upload big dump files (memory, HTTP or timeout problems). for +alternatives.

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

      The name of the directory where dumps can be saved.

      +

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

      +

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

      +
      +

      Note

      +

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

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

      The name of the directory where temporary files can be stored.

      +

      This is needed for importing ESRI Shapefiles, see 6.30 Import: How can I import ESRI Shapefiles? and to +work around limitations of open_basedir for uploaded files, see +1.11 I get an ‘open_basedir restriction’ while uploading a file from the query box..

      +

      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 web server. +However for security reasons, this directory should be outside the +tree published by webserver. If you cannot avoid having this directory +published by webserver, place at least an empty index.html file +there, so that directory listing is not possible.

      +

      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.

      +
      + +
      +
      +

      Various display setting

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

      Defines whether or not type display direction option is shown when +browsing a table.

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

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

      +
      + +
      +
      +$cfg['EditInWindow']
      +
      +++ + + + + + +
      Type:boolean
      Default value:true
      +
      + +
      +
      +$cfg['QueryWindowWidth']
      +
      +++ + + + + + +
      Type:integer
      Default value:550
      +
      + +
      +
      +$cfg['QueryWindowHeight']
      +
      +++ + + + + + +
      Type:integer
      Default value:310
      +
      + +
      +
      +$cfg['QueryHistoryDB']
      +
      +++ + + + + + +
      Type:boolean
      Default value:false
      +
      + +
      +
      +$cfg['QueryWindowDefTab']
      +
      +++ + + + + + +
      Type:string
      Default value:'sql'
      +
      + +
      +
      +$cfg['QueryHistoryMax']
      +
      +++ + + + + + +
      Type:integer
      Default value:25
      +

      All those variables affect the query window feature. A SQL link or +icon is always displayed in the navigation panel. If JavaScript is enabled +in your browser, a click on this opens a distinct query window, which is a +direct interface to enter SQL queries. Otherwise, the right panel +changes to display a query box.

      +

      The size of this query window can be customized with +$cfg['QueryWindowWidth'] and +$cfg['QueryWindowHeight'] - both integers for the size in +pixels. Note that normally, those parameters will be modified in +layout.inc.php` for the theme you are using.

      +

      If $cfg['EditInWindow'] is set to true, a click on [Edit] +from the results page (in the Showing Rows section) opens the +query window and puts the current query inside it. If set to false, +clicking on the link puts the SQL query +in the right panel’s query box.

      +

      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'].

      +

      The query window also has a custom tabbed look to group the features. +Using the variable $cfg['QueryWindowDefTab'] you can +specify the default tab to be used when opening the query window. It can be +set to either sql, files, history or full.

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

      Enable Transformations.

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

      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.

      +
      + +
      +
      +$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 libraries/config/user_preferences.forms.php.

      +
      + +
      +
      +$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['ThemePath']
      +
      +++ + + + + + +
      Type:string
      Default value:'./themes'
      +

      If theme manager is active, use this as the path of the subdirectory +containing all the themes.

      +
      + +
      +
      +$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 $cfg['ThemePath']).

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

      Whether to allow different theme for each server.

      +
      + +
      +
      +

      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?.

      +
      + +
      +
      +

      SQL validator settings

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

      phpMyAdmin now supports use of the Mimer SQL Validator service, as originally +published on Slashdot. For +help in setting up your system to use the service, see the +6.14 How do I set up the SQL Validator?.

      +
      + +
      +
      +$cfg['SQLValidator']['username']
      +
      +++ + + + + + +
      Type:string
      Default value:''
      +
      + +
      +
      +$cfg['SQLValidator']['password']
      +
      +++ + + + + + +
      Type:string
      Default value:''
      +

      The SOAP service allows you to log in with anonymous and any password, +so we use those by default. Instead, if you have an account with them, you +can put your login details here, and it will be used in place of the +anonymous login.

      +
      + +
      +
      +

      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.

      +
      + +
      +
      +

      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 bottom of main page (right frame).

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

      Enable to let server present itself as demo server. +This is used for <http://demo.phpmyadmin.net/>.

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

      Whether to display errors from PHP or not.

      +
      + +
      +
      + + +
      +
      +
      + +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/copyright.html b/sources/doc/html/copyright.html new file mode 100644 index 0000000..8c2004b --- /dev/null +++ b/sources/doc/html/copyright.html @@ -0,0 +1,134 @@ + + + + + + + + + + Copyright — phpMyAdmin 4.1.4 documentation + + + + + + + + + + + + + + + + +
      +
      +
      +
      + + + + +
      +
      +
      +
      +
      +

      Previous topic

      +

      Distributing and packaging phpMyAdmin

      +

      Next topic

      +

      Credits

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/credits.html b/sources/doc/html/credits.html new file mode 100644 index 0000000..12f9131 --- /dev/null +++ b/sources/doc/html/credits.html @@ -0,0 +1,684 @@ + + + + + + + + + + Credits — phpMyAdmin 4.1.4 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
        • +
        • SQL analyser (most of it)
        • +
        • current project maintainer
        • +
        • maintains content on our website
        • +
        +
      • +
      • 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” +(http://sf.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> +
      • +
      • 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
        • +
        +
      • +
      +

      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:

      +
        +
      • Arabic
          +
        • Abdullah Al-Saedi <abdullah.10_at_windowslive.com>
        • +
        +
      • +
      • Bulgarian
          +
        • stoyanster <stoyanster_at_gmail.com>
        • +
        +
      • +
      • Catalan
          +
        • Xavier Navarro <xvnavarro_at_gmail.com>
        • +
        +
      • +
      • Czech
          +
        • Michal Čihař <michal_at_cihar.com>
        • +
        +
      • +
      • Danish
          +
        • opensource <opensource_at_jth.net>
        • +
        • Jørgen Thomsen <opensource_at_jth.net>
        • +
        +
      • +
      • German
          +
        • mrbendig <mrbendig_at_mrbendig.com>
        • +
        • torsten.funck <torsten.funck_at_googlemail.com>
        • +
        • Sven Strickroth <email_at_cs-ware.de>
        • +
        • typo3 <typo3_at_dirk-weise.de>
        • +
        • Jo Michael <me_at_mynetx.net>
        • +
        +
      • +
      • Greek
          +
        • Panagiotis Papazoglou <papaz_p_at_yahoo.com>
        • +
        +
      • +
      • English (United Kingdom)
          +
        • Robert Readman <robert_readman_at_hotmail.com>
        • +
        +
      • +
      • Spanish
          +
        • Matías Bellone <matiasbellone_at_gmail.com>
        • +
        +
      • +
      • French
          +
        • Marc Delisle <marc_at_infomarc.info>
        • +
        +
      • +
      • Hindi
          +
        • u4663530 <u4663530_at_anu.edu.au>
        • +
        • rsedwardian <rsedwardian_at_gmail.com>
        • +
        +
      • +
      • Hungarian
          +
        • gergo314 <gergo314_at_gmail.com>
        • +
        +
      • +
      • Italian
          +
        • Rouslan Placella <rouslan_at_placella.com>
        • +
        +
      • +
      • Japanese
          +
        • Yuichiro <yuichiro_at_pop07.odn.ne.jp>
        • +
        +
      • +
      • Lithuanian
          +
        • Kęstutis <forkik_at_gmail.com>
        • +
        +
      • +
      • Norwegian Bokmål
          +
        • Sven-Erik Andersen <sven.erik.andersen_at_gmail.com>
        • +
        +
      • +
      • Dutch
          +
        • Dieter Adriaenssens <ruleant_at_users.sourceforge.net>
        • +
        • Herman van Rink <rink_at_initfour.nl>
        • +
        +
      • +
      • Polish
          +
        • Stanisław Krukowski <stankruk_at_neostrada.pl>
        • +
        • Marcin Kozioł <lord_dark_at_wp.pl>
        • +
        +
      • +
      • Portuguese
          +
        • JoaoTMDias <contacto_at_joaodias.me>
        • +
        +
      • +
      • Portuguese (Brazil)
          +
        • wiltave <wiltave_at_gmail.com>
        • +
        • emerson4br <emerson4br_at_gmail.com>
        • +
        +
      • +
      • Romanian
          +
        • alexukf <alex.ukf_at_gmail.com>
        • +
        +
      • +
      • Russian
          +
        • Victor Volkov <hanut_at_php-myadmin.ru>
        • +
        +
      • +
      • Sinhala
          +
        • Madhura Jayaratne <madhura.cj_at_gmail.com>
        • +
        +
      • +
      • Slovak
          +
        • Martin Lacina <martin_at_whistler.sk>
        • +
        +
      • +
      • Slovenian
          +
        • Domen <dbc334_at_gmail.com>
        • +
        +
      • +
      • Swedish
          +
        • stefan <stefan_at_inkopsforum.se>
        • +
        +
      • +
      • Tamil
          +
        • ysajeepan <ysajeepan_at_live.com>
        • +
        +
      • +
      • Telugu
          +
        • veeven <veeven_at_gmail.com>
        • +
        +
      • +
      • Thai
          +
        • kanitchet <kanichet_at_hotmail.com>
        • +
        +
      • +
      • Turkish
          +
        • Burak Yavuz <hitowerdigit_at_hotmail.com>
        • +
        +
      • +
      • Uighur
          +
        • gheni <gheni_at_yahoo.cn>
        • +
        +
      • +
      • Ukrainian
          +
        • typim <duke3d_at_ukr.net>
        • +
        • oleg-ilnytskyi <ukraine.oleg_at_gmail.com>
        • +
        +
      • +
      • Urdu
          +
        • Mehbooob Khan <mehboobbugti_at_gmail.com>
        • +
        +
      • +
      • Simplified Chinese
          +
        • shanyan baishui <Siramizu_at_gmail.com>
        • +
        +
      • +
      • Traditional Chinese
          +
        • star <star_at_origin.club.tw>
        • +
        +
      • +
      +
      +
      +

      Documentation translators

      +

      Following people have contributed to translation of phpMyAdmin documentation:

      +
        +
      • Czech
          +
        • Michal Čihař <michal_at_cihar.com>
        • +
        +
      • +
      • Greek
          +
        • Panagiotis Papazoglou <papaz_p_at_yahoo.com>
        • +
        +
      • +
      • English (United Kingdom)
          +
        • Robert Readman <robert_readman_at_hotmail.com>
        • +
        +
      • +
      • French
          +
        • Cédric Corazza <cedric.corazza_at_wanadoo.fr>
        • +
        +
      • +
      • Japanese
          +
        • Yuichiro Takahashi <yuichiro_at_pop07.odn.ne.jp>
        • +
        +
      • +
      • Polish
          +
        • Stanisław Krukowski <stankruk_at_neostrada.pl>
        • +
        +
      • +
      • Portuguese (Brazil)
          +
        • mjaning <mjaning_at_gmail.com>
        • +
        +
      • +
      • Slovenian
          +
        • Domen <dbc334_at_gmail.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.

      +
      +
      + + +
      +
      +
      +
      +
      +

      Table Of Contents

      + + +

      Previous topic

      +

      Copyright

      +

      Next topic

      +

      Glossary

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/developers.html b/sources/doc/html/developers.html new file mode 100644 index 0000000..91c0015 --- /dev/null +++ b/sources/doc/html/developers.html @@ -0,0 +1,118 @@ + + + + + + + + + + Developers Information — phpMyAdmin 4.1.4 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.

      +
      + + +
      +
      +
      +
      +
      +

      Previous topic

      +

      FAQ - Frequently Asked Questions

      +

      Next topic

      +

      Distributing and packaging phpMyAdmin

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/faq.html b/sources/doc/html/faq.html new file mode 100644 index 0000000..9db7978 --- /dev/null +++ b/sources/doc/html/faq.html @@ -0,0 +1,1791 @@ + + + + + + + + + + FAQ - Frequently Asked Questions — phpMyAdmin 4.1.4 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 or Bzip a dump or a CSV export? It does not seem to work.

      +

      These features are based on the gzencode() and bzcompress() +PHP functions to be more independent of the platform (Unix/Windows, +Safe Mode or not, and so on). So, you must have Zlib/Bzip2 support +(--with-zlib and --with-bz2).

      +
      +
      +

      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 query box.

      +

      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?

      +

      The MySQL manual explains how to reset the permissions.

      +
      +
      +

      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 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. One user also said that post_max_size and +memory_limit need 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 MySQL versions does phpMyAdmin support?

      +

      Since phpMyAdmin 3.0.x, only MySQL 5.0.1 and newer are supported. For +older MySQL versions, you need to use the latest 2.x branch. +phpMyAdmin can connect to your MySQL server using PHP’s classic MySQL +extension as well as the improved MySQL +extension (MySQLi) that is available in PHP +5.0. The latter one should be used unless you have a good reason not +to do so. When compiling PHP, we strongly recommend that you manually +link the MySQL extension of your choice to a MySQL client library of +at least the same minor version since the one that is bundled with +some PHP distributions is rather old and might cause problems see +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”. MariaDB is also supported +(versions 5.1 and 5.2 were tested).

      +

      +Changed in version 3.5: Since phpMyAdmin 3.5 Drizzle is 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 MySQL 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. Your chosen extension is specified in +$cfg['Servers'][$i]['extension']. 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 the error “cannot load MySQL extension, please check PHP Configuration”.

      +

      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 mysql.so or php_mysql.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 and you can change which of then is being used by +$cfg['Servers'][$i]['extension'].

      +
      + +
      +

      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
      +
      +
      +

      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 Does phpMyAdmin support PHP 5?

      +

      Yes.

      +

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

      +
      +
      +

      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 FollowSymLinks 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:

      + +

      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?

      +

      Be sure that you have enabled SSLOptions and StdEnvVars in +your Apache 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.

      +
      +
      +
      +

      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. We also got a report +from a user under IIS, that used a zipped distribution kit: the file +libraries/Config.class.php contained an end-of-line character (hex 0A) +at the end; removing this character cleared his errors.

      +
      +
      +

      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?

      +

      For RedHat users, Harald Legner suggests this on the mailing list:

      +

      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.

      +

      Here is a fix suggested by Brad Ummer:

      +
        +
      • First, you need to determine what socket is being used by MySQL. To do +this, telnet 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).
      • +
      • Then, you need to tell PHP to use this socket. To do this in +phpMyAdmin, you need to complete the socket information 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 (i.e. +‘0755’).
      • +
      +

      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

      +

      Themes are configured with $cfg['ThemePath'], +$cfg['ThemeManager'] and $cfg['ThemeDefault']. +Under $cfg['ThemePath'], 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.

      +

      To create a theme:

      +
        +
      • make a new subdirectory (for example “your_theme_name”) under $cfg['ThemePath'] (by +default 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 info.inc.php in “your_theme_name” to contain your chosen +theme name, that will be visible in user interface
      • +
      • make a new screenshot of your theme and save it under +“your_theme_name/screen.png”
      • +
      +

      In theme directory there is file info.inc.php which contains theme +verbose name, theme generation and theme version. These versions and +generations are enumerated from 1 and do not have any direct +dependence on phpMyAdmin version. Themes within same generation should +be backwards compatible - theme with version 2 should work in +phpMyAdmin requiring version 1. Themes with different generation are +incompatible.

      +

      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).

      +
      +
      +

      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 <http://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 Hardened-PHP, 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.
      • +
      +
      +
      +

      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 <http://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 What is currently not supported in phpMyAdmin about InnoDB?

      +

      In Relation view, having more than one index column in the foreign key. In Query-by- +example (Query), automatic generation of the query LEFT JOIN from the +foreign table.

      +
      +
      +

      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 one, 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 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 +<http://dev.mysql.com/doc/mysql/en/ansi-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 Bug report +#1013.

      +
      +
      +

      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.

      +
      +
      +

      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.

      +
      +
      +
      +

      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, pma_username, +pma_password 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. Using +pma_username and pma_password has been tested along with the +usage of ‘cookie’ auth_type.

      +
      +
      +
      +

      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’. +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 In Internet Explorer 5.0, I get JavaScript errors when browsing my rows.

      +

      Upgrade to at least Internet Explorer 5.5 SP2.

      +
      +
      +

      5.6 In Internet Explorer 5.0, 5.5 or 6.0, I get an error (like “Page not found”) when trying to modify a row in a table with many columns, or with a text column.

      +

      Your table neither have a primary key nor an unique one, so we must use a long +URL to identify this row. There is a limit on the length of the +URL in those browsers, and this not happen in Netscape, for example. +The workaround is to create a primary or unique key, or use another browser.

      +
      +
      +

      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 With Netscape 4.75 I get empty rows between each row of data in a CSV exported file.

      +

      This is a known Netscape 4.75 bug: it adds some line feeds when +exporting data in octet-stream mode. Since we can’t detect the +specific Netscape version, we cannot workaround this bug.

      +
      +
      +

      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 With Internet Explorer 5.5 or 6, and HTTP authentication type, I cannot manage two servers: I log in to the first one, then the other one, but if I switch back to the first, I have to log in on each operation.

      +

      This is a bug in Internet Explorer, other browsers do not behave this +way.

      +
      +
      +

      5.14 Using Opera6, I can manage to get to the authentication, but nothing happens after that, only a blank screen.

      +

      Please upgrade to Opera7 at least.

      +
      +
      +

      5.15 I have display problems with Safari.

      +

      Please upgrade to at least version 1.2.3.

      +
      +
      +

      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 With Konqueror 4.2.x an invalid LIMIT clause is generated when I browse a table.

      +

      This happens only when both of these conditions are met: using the +http authentication mode and register_globals being set to +On on the server. It seems to be a browser-specific problem; +meanwhile use the cookie authentication mode.

      +
      +
      +

      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.

      +
      +
      +
      +

      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.

      +
      +
      +

      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)
      +) TYPE=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)
      +) TYPE=MyISAM;
      +
      +INSERT INTO REL_persons VALUES (11, 'Marc', 'S', '');
      +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)
      +) TYPE=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.
      • +
      +
      +
      +

      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 How do I set up the SQL Validator?

      +

      To use SQL Validator, you need PHP with XML, PCRE and +PEAR support. In addition you need a SOAP support, either as a +PHP extension or as a PEAR SOAP module.

      +

      To install PEAR SOAP module, run pear install +Net_Socket Net_URL HTTP_Request Mail_Mime Net_DIME SOAP to get the necessary +PEAR modules for usage.

      +

      If you use the Validator, you should be aware that any SQL statement +you submit will be stored anonymously (database/table/column names, strings, +numbers replaced with generic values). The Mimer SQL Validator itself, +is © 2001 Upright Database Technology. We utilize it as free SOAP service.

      +
      +
      +

      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! WTF is this feature then useful for?

      +

      Slow down :). 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 is this variable for?

      +

      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.

      +

      You can also have, inside the query, a placeholder for a variable. +This is done by inserting into the query a SQL comment between /* and +*/. Inside the comment, the special string [VARIABLE] is used. +Be aware that the whole query minus the SQL comment must be +valid by itself, otherwise you won’t be able to store it as a bookmark.

      +

      When you execute the bookmark, everything typed into the value +input box on the query box page will replace the string /*[VARIABLE]*/ in +your stored query.

      +

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

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

      which will be expanded to

      +
      , VARIABLE as myname
      +
      +
      +

      in your query, where VARIABLE is the string you entered in the input box. If an +empty string is provided, no replacements are made.

      +

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

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

      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%'
      +
      +
      +

      You can use multiple occurrences of /*[VARIABLE]*/ in a single query +(that is, multiple occurrences of the same variable).

      +

      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.

      +

      Your initial query which is going to be stored as a bookmark has to yield at +least one result row so you can store the bookmark. You may have that to work +around using well positioned /**/ comments.

      +
      +
      +

      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.

      +
      +
      +

      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 the wiki.

      +
      +
      +

      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 +certail 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.

      +
      +
      +
      +

      phpMyAdmin project

      +
      +

      7.1 I have found a bug. How do I inform developers?

      +

      Our Bug Tracker is located at <http://sf.net/projects/phpmyadmin/> under the +Bugs section. But please first discuss your bug with other users: +<https://sourceforge.net/projects/phpmyadmin/forums>.

      +
      +
      +

      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 <http://www.phpmyadmin.net/home_page/security.php>.

      +
      +
      +

      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.

      +
      +
      +
      +

      Synchronization

      +
      +

      9.1 (withdrawn).

      +
      +
      +

      9.2 (withdrawn).

      +
      +
      +
      + + +
      +
      +
      +
      +
      +

      Table Of Contents

      + + +

      Previous topic

      +

      Other sources of information

      +

      Next topic

      +

      Developers Information

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/genindex.html b/sources/doc/html/genindex.html new file mode 100644 index 0000000..5077c19 --- /dev/null +++ b/sources/doc/html/genindex.html @@ -0,0 +1,3847 @@ + + + + + + + + + + + + Index — phpMyAdmin 4.1.4 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] +
      + + +
      $cfg['AllowThirdPartyFraming'] +
      + + +
      $cfg['AllowUserDropDatabase'] +
      + + +
      $cfg['AvailableCharsets'] +
      + + +
      $cfg['BgOne'] +
      + + +
      $cfg['BgTwo'] +
      + + +
      $cfg['blowfish_secret'], [1] +
      + + +
      $cfg['Border'] +
      + + +
      $cfg['BrowseMarkerBackground'] +
      + + +
      $cfg['BrowseMarkerColor'] +
      + + +
      $cfg['BrowseMarkerEnable'] +
      + + +
      $cfg['BrowseMIME'] +
      + + +
      $cfg['BrowsePointerBackground'] +
      + + +
      $cfg['BrowsePointerColor'] +
      + + +
      $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['CSPAllow'] +
      + + +
      $cfg['DBG'] +
      + + +
      $cfg['DBG']['demo'] +
      + + +
      $cfg['DBG']['sql'] +
      + + +
      $cfg['DefaultConnectionCollation'] +
      + + +
      $cfg['DefaultDisplay'] +
      + + +
      $cfg['DefaultFunctions'] +
      + + +
      $cfg['DefaultLang'] +
      + + +
      $cfg['DefaultQueryDatabase'] +
      + + +
      $cfg['DefaultQueryTable'] +
      + + +
      $cfg['DefaultTabDatabase'] +
      + + +
      $cfg['DefaultTabServer'] +
      + + +
      $cfg['DefaultTabTable'] +
      + + +
      $cfg['DisableMultiTableMaintenance'] +
      + + +
      $cfg['DisplayBinaryAsHex'] +
      + + +
      $cfg['DisplayServersList'] +
      + + +
      $cfg['EditInWindow'], [1] +
      + + +
      $cfg['Error_Handler']['display'] +
      + + +
      $cfg['ExecTimeLimit'], [1] +
      + + +
      $cfg['Export'] +
      + + +
      $cfg['Export']['charset'] +
      + + +
      $cfg['Export']['method'] +
      + + +
      $cfg['FilterLanguages'] +
      + + +
      $cfg['FontFamily'] +
      + + +
      $cfg['FontFamilyFixed'] +
      + + +
      $cfg['ForceSSL'] +
      + + +
      $cfg['ForeignKeyDropdownOrder'], [1] +
      + + +
      $cfg['ForeignKeyMaxLimit'], [1] +
      + + +
      $cfg['GD2Available'] +
      + + +
      $cfg['GridEditing'] +
      + + +
      $cfg['GZipDump'] +
      + + +
      $cfg['HeaderFlipType'] +
      + + +
      $cfg['HideStructureActions'] +
      + + +
      $cfg['IconvExtraParams'] +
      + + +
      $cfg['IgnoreMultiSubmitErrors'] +
      + + +
      $cfg['Import'] +
      + + +
      $cfg['Import']['charset'] +
      + + +
      $cfg['InitialSlidersState'] +
      + + +
      $cfg['InsertRows'] +
      + + +
      $cfg['Lang'] +
      + + +
      $cfg['LimitChars'] +
      + + +
      $cfg['LinkLengthLimit'] +
      + + +
      $cfg['LoginCookieDeleteAll'] +
      + + +
      $cfg['LoginCookieRecall'] +
      + + +
      $cfg['LoginCookieStore'] +
      + + +
      $cfg['LoginCookieValidity'], [1] +
      + + +
      $cfg['LongtextDoubleTextarea'] +
      + + +
      $cfg['MainBackground'] +
      + + +
      $cfg['MaxCharactersInDisplayedSQL'] +
      + + +
      $cfg['MaxDbList'] +
      + + +
      $cfg['MaxExactCount'], [1] +
      + + +
      $cfg['MaxExactCountViews'] +
      + + +
      $cfg['MaxNavigationItems'] +
      + + +
      $cfg['MaxRows'] +
      + + +
      $cfg['MaxSizeForInputField'] +
      + + +
      $cfg['MaxTableList'] +
      + + +
      $cfg['McryptDisableWarning'] +
      + + +
      $cfg['MemoryLimit'] +
      + + +
      $cfg['MinSizeForInputField'] +
      + + +
      $cfg['NaturalOrder'] +
      + + +
      $cfg['NaviBackground'] +
      + + +
      $cfg['NavigationDisplayLogo'] +
      + + +
      $cfg['NavigationDisplayServers'] +
      + + +
      $cfg['NavigationLogoLink'] +
      + + +
      $cfg['NavigationLogoLinkWindow'] +
      + + +
      $cfg['NavigationTreeDbSeparator'], [1] +
      + + +
      $cfg['NavigationTreeDefaultTabTable'], [1] +
      + + +
      $cfg['NavigationTreeDisplayDbFilterMinimum'] +
      + + +
      $cfg['NavigationTreeDisplayItemFilterMinimum'] +
      + + +
      $cfg['NavigationTreeEnableGrouping'] +
      + + +
      $cfg['NavigationTreePointerEnable'] +
      + + +
      $cfg['NavigationTreeTableLevel'] +
      + + +
      $cfg['NavigationTreeTableSeparator'], [1] +
      + + +
      $cfg['NaviPointerBackground'] +
      + + +
      $cfg['NaviPointerColor'] +
      + + +
      $cfg['NaviWidth'] +
      + + +
      $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] +
      + + +
      $cfg['PmaNoRelation_DisableWarning'] +
      + + +
      $cfg['PropertiesNumColumns'] +
      + + +
      $cfg['ProtectBinary'] +
      + + +
      $cfg['ProxyPass'] +
      + + +
      $cfg['ProxyUrl'] +
      + + +
      $cfg['ProxyUser'] +
      + + +
      $cfg['QueryHistoryDB'], [1], [2] +
      + + +
      $cfg['QueryHistoryMax'], [1], [2] +
      + + +
      $cfg['QueryWindowDefTab'], [1] +
      + + +
      $cfg['QueryWindowHeight'], [1] +
      + + +
      $cfg['QueryWindowWidth'], [1] +
      + + +
      $cfg['RecodingEngine'] +
      + + +
      $cfg['RememberSorting'], [1] +
      + +
      + +
      $cfg['RepeatCells'] +
      + + +
      $cfg['ReservedWordDisableWarning'] +
      + + +
      $cfg['RetainQueryBox'] +
      + + +
      $cfg['RowActionLinks'] +
      + + +
      $cfg['SaveCellsAtOnce'] +
      + + +
      $cfg['SaveDir'] +
      + + +
      $cfg['SendErrorReports'] +
      + + +
      $cfg['ServerDefault'], [1], [2] +
      + + +
      $cfg['ServerLibraryDifference_DisableWarning'] +
      + + +
      $cfg['Servers'], [1], [2], [3] +
      + + +
      $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'] +
      + + +
      $cfg['Servers'][$i]['auth_http_realm'] +
      + + +
      $cfg['Servers'][$i]['auth_swekey_config'], [1] +
      + + +
      $cfg['Servers'][$i]['auth_type'], [1], [2] +
      + + +
      $cfg['Servers'][$i]['bookmarktable'], [1] +
      + + +
      $cfg['Servers'][$i]['column_comments'] +
      + + +
      $cfg['Servers'][$i]['column_info'], [1], [2] +
      + + +
      $cfg['Servers'][$i]['compress'] +
      + + +
      $cfg['Servers'][$i]['connect_type'] +
      + + +
      $cfg['Servers'][$i]['controlhost'] +
      + + +
      $cfg['Servers'][$i]['controlpass'], [1], [2] +
      + + +
      $cfg['Servers'][$i]['controlport'] +
      + + +
      $cfg['Servers'][$i]['controluser'], [1], [2], [3] +
      + + +
      $cfg['Servers'][$i]['designer_coords'], [1] +
      + + +
      $cfg['Servers'][$i]['extension'], [1], [2], [3], [4] +
      + + +
      $cfg['Servers'][$i]['hide_db'] +
      + + +
      $cfg['Servers'][$i]['history'], [1], [2] +
      + + +
      $cfg['Servers'][$i]['host'], [1], [2], [3], [4] +
      + + +
      $cfg['Servers'][$i]['LogoutURL'] +
      + + +
      $cfg['Servers'][$i]['MaxTableUiprefs'] +
      + + +
      $cfg['Servers'][$i]['navigationhiding'], [1] +
      + + +
      $cfg['Servers'][$i]['nopassword'] +
      + + +
      $cfg['Servers'][$i]['only_db'], [1] +
      + + +
      $cfg['Servers'][$i]['password'], [1] +
      + + +
      $cfg['Servers'][$i]['pdf_pages'], [1] +
      + + +
      $cfg['Servers'][$i]['pmadb'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17] +
      + + +
      $cfg['Servers'][$i]['port'] +
      + + +
      $cfg['Servers'][$i]['recent'], [1] +
      + + +
      $cfg['Servers'][$i]['relation'], [1] +
      + + +
      $cfg['Servers'][$i]['SignonScript'], [1], [2], [3] +
      + + +
      $cfg['Servers'][$i]['SignonSession'], [1], [2] +
      + + +
      $cfg['Servers'][$i]['SignonURL'], [1], [2] +
      + + +
      $cfg['Servers'][$i]['socket'], [1] +
      + + +
      $cfg['Servers'][$i]['ssl'] +
      + + +
      $cfg['Servers'][$i]['ssl_ca'] +
      + + +
      $cfg['Servers'][$i]['ssl_ca_path'] +
      + + +
      $cfg['Servers'][$i]['ssl_cert'] +
      + + +
      $cfg['Servers'][$i]['ssl_ciphers'] +
      + + +
      $cfg['Servers'][$i]['ssl_key'] +
      + + +
      $cfg['Servers'][$i]['StatusCacheDatabases'], [1] +
      + + +
      $cfg['Servers'][$i]['StatusCacheLifetime'], [1] +
      + + +
      $cfg['Servers'][$i]['table_coords'], [1] +
      + + +
      $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] +
      + + +
      $cfg['Servers'][$i]['users'], [1] +
      + + +
      $cfg['Servers'][$i]['verbose'], [1], [2], [3] +
      + + +
      $cfg['SessionSavePath'] +
      + + +
      $cfg['ShowAll'] +
      + + +
      $cfg['ShowBrowseComments'] +
      + + +
      $cfg['ShowChgPassword'] +
      + + +
      $cfg['ShowCreateDb'] +
      + + +
      $cfg['ShowDbStructureCreation'] +
      + + +
      $cfg['ShowDbStructureLastCheck'] +
      + + +
      $cfg['ShowDbStructureLastUpdate'] +
      + + +
      $cfg['ShowDisplayDirection'] +
      + + +
      $cfg['ShowFieldTypesInDataEditView'] +
      + + +
      $cfg['ShowFunctionFields'] +
      + + +
      $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['SQLQuery']['Validate'] +
      + + +
      $cfg['SQLValidator'], [1] +
      + + +
      $cfg['SQLValidator']['password'] +
      + + +
      $cfg['SQLValidator']['use'] +
      + + +
      $cfg['SQLValidator']['username'] +
      + + +
      $cfg['SuhosinDisableWarning'], [1] +
      + + +
      $cfg['TableNavigationLinksMode'] +
      + + +
      $cfg['TabsMode'] +
      + + +
      $cfg['TempDir'], [1], [2] +
      + + +
      $cfg['TextareaAutoSelect'] +
      + + +
      $cfg['TextareaCols'] +
      + + +
      $cfg['TextareaRows'] +
      + + +
      $cfg['ThBackground'] +
      + + +
      $cfg['ThColor'] +
      + + +
      $cfg['ThemeDefault'], [1] +
      + + +
      $cfg['ThemeManager'], [1], [2] +
      + + +
      $cfg['ThemePath'], [1], [2], [3], [4] +
      + + +
      $cfg['ThemePerServer'] +
      + + +
      $cfg['TitleDatabase'] +
      + + +
      $cfg['TitleDefault'] +
      + + +
      $cfg['TitleServer'] +
      + + +
      $cfg['TitleTable'] +
      + + +
      $cfg['TranslationWarningThreshold'] +
      + + +
      $cfg['TrustedProxies'], [1] +
      + + +
      $cfg['UploadDir'], [1], [2] +
      + + +
      $cfg['UseDbSearch'] +
      + + +
      $cfg['UserprefsDeveloperTab'] +
      + + +
      $cfg['UserprefsDisallow'], [1] +
      + + +
      $cfg['VersionCheck'] +
      + + +
      $cfg['ZipDump'] +
      + + +
      .htaccess +
      + +
      + +

      A

      + + + +
      + +
      ACL +
      + + +
      ActionLinksMode +
      + + +
      AllowArbitraryServer +
      + + +
      AllowDeny, order +
      + + +
      AllowDeny, rules +
      + + +
      AllowNoPassword +
      + + +
      AllowRoot +
      + +
      + +
      AllowThirdPartyFraming +
      + + +
      AllowUserDropDatabase +
      + + +
      auth_http_realm +
      + + +
      auth_swekey_config +
      + + +
      auth_type +
      + + +
      Authentication mode +
      + +
      + +
      Config +
      + + +
      Cookie +
      + + +
      HTTP +
      + + +
      Signon +
      + + +
      Swekey +
      + +
      + +
      AvailableCharsets +
      + +
      + +

      B

      + + + +
      + +
      BgOne +
      + + +
      BgTwo +
      + + +
      Blowfish +
      + + +
      blowfish_secret +
      + + +
      bookmarktable +
      + + +
      Border +
      + + +
      BrowseMarkerBackground +
      + + +
      BrowseMarkerColor +
      + +
      + +
      BrowseMarkerEnable +
      + + +
      BrowseMIME +
      + + +
      BrowsePointerBackground +
      + + +
      BrowsePointerColor +
      + + +
      BrowsePointerEnable +
      + + +
      Browser +
      + + +
      bzip2 +
      + + +
      BZipDump +
      + +
      + +

      C

      + + + +
      + +
      CaptchaLoginPrivateKey +
      + + +
      CaptchaLoginPublicKey +
      + + +
      CGI +
      + + +
      Changelog +
      + + +
      CharEditing +
      + + +
      CharTextareaCols +
      + + +
      CharTextareaRows +
      + + +
      CheckConfigurationPermissions +
      + + +
      Client +
      + + +
      CodemirrorEnable +
      + + +
      column +
      + + +
      column_info +
      + + +
      compress +
      + + +
      CompressOnFly +
      + +
      + +
      + Config +
      + +
      + +
      Authentication mode +
      + +
      + +
      config.inc.php +
      + + +
      + configuration option +
      + +
      + +
      $cfg['ActionLinksMode'] +
      + + +
      $cfg['AllowArbitraryServer'], [1], [2] +
      + + +
      $cfg['AllowThirdPartyFraming'] +
      + + +
      $cfg['AllowUserDropDatabase'] +
      + + +
      $cfg['AvailableCharsets'] +
      + + +
      $cfg['BZipDump'] +
      + + +
      $cfg['BgOne'] +
      + + +
      $cfg['BgTwo'] +
      + + +
      $cfg['Border'] +
      + + +
      $cfg['BrowseMIME'] +
      + + +
      $cfg['BrowseMarkerBackground'] +
      + + +
      $cfg['BrowseMarkerColor'] +
      + + +
      $cfg['BrowseMarkerEnable'] +
      + + +
      $cfg['BrowsePointerBackground'] +
      + + +
      $cfg['BrowsePointerColor'] +
      + + +
      $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['DBG'] +
      + + +
      $cfg['DBG']['demo'] +
      + + +
      $cfg['DBG']['sql'] +
      + + +
      $cfg['DefaultConnectionCollation'] +
      + + +
      $cfg['DefaultDisplay'] +
      + + +
      $cfg['DefaultFunctions'] +
      + + +
      $cfg['DefaultLang'] +
      + + +
      $cfg['DefaultQueryDatabase'] +
      + + +
      $cfg['DefaultQueryTable'] +
      + + +
      $cfg['DefaultTabDatabase'] +
      + + +
      $cfg['DefaultTabServer'] +
      + + +
      $cfg['DefaultTabTable'] +
      + + +
      $cfg['DisableMultiTableMaintenance'] +
      + + +
      $cfg['DisplayBinaryAsHex'] +
      + + +
      $cfg['DisplayServersList'] +
      + + +
      $cfg['EditInWindow'], [1] +
      + + +
      $cfg['Error_Handler']['display'] +
      + + +
      $cfg['ExecTimeLimit'], [1] +
      + + +
      $cfg['Export'] +
      + + +
      $cfg['Export']['charset'] +
      + + +
      $cfg['Export']['method'] +
      + + +
      $cfg['FilterLanguages'] +
      + + +
      $cfg['FontFamily'] +
      + + +
      $cfg['FontFamilyFixed'] +
      + + +
      $cfg['ForceSSL'] +
      + + +
      $cfg['ForeignKeyDropdownOrder'], [1] +
      + + +
      $cfg['ForeignKeyMaxLimit'], [1] +
      + + +
      $cfg['GD2Available'] +
      + + +
      $cfg['GZipDump'] +
      + + +
      $cfg['GridEditing'] +
      + + +
      $cfg['HeaderFlipType'] +
      + + +
      $cfg['HideStructureActions'] +
      + + +
      $cfg['IconvExtraParams'] +
      + + +
      $cfg['IgnoreMultiSubmitErrors'] +
      + + +
      $cfg['Import'] +
      + + +
      $cfg['Import']['charset'] +
      + + +
      $cfg['InitialSlidersState'] +
      + + +
      $cfg['InsertRows'] +
      + + +
      $cfg['Lang'] +
      + + +
      $cfg['LimitChars'] +
      + + +
      $cfg['LinkLengthLimit'] +
      + + +
      $cfg['LoginCookieDeleteAll'] +
      + + +
      $cfg['LoginCookieRecall'] +
      + + +
      $cfg['LoginCookieStore'] +
      + + +
      $cfg['LoginCookieValidity'], [1] +
      + + +
      $cfg['LongtextDoubleTextarea'] +
      + + +
      $cfg['MainBackground'] +
      + + +
      $cfg['MaxCharactersInDisplayedSQL'] +
      + + +
      $cfg['MaxDbList'] +
      + + +
      $cfg['MaxExactCount'], [1] +
      + + +
      $cfg['MaxExactCountViews'] +
      + + +
      $cfg['MaxNavigationItems'] +
      + + +
      $cfg['MaxRows'] +
      + + +
      $cfg['MaxSizeForInputField'] +
      + + +
      $cfg['MaxTableList'] +
      + + +
      $cfg['McryptDisableWarning'] +
      + + +
      $cfg['MemoryLimit'] +
      + + +
      $cfg['MinSizeForInputField'] +
      + + +
      $cfg['NaturalOrder'] +
      + + +
      $cfg['NaviBackground'] +
      + + +
      $cfg['NaviPointerBackground'] +
      + + +
      $cfg['NaviPointerColor'] +
      + + +
      $cfg['NaviWidth'] +
      + + +
      $cfg['NavigationDisplayLogo'] +
      + + +
      $cfg['NavigationDisplayServers'] +
      + + +
      $cfg['NavigationLogoLink'] +
      + + +
      $cfg['NavigationLogoLinkWindow'] +
      + + +
      $cfg['NavigationTreeDbSeparator'], [1] +
      + + +
      $cfg['NavigationTreeDefaultTabTable'], [1] +
      + + +
      $cfg['NavigationTreeDisplayDbFilterMinimum'] +
      + + +
      $cfg['NavigationTreeDisplayItemFilterMinimum'] +
      + + +
      $cfg['NavigationTreeEnableGrouping'] +
      + + +
      $cfg['NavigationTreePointerEnable'] +
      + + +
      $cfg['NavigationTreeTableLevel'] +
      + + +
      $cfg['NavigationTreeTableSeparator'], [1] +
      + + +
      $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] +
      + + +
      $cfg['PmaNoRelation_DisableWarning'] +
      + + +
      $cfg['PropertiesNumColumns'] +
      + + +
      $cfg['ProtectBinary'] +
      + + +
      $cfg['ProxyPass'] +
      + + +
      $cfg['ProxyUrl'] +
      + + +
      $cfg['ProxyUser'] +
      + + +
      $cfg['QueryHistoryDB'], [1], [2] +
      + + +
      $cfg['QueryHistoryMax'], [1], [2] +
      + + +
      $cfg['QueryWindowDefTab'], [1] +
      + + +
      $cfg['QueryWindowHeight'], [1] +
      + + +
      $cfg['QueryWindowWidth'], [1] +
      + + +
      $cfg['RecodingEngine'] +
      + + +
      $cfg['RememberSorting'], [1] +
      + + +
      $cfg['RepeatCells'] +
      + + +
      $cfg['ReservedWordDisableWarning'] +
      + + +
      $cfg['RetainQueryBox'] +
      + + +
      $cfg['RowActionLinks'] +
      + + +
      $cfg['SQLQuery']['Edit'] +
      + + +
      $cfg['SQLQuery']['Explain'] +
      + + +
      $cfg['SQLQuery']['Refresh'] +
      + + +
      $cfg['SQLQuery']['ShowAsPHP'] +
      + + +
      $cfg['SQLQuery']['Validate'] +
      + + +
      $cfg['SQLValidator'], [1] +
      + + +
      $cfg['SQLValidator']['password'] +
      + + +
      $cfg['SQLValidator']['use'] +
      + + +
      $cfg['SQLValidator']['username'] +
      + + +
      $cfg['SaveCellsAtOnce'] +
      + + +
      $cfg['SaveDir'] +
      + + +
      $cfg['SendErrorReports'] +
      + + +
      $cfg['ServerDefault'], [1], [2] +
      + + +
      $cfg['ServerLibraryDifference_DisableWarning'] +
      + + +
      $cfg['Servers'], [1], [2], [3] +
      + + +
      $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'] +
      + + +
      $cfg['Servers'][$i]['LogoutURL'] +
      + + +
      $cfg['Servers'][$i]['MaxTableUiprefs'] +
      + + +
      $cfg['Servers'][$i]['SignonScript'], [1], [2], [3] +
      + + +
      $cfg['Servers'][$i]['SignonSession'], [1], [2] +
      + + +
      $cfg['Servers'][$i]['SignonURL'], [1], [2] +
      + + +
      $cfg['Servers'][$i]['StatusCacheDatabases'], [1] +
      + + +
      $cfg['Servers'][$i]['StatusCacheLifetime'], [1] +
      + + +
      $cfg['Servers'][$i]['auth_http_realm'] +
      + + +
      $cfg['Servers'][$i]['auth_swekey_config'], [1] +
      + + +
      $cfg['Servers'][$i]['auth_type'], [1], [2] +
      + + +
      $cfg['Servers'][$i]['bookmarktable'], [1] +
      + + +
      $cfg['Servers'][$i]['column_comments'] +
      + + +
      $cfg['Servers'][$i]['column_info'], [1], [2] +
      + + +
      $cfg['Servers'][$i]['compress'] +
      + + +
      $cfg['Servers'][$i]['connect_type'] +
      + + +
      $cfg['Servers'][$i]['controlhost'] +
      + + +
      $cfg['Servers'][$i]['controlpass'], [1], [2] +
      + + +
      $cfg['Servers'][$i]['controlport'] +
      + + +
      $cfg['Servers'][$i]['controluser'], [1], [2], [3] +
      + + +
      $cfg['Servers'][$i]['designer_coords'], [1] +
      + + +
      $cfg['Servers'][$i]['extension'], [1], [2], [3], [4] +
      + + +
      $cfg['Servers'][$i]['hide_db'] +
      + + +
      $cfg['Servers'][$i]['history'], [1], [2] +
      + + +
      $cfg['Servers'][$i]['host'], [1], [2], [3], [4] +
      + + +
      $cfg['Servers'][$i]['navigationhiding'], [1] +
      + + +
      $cfg['Servers'][$i]['nopassword'] +
      + + +
      $cfg['Servers'][$i]['only_db'], [1] +
      + + +
      $cfg['Servers'][$i]['password'], [1] +
      + + +
      $cfg['Servers'][$i]['pdf_pages'], [1] +
      + + +
      $cfg['Servers'][$i]['pmadb'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17] +
      + + +
      $cfg['Servers'][$i]['port'] +
      + + +
      $cfg['Servers'][$i]['recent'], [1] +
      + + +
      $cfg['Servers'][$i]['relation'], [1] +
      + + +
      $cfg['Servers'][$i]['socket'], [1] +
      + + +
      $cfg['Servers'][$i]['ssl'] +
      + + +
      $cfg['Servers'][$i]['ssl_ca'] +
      + + +
      $cfg['Servers'][$i]['ssl_ca_path'] +
      + + +
      $cfg['Servers'][$i]['ssl_cert'] +
      + + +
      $cfg['Servers'][$i]['ssl_ciphers'] +
      + + +
      $cfg['Servers'][$i]['ssl_key'] +
      + + +
      $cfg['Servers'][$i]['table_coords'], [1] +
      + + +
      $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] +
      + + +
      $cfg['Servers'][$i]['users'], [1] +
      + + +
      $cfg['Servers'][$i]['verbose'], [1], [2], [3] +
      + + +
      $cfg['SessionSavePath'] +
      + + +
      $cfg['ShowAll'] +
      + + +
      $cfg['ShowBrowseComments'] +
      + + +
      $cfg['ShowChgPassword'] +
      + + +
      $cfg['ShowCreateDb'] +
      + + +
      $cfg['ShowDbStructureCreation'] +
      + + +
      $cfg['ShowDbStructureLastCheck'] +
      + + +
      $cfg['ShowDbStructureLastUpdate'] +
      + + +
      $cfg['ShowDisplayDirection'] +
      + + +
      $cfg['ShowFieldTypesInDataEditView'] +
      + + +
      $cfg['ShowFunctionFields'] +
      + + +
      $cfg['ShowHint'] +
      + + +
      $cfg['ShowPhpInfo'] +
      + + +
      $cfg['ShowPropertyComments'] +
      + + +
      $cfg['ShowSQL'] +
      + + +
      $cfg['ShowServerInfo'] +
      + + +
      $cfg['ShowStats'] +
      + + +
      $cfg['SkipLockedTables'] +
      + + +
      $cfg['SuhosinDisableWarning'], [1] +
      + + +
      $cfg['TableNavigationLinksMode'] +
      + + +
      $cfg['TabsMode'] +
      + + +
      $cfg['TempDir'], [1], [2] +
      + + +
      $cfg['TextareaAutoSelect'] +
      + + +
      $cfg['TextareaCols'] +
      + + +
      $cfg['TextareaRows'] +
      + + +
      $cfg['ThBackground'] +
      + + +
      $cfg['ThColor'] +
      + + +
      $cfg['ThemeDefault'], [1] +
      + + +
      $cfg['ThemeManager'], [1], [2] +
      + + +
      $cfg['ThemePath'], [1], [2], [3], [4] +
      + + +
      $cfg['ThemePerServer'] +
      + + +
      $cfg['TitleDatabase'] +
      + + +
      $cfg['TitleDefault'] +
      + + +
      $cfg['TitleServer'] +
      + + +
      $cfg['TitleTable'] +
      + + +
      $cfg['TranslationWarningThreshold'] +
      + + +
      $cfg['TrustedProxies'], [1] +
      + + +
      $cfg['UploadDir'], [1], [2] +
      + + +
      $cfg['UseDbSearch'] +
      + + +
      $cfg['UserprefsDeveloperTab'] +
      + + +
      $cfg['UserprefsDisallow'], [1] +
      + + +
      $cfg['VersionCheck'] +
      + + +
      $cfg['ZipDump'] +
      + + +
      $cfg['blowfish_secret'], [1] +
      + +
      + +
      Configuration storage +
      + + +
      Confirm +
      + + +
      connect_type +
      + + +
      controlhost +
      + + +
      controlpass +
      + + +
      controlport +
      + + +
      controluser +
      + + +
      Cookie +
      + +
      + +
      Authentication mode +
      + +
      + +
      CSPAllow +
      + + +
      CSV +
      + +
      + +

      D

      + + + +
      + +
      database +
      + + +
      DB +
      + + +
      DBG +
      + + +
      DBG, demo +
      + + +
      DBG, sql +
      + + +
      DefaultConnectionCollation +
      + + +
      DefaultDisplay +
      + + +
      DefaultFunctions +
      + + +
      DefaultLang +
      + +
      + +
      DefaultQueryDatabase +
      + + +
      DefaultQueryTable +
      + + +
      DefaultTabDatabase +
      + + +
      DefaultTabServer +
      + + +
      DefaultTabTable +
      + + +
      designer_coords +
      + + +
      DisableMultiTableMaintenance +
      + + +
      DisplayBinaryAsHex +
      + + +
      DisplayServersList +
      + +
      + +

      E

      + + + +
      + +
      EditInWindow +
      + + +
      Engine +
      + + +
      Error_Handler, display +
      + + +
      ExecTimeLimit +
      + +
      + +
      Export +
      + + +
      Export, method +
      + + +
      extension, [1] +
      + +
      + +

      F

      + + + +
      + +
      FAQ +
      + + +
      Field +
      + + +
      FilterLanguages +
      + + +
      FontFamily +
      + + +
      FontFamilyFixed +
      + +
      + +
      ForceSSL +
      + + +
      foreign key +
      + + +
      ForeignKeyDropdownOrder +
      + + +
      ForeignKeyMaxLimit +
      + + +
      FPDF +
      + +
      + +

      G

      + + + +
      + +
      GD +
      + + +
      GD2 +
      + + +
      GD2Available +
      + +
      + +
      GridEditing +
      + + +
      gzip +
      + + +
      GZipDump +
      + +
      + +

      H

      + + + +
      + +
      HeaderFlipType +
      + + +
      hide_db +
      + + +
      HideStructureActions +
      + + +
      history +
      + +
      + +
      host, [1] +
      + + +
      hostname +
      + + +
      HTTP +
      + +
      + +
      Authentication mode +
      + +
      + +
      https +
      + +
      + +

      I

      + + + +
      + +
      IconvExtraParams +
      + + +
      IEC +
      + + +
      IgnoreMultiSubmitErrors +
      + + +
      IIS +
      + + +
      Import +
      + + +
      Index +
      + + +
      InitialSlidersState +
      + +
      + +
      InsertRows +
      + + +
      IP +
      + + +
      IP Address +
      + + +
      IPv6 +
      + + +
      ISAPI +
      + + +
      ISO +
      + + +
      ISP +
      + +
      + +

      J

      + + + +
      + +
      JPEG +
      + +
      + +
      JPG +
      + +
      + +

      K

      + + +
      + +
      Key +
      + +
      + +

      L

      + + + +
      + +
      Lang +
      + + +
      LATEX +
      + + +
      LimitChars +
      + + +
      LinkLengthLimit +
      + + +
      LoginCookieDeleteAll +
      + +
      + +
      LoginCookieRecall +
      + + +
      LoginCookieStore +
      + + +
      LoginCookieValidity +
      + + +
      LogoutURL +
      + + +
      LongtextDoubleTextarea +
      + +
      + +

      M

      + + + +
      + +
      Mac +
      + + +
      Mac OS X +
      + + +
      MainBackground +
      + + +
      MaxCharactersInDisplayedSQL +
      + + +
      MaxDbList +
      + + +
      MaxExactCount +
      + + +
      MaxExactCountViews +
      + + +
      MaxNavigationItems +
      + + +
      MaxRows +
      + + +
      MaxSizeForInputField +
      + + +
      MaxTableList +
      + +
      + +
      MaxTableUiprefs +
      + + +
      MCrypt +
      + + +
      mcrypt +
      + + +
      McryptDisableWarning +
      + + +
      MemoryLimit +
      + + +
      MIME +
      + + +
      MinSizeForInputField +
      + + +
      module +
      + + +
      MySQL +
      + + +
      mysql +
      + + +
      mysqli +
      + +
      + +

      N

      + + + +
      + +
      NaturalOrder +
      + + +
      NaviBackground +
      + + +
      NavigationDisplayLogo +
      + + +
      NavigationDisplayServers +
      + + +
      navigationhiding +
      + + +
      NavigationLogoLink +
      + + +
      NavigationLogoLinkWindow +
      + + +
      NavigationTreeDbSeparator +
      + + +
      NavigationTreeDefaultTabTable +
      + + +
      NavigationTreeDisplayDbFilterMinimum +
      + +
      + +
      NavigationTreeDisplayItemFilterMinimum +
      + + +
      NavigationTreeEnableGrouping +
      + + +
      NavigationTreePointerEnable +
      + + +
      NavigationTreeTableLevel +
      + + +
      NavigationTreeTableSeparator +
      + + +
      NaviPointerBackground +
      + + +
      NaviPointerColor +
      + + +
      NaviWidth +
      + + +
      nopassword +
      + + +
      NumRecentTables +
      + +
      + +

      O

      + + + +
      + +
      OBGzip +
      + + +
      only_db +
      + + +
      OpenDocument +
      + +
      + +
      Order +
      + + +
      OS X +
      + +
      + +

      P

      + + + +
      + +
      password +
      + + +
      PCRE +
      + + +
      PDF +
      + + +
      pdf_pages +
      + + +
      PDFDefaultPageSize +
      + + +
      PDFPageSizes +
      + + +
      PEAR +
      + + +
      PersistentConnections +
      + + +
      PHP +
      + + +
      phpMyAdmin configuration storage +
      + +
      + +
      PmaAbsoluteUri +
      + + +
      pmadb, [1] +
      + + +
      PmaNoRelation_DisableWarning +
      + + +
      port, [1] +
      + + +
      PropertiesNumColumns +
      + + +
      ProtectBinary +
      + + +
      ProxyPass +
      + + +
      ProxyUrl +
      + + +
      ProxyUser +
      + +
      + +

      Q

      + + + +
      + +
      QueryHistoryDB +
      + + +
      QueryHistoryMax +
      + + +
      QueryWindowDefTab +
      + +
      + +
      QueryWindowHeight +
      + + +
      QueryWindowWidth +
      + +
      + +

      R

      + + + +
      + +
      recent +
      + + +
      RecodingEngine +
      + + +
      relation +
      + + +
      RememberSorting +
      + + +
      RepeatCells +
      + + +
      ReservedWordDisableWarning +
      + +
      + +
      RetainQueryBox +
      + + +
      RFC +
      + +
      + +
      RFC 1867 +
      + + +
      RFC 1952 +
      + + +
      RFC 2616 +
      + +
      + +
      RFC 1952 +
      + + +
      Row (record, tuple) +
      + + +
      RowActionLinks +
      + +
      + +

      S

      + + + +
      + +
      SaveCellsAtOnce +
      + + +
      SaveDir +
      + + +
      SendErrorReports +
      + + +
      Server +
      + + +
      + server configuration +
      + +
      + +
      AllowDeny, order +
      + + +
      AllowDeny, rules +
      + + +
      AllowNoPassword +
      + + +
      AllowRoot +
      + + +
      LogoutURL +
      + + +
      MaxTableUiprefs +
      + + +
      SignonScript +
      + + +
      SignonSession +
      + + +
      SignonURL +
      + + +
      StatusCacheDatabases +
      + + +
      StatusCacheLifetime +
      + + +
      auth_http_realm +
      + + +
      auth_swekey_config +
      + + +
      auth_type +
      + + +
      bookmarktable +
      + + +
      column_info +
      + + +
      compress +
      + + +
      connect_type +
      + + +
      controlhost +
      + + +
      controlpass +
      + + +
      controlport +
      + + +
      controluser +
      + + +
      designer_coords +
      + + +
      extension +
      + + +
      hide_db +
      + + +
      history +
      + + +
      host +
      + + +
      navigationhiding +
      + + +
      nopassword +
      + + +
      only_db +
      + + +
      password +
      + + +
      pdf_pages +
      + + +
      pmadb +
      + + +
      port +
      + + +
      recent +
      + + +
      relation +
      + + +
      socket +
      + + +
      ssl +
      + + +
      ssl_ca +
      + + +
      ssl_ca_path +
      + + +
      ssl_cert +
      + + +
      ssl_ciphers +
      + + +
      ssl_key +
      + + +
      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 +
      + + +
      Setup script +
      + + +
      ShowAll +
      + + +
      ShowBrowseComments +
      + + +
      ShowChgPassword +
      + + +
      ShowCreateDb +
      + + +
      ShowDbStructureCreation +
      + + +
      ShowDbStructureLastCheck +
      + + +
      ShowDbStructureLastUpdate +
      + + +
      ShowDisplayDirection +
      + + +
      ShowFieldTypesInDataEditView +
      + + +
      ShowFunctionFields +
      + + +
      ShowHint +
      + + +
      ShowPhpInfo +
      + + +
      ShowPropertyComments +
      + + +
      ShowServerInfo +
      + + +
      ShowSQL +
      + + +
      ShowStats +
      + + +
      + Signon +
      + +
      + +
      Authentication mode +
      + +
      + +
      SignonScript +
      + +
      + +
      SignonSession +
      + + +
      SignonURL +
      + + +
      SkipLockedTables +
      + + +
      SOAP +
      + + +
      socket, [1] +
      + + +
      SQL +
      + + +
      SQLQuery, Edit +
      + + +
      SQLQuery, Explain +
      + + +
      SQLQuery, Refresh +
      + + +
      SQLQuery, ShowAsPHP +
      + + +
      SQLQuery, Validate +
      + + +
      SQLValidator +
      + + +
      SQLValidator, password +
      + + +
      SQLValidator, use +
      + + +
      SQLValidator, username +
      + + +
      SSL +
      + + +
      ssl +
      + + +
      ssl_ca +
      + + +
      ssl_ca_path +
      + + +
      ssl_cert +
      + + +
      ssl_ciphers +
      + + +
      ssl_key +
      + + +
      StatusCacheDatabases +
      + + +
      StatusCacheLifetime +
      + + +
      Storage Engines +
      + + +
      Stored procedure +
      + + +
      SuhosinDisableWarning +
      + + +
      + Swekey +
      + +
      + +
      Authentication mode +
      + +
      +
      + +

      T

      + + + +
      + +
      table +
      + + +
      table_coords +
      + + +
      table_info +
      + + +
      table_uiprefs +
      + + +
      TableNavigationLinksMode +
      + + +
      TabsMode +
      + + +
      tar +
      + + +
      TCP +
      + + +
      TCPDF +
      + + +
      TempDir +
      + + +
      TextareaAutoSelect +
      + + +
      TextareaCols +
      + + +
      TextareaRows +
      + + +
      ThBackground +
      + + +
      ThColor +
      + + +
      ThemeDefault +
      + +
      + +
      ThemeManager +
      + + +
      ThemePath +
      + + +
      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 +
      + +
      + +

      U

      + + + +
      + +
      UFPDF +
      + + +
      UploadDir +
      + + +
      URL +
      + + +
      UseDbSearch +
      + + +
      user +
      + +
      + +
      userconfig +
      + + +
      usergroups +
      + + +
      UserprefsDeveloperTab +
      + + +
      UserprefsDisallow +
      + + +
      users +
      + +
      + +

      V

      + + + +
      + +
      verbose +
      + +
      + +
      VersionCheck +
      + +
      + +

      W

      + + +
      + +
      Webserver +
      + +
      + +

      X

      + + +
      + +
      XML +
      + +
      + +

      Z

      + + + +
      + +
      ZIP +
      + + +
      ZipDump +
      + +
      + +
      zlib +
      + +
      + + + +
      +
      +
      +
      +
      + + + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/glossary.html b/sources/doc/html/glossary.html new file mode 100644 index 0000000..73ed01a --- /dev/null +++ b/sources/doc/html/glossary.html @@ -0,0 +1,627 @@ + + + + + + + + + + Glossary — phpMyAdmin 4.1.4 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.

      + +
      +
      FPDF
      +

      the free PDF library

      +
      +

      See also

      +

      <http://www.fpdf.org/>

      +
      +
      +
      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:: <http://www.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.

      + +
      +
      MCrypt
      +

      a cryptographic library.

      + +
      +
      mcrypt
      +

      the MCrypt PHP extension.

      +
      +

      See also

      +

      <http://php.net/mcrypt>

      +
      +
      +
      MIME
      +

      Multipurpose Internet Mail Extensions is +an Internet Standard for the format of e-mail.

      + +
      +
      module
      +

      some sort of extension for the Apache Webserver.

      + +
      +
      MySQL
      +

      a multithreaded, multi-user, SQL (Structured Query Language) Database Management System (DBMS).

      + +
      +
      mysqli
      +

      the improved MySQL client PHP extension.

      +
      +

      See also

      +

      <http://php.net/mysqli>

      +
      +
      +
      mysql
      +

      the MySQL client PHP extension.

      +
      +

      See also

      +

      <http://php.net/mysql>

      +
      +
      +
      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

      +

      <http://pear.php.net/>

      +
      +
      +
      PCRE
      +

      Perl Compatible Regular Expressions is the perl-compatible regular +expression functions for PHP

      +
      +

      See also

      +

      <http://php.net/pcre>

      +
      +
      +
      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.

      + +
      +
      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
      +

      handlers for different table types

      + +
      +
      SOAP
      +

      Simple Object Access Protocol is a protocol specification for exchanging +structured information in the implementation of Web Services in computer +networks.

      + +
      +
      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
      +

      Rewrite of UFPDF with various improvements.

      +
      +

      See also

      +

      <http://www.tcpdf.org/>

      +
      +
      +
      trigger
      +

      a procedural code that is automatically executed in response to certain events on a particular table or view in a database

      + +
      +
      UFPDF
      +

      Unicode/UTF-8 extension for FPDF

      + +
      +
      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.

      + +
      +
      +
      + + +
      +
      +
      +
      +
      +

      Previous topic

      +

      Credits

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/index.html b/sources/doc/html/index.html new file mode 100644 index 0000000..acc441d --- /dev/null +++ b/sources/doc/html/index.html @@ -0,0 +1,207 @@ + + + + + + + + + + Welcome to phpMyAdmin’s documentation! — phpMyAdmin 4.1.4 documentation + + + + + + + + + + + + + + + +
      +
      +
      +
      + + +
      +

      Indices and tables

      + +
      + + +
      +
      +
      +
      +
      +

      Table Of Contents

      + + +

      Next topic

      +

      Introduction

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/intro.html b/sources/doc/html/intro.html new file mode 100644 index 0000000..633d001 --- /dev/null +++ b/sources/doc/html/intro.html @@ -0,0 +1,182 @@ + + + + + + + + + + Introduction — phpMyAdmin 4.1.4 documentation + + + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +

      Introduction

      +

      phpMyAdmin can manage a whole MySQL server (needs a super-user) as +well as a single database. To accomplish the latter you’ll need a +properly set up MySQL user who can read/write only the desired +database. It’s up to you to look up the appropriate part in the MySQL +manual.

      +
      +

      Supported features

      +

      Currently phpMyAdmin can:

      +
        +
      • browse 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
      • +
      • manage MySQL users 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 see 3.6 What is currently not supported in phpMyAdmin about InnoDB?
      • +
      • support mysqli, the improved MySQL extension see 1.17 Which MySQL versions does phpMyAdmin support?
      • +
      • create, edit, call, export and drop stored procedures and functions
      • +
      • create, edit, export and drop events and triggers
      • +
      • communicate in 62 different languages
      • +
      +
      +
      +

      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 RFC 1952 or +Bzip2 formats) dumps and CSV exports if you use PHP with +Zlib support (--with-zlib) and/or Bzip2 support +(--with-bz2). Proper support may also need changes in php.ini.
      +
      +
      + + +
      +
      +
      +
      +
      +

      Table Of Contents

      + + +

      Previous topic

      +

      Welcome to phpMyAdmin’s documentation!

      +

      Next topic

      +

      Requirements

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/objects.inv b/sources/doc/html/objects.inv new file mode 100644 index 0000000..50e40a6 Binary files /dev/null and b/sources/doc/html/objects.inv differ diff --git a/sources/doc/html/other.html b/sources/doc/html/other.html new file mode 100644 index 0000000..7365cc8 --- /dev/null +++ b/sources/doc/html/other.html @@ -0,0 +1,135 @@ + + + + + + + + + + Other sources of information — phpMyAdmin 4.1.4 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 are listed on our wiki page.

      +
      +
      + + +
      +
      +
      +
      +
      +

      Table Of Contents

      + + +

      Previous topic

      +

      User management

      +

      Next topic

      +

      FAQ - Frequently Asked Questions

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/privileges.html b/sources/doc/html/privileges.html new file mode 100644 index 0000000..c64c1d8 --- /dev/null +++ b/sources/doc/html/privileges.html @@ -0,0 +1,169 @@ + + + + + + + + + + User management — phpMyAdmin 4.1.4 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 +Users link on the home page) not from within the Users +link under the table. 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.

      +
      +
      + + +
      +
      +
      +
      +
      +

      Table Of Contents

      + + +

      Previous topic

      +

      Transformations

      +

      Next topic

      +

      Other sources of information

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/require.html b/sources/doc/html/require.html new file mode 100644 index 0000000..6a6eec1 --- /dev/null +++ b/sources/doc/html/require.html @@ -0,0 +1,168 @@ + + + + + + + + + + Requirements — phpMyAdmin 4.1.4 documentation + + + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +

      Requirements

      +
      +

      Web server

      +

      Since, phpMyAdmin’s interface is based entirely in your browser, you’ll need a +web server (such as Apache, IIS) to install phpMyAdmin’s files into.

      +
      +
      +

      PHP

      +
        +
      • You need PHP 5.3.0 or newer, with session support, the Standard PHP Library +(SPL) extension, JSON support, and the mbstring extension.
      • +
      • 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 mcrypt extension is strongly suggested for most +users and is required for 64–bit machines. Not using mcrypt will +cause phpMyAdmin to load pages significantly slower.
      • +
      • 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.
      • +
      • Performance suggestion: install the ctype extension.
      • +
      + +
      +
      +

      Database

      +

      phpMyAdmin supports MySQL-compatible databases.

      +
        +
      • MySQL 5.5 or newer
      • +
      • MariaDB 5.5 or newer
      • +
      • Drizzle
      • +
      + +
      +
      +

      Web browser

      +

      To access phpMyAdmin you need a web browser with cookies and javascript +enabled.

      +
      +
      + + +
      +
      +
      +
      +
      +

      Table Of Contents

      + + +

      Previous topic

      +

      Introduction

      +

      Next topic

      +

      Installation

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/search.html b/sources/doc/html/search.html new file mode 100644 index 0000000..7c22c48 --- /dev/null +++ b/sources/doc/html/search.html @@ -0,0 +1,100 @@ + + + + + + + + + + Search — phpMyAdmin 4.1.4 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/sources/doc/html/searchindex.js b/sources/doc/html/searchindex.js new file mode 100644 index 0000000..2e9badb --- /dev/null +++ b/sources/doc/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({objects:{"":{"$cfg['ExecTimeLimit']":[13,0,1,"cfg_ExecTimeLimit"],"$cfg['Servers'][$i]['SignonURL']":[13,0,1,"cfg_Servers_SignonURL"],"$cfg['UploadDir']":[13,0,1,"cfg_UploadDir"],"$cfg['Servers'][$i]['socket']":[13,0,1,"cfg_Servers_socket"],"$cfg['SkipLockedTables']":[13,0,1,"cfg_SkipLockedTables"],"$cfg['Servers'][$i]['AllowRoot']":[13,0,1,"cfg_Servers_AllowRoot"],"$cfg['Servers'][$i]['AllowDeny']['order']":[13,0,1,"cfg_Servers_AllowDeny_order"],"$cfg['BrowseMarkerBackground']":[13,0,1,"cfg_BrowseMarkerBackground"],"$cfg['SQLValidator']":[13,0,1,"cfg_SQLValidator"],"$cfg['ShowBrowseComments']":[13,0,1,"cfg_ShowBrowseComments"],"$cfg['CompressOnFly']":[13,0,1,"cfg_CompressOnFly"],"$cfg['ThemeManager']":[13,0,1,"cfg_ThemeManager"],"$cfg['Servers'][$i]['recent']":[13,0,1,"cfg_Servers_recent"],"$cfg['OBGzip']":[13,0,1,"cfg_OBGzip"],"$cfg['MaxRows']":[13,0,1,"cfg_MaxRows"],"$cfg['SaveCellsAtOnce']":[13,0,1,"cfg_SaveCellsAtOnce"],"$cfg['NavigationTreeDisplayItemFilterMinimum']":[13,0,1,"cfg_NavigationTreeDisplayItemFilterMinimum"],"$cfg['Servers'][$i]['compress']":[13,0,1,"cfg_Servers_compress"],"$cfg['ProtectBinary']":[13,0,1,"cfg_ProtectBinary"],"$cfg['DisplayServersList']":[13,0,1,"cfg_DisplayServersList"],"$cfg['ShowPropertyComments']":[13,0,1,"cfg_ShowPropertyComments"],"$cfg['MaxDbList']":[13,0,1,"cfg_MaxDbList"],"$cfg['CheckConfigurationPermissions']":[13,0,1,"cfg_CheckConfigurationPermissions"],"$cfg['SQLValidator']['username']":[13,0,1,"cfg_SQLValidator_username"],"$cfg['Servers'][$i]['password']":[13,0,1,"cfg_Servers_password"],"$cfg['Export']['method']":[13,0,1,"cfg_Export_method"],"$cfg['FontFamilyFixed']":[13,0,1,"cfg_FontFamilyFixed"],"$cfg['NavigationLogoLink']":[13,0,1,"cfg_NavigationLogoLink"],"$cfg['ThemePath']":[13,0,1,"cfg_ThemePath"],"$cfg['Servers'][$i]['port']":[13,0,1,"cfg_Servers_port"],"$cfg['Confirm']":[13,0,1,"cfg_Confirm"],"$cfg['Servers'][$i]['tracking_version_auto_create']":[13,0,1,"cfg_Servers_tracking_version_auto_create"],"$cfg['Servers'][$i]['navigationhiding']":[13,0,1,"cfg_Servers_navigationhiding"],"$cfg['ReservedWordDisableWarning']":[13,0,1,"cfg_ReservedWordDisableWarning"],"$cfg['DefaultLang']":[13,0,1,"cfg_DefaultLang"],"$cfg['LinkLengthLimit']":[13,0,1,"cfg_LinkLengthLimit"],"$cfg['DBG']['sql']":[13,0,1,"cfg_DBG_sql"],"$cfg['Servers']":[13,0,1,"cfg_Servers"],"$cfg['NavigationTreeEnableGrouping']":[13,0,1,"cfg_NavigationTreeEnableGrouping"],"$cfg['Servers'][$i]['table_uiprefs']":[13,0,1,"cfg_Servers_table_uiprefs"],"$cfg['DefaultQueryTable']":[13,0,1,"cfg_DefaultQueryTable"],"$cfg['QueryWindowWidth']":[13,0,1,"cfg_QueryWindowWidth"],"$cfg['DefaultDisplay']":[13,0,1,"cfg_DefaultDisplay"],"$cfg['NaviPointerBackground']":[13,0,1,"cfg_NaviPointerBackground"],"$cfg['Servers'][$i]['user']":[13,0,1,"cfg_Servers_user"],"$cfg['ForceSSL']":[13,0,1,"cfg_ForceSSL"],"$cfg['ShowPhpInfo']":[13,0,1,"cfg_ShowPhpInfo"],"$cfg['Servers'][$i]['AllowNoPassword']":[13,0,1,"cfg_Servers_AllowNoPassword"],"$cfg['SQLQuery']['Refresh']":[13,0,1,"cfg_SQLQuery_Refresh"],"$cfg['Lang']":[13,0,1,"cfg_Lang"],"$cfg['EditInWindow']":[13,0,1,"cfg_EditInWindow"],"$cfg['SQLQuery']['Explain']":[13,0,1,"cfg_SQLQuery_Explain"],"$cfg['ShowAll']":[13,0,1,"cfg_ShowAll"],"$cfg['DefaultQueryDatabase']":[13,0,1,"cfg_DefaultQueryDatabase"],"$cfg['TextareaCols']":[13,0,1,"cfg_TextareaCols"],"$cfg['Border']":[13,0,1,"cfg_Border"],"$cfg['UserprefsDisallow']":[13,0,1,"cfg_UserprefsDisallow"],"$cfg['Servers'][$i]['pdf_pages']":[13,0,1,"cfg_Servers_pdf_pages"],"$cfg['HideStructureActions']":[13,0,1,"cfg_HideStructureActions"],"$cfg['RetainQueryBox']":[13,0,1,"cfg_RetainQueryBox"],"$cfg['Order']":[13,0,1,"cfg_Order"],"$cfg['GD2Available']":[13,0,1,"cfg_GD2Available"],"$cfg['PmaAbsoluteUri']":[13,0,1,"cfg_PmaAbsoluteUri"],"$cfg['LimitChars']":[13,0,1,"cfg_LimitChars"],"$cfg['ShowFieldTypesInDataEditView']":[13,0,1,"cfg_ShowFieldTypesInDataEditView"],"$cfg['Servers'][$i]['controluser']":[13,0,1,"cfg_Servers_controluser"],"$cfg['NavigationTreeTableLevel']":[13,0,1,"cfg_NavigationTreeTableLevel"],"$cfg['Servers'][$i]['relation']":[13,0,1,"cfg_Servers_relation"],"$cfg['ActionLinksMode']":[13,0,1,"cfg_ActionLinksMode"],"$cfg['Servers'][$i]['MaxTableUiprefs']":[13,0,1,"cfg_Servers_MaxTableUiprefs"],"$cfg['RememberSorting']":[13,0,1,"cfg_RememberSorting"],"$cfg['UserprefsDeveloperTab']":[13,0,1,"cfg_UserprefsDeveloperTab"],"$cfg['McryptDisableWarning']":[13,0,1,"cfg_McryptDisableWarning"],"$cfg['ShowDbStructureLastUpdate']":[13,0,1,"cfg_ShowDbStructureLastUpdate"],"$cfg['SendErrorReports']":[13,0,1,"cfg_SendErrorReports"],"$cfg['GZipDump']":[13,0,1,"cfg_GZipDump"],"$cfg['Servers'][$i]['connect_type']":[13,0,1,"cfg_Servers_connect_type"],"$cfg['CaptchaLoginPublicKey']":[13,0,1,"cfg_CaptchaLoginPublicKey"],"$cfg['MemoryLimit']":[13,0,1,"cfg_MemoryLimit"],"$cfg['TextareaAutoSelect']":[13,0,1,"cfg_TextareaAutoSelect"],"$cfg['Servers'][$i]['auth_swekey_config']":[13,0,1,"cfg_Servers_auth_swekey_config"],"$cfg['BZipDump']":[13,0,1,"cfg_BZipDump"],"$cfg['BrowseMarkerEnable']":[13,0,1,"cfg_BrowseMarkerEnable"],"$cfg['ShowStats']":[13,0,1,"cfg_ShowStats"],"$cfg['ThemeDefault']":[13,0,1,"cfg_ThemeDefault"],"$cfg['NaturalOrder']":[13,0,1,"cfg_NaturalOrder"],"$cfg['TitleServer']":[13,0,1,"cfg_TitleServer"],"$cfg['ThColor']":[13,0,1,"cfg_ThColor"],"$cfg['BrowsePointerColor']":[13,0,1,"cfg_BrowsePointerColor"],"$cfg['DefaultConnectionCollation']":[13,0,1,"cfg_DefaultConnectionCollation"],"$cfg['Servers'][$i]['verbose']":[13,0,1,"cfg_Servers_verbose"],"$cfg['AllowThirdPartyFraming']":[13,0,1,"cfg_AllowThirdPartyFraming"],"$cfg['Servers'][$i]['bookmarktable']":[13,0,1,"cfg_Servers_bookmarktable"],"$cfg['NavigationDisplayLogo']":[13,0,1,"cfg_NavigationDisplayLogo"],"$cfg['Servers'][$i]['ssl_ca']":[13,0,1,"cfg_Servers_ssl_ca"],"$cfg['Servers'][$i]['nopassword']":[13,0,1,"cfg_Servers_nopassword"],"$cfg['NavigationTreeDefaultTabTable']":[13,0,1,"cfg_NavigationTreeDefaultTabTable"],"$cfg['NavigationLogoLinkWindow']":[13,0,1,"cfg_NavigationLogoLinkWindow"],"$cfg['FilterLanguages']":[13,0,1,"cfg_FilterLanguages"],"$cfg['ShowSQL']":[13,0,1,"cfg_ShowSQL"],"$cfg['InitialSlidersState']":[13,0,1,"cfg_InitialSlidersState"],"$cfg['Servers'][$i]['userconfig']":[13,0,1,"cfg_Servers_userconfig"],"$cfg['blowfish_secret']":[13,0,1,"cfg_blowfish_secret"],"$cfg['DefaultTabTable']":[13,0,1,"cfg_DefaultTabTable"],"$cfg['ShowServerInfo']":[13,0,1,"cfg_ShowServerInfo"],"$cfg['ShowChgPassword']":[13,0,1,"cfg_ShowChgPassword"],"$cfg['Servers'][$i]['ssl']":[13,0,1,"cfg_Servers_ssl"],"$cfg['Servers'][$i]['hide_db']":[13,0,1,"cfg_Servers_hide_db"],"$cfg['MaxCharactersInDisplayedSQL']":[13,0,1,"cfg_MaxCharactersInDisplayedSQL"],"$cfg['LoginCookieValidity']":[13,0,1,"cfg_LoginCookieValidity"],"$cfg['Servers'][$i]['ssl_ciphers']":[13,0,1,"cfg_Servers_ssl_ciphers"],"$cfg['Servers'][$i]['controlport']":[13,0,1,"cfg_Servers_controlport"],"$cfg['ShowDbStructureLastCheck']":[13,0,1,"cfg_ShowDbStructureLastCheck"],"$cfg['DefaultTabServer']":[13,0,1,"cfg_DefaultTabServer"],"$cfg['NumRecentTables']":[13,0,1,"cfg_NumRecentTables"],"$cfg['Servers'][$i]['extension']":[13,0,1,"cfg_Servers_extension"],"$cfg['CharTextareaRows']":[13,0,1,"cfg_CharTextareaRows"],"$cfg['BgOne']":[13,0,1,"cfg_BgOne"],"$cfg['HeaderFlipType']":[13,0,1,"cfg_HeaderFlipType"],"$cfg['Servers'][$i]['ssl_cert']":[13,0,1,"cfg_Servers_ssl_cert"],"$cfg['ShowDbStructureCreation']":[13,0,1,"cfg_ShowDbStructureCreation"],"$cfg['QueryHistoryMax']":[13,0,1,"cfg_QueryHistoryMax"],"$cfg['BrowsePointerEnable']":[13,0,1,"cfg_BrowsePointerEnable"],"$cfg['LoginCookieStore']":[13,0,1,"cfg_LoginCookieStore"],"$cfg['Servers'][$i]['history']":[13,0,1,"cfg_Servers_history"],"$cfg['BgTwo']":[13,0,1,"cfg_BgTwo"],"$cfg['TitleDatabase']":[13,0,1,"cfg_TitleDatabase"],"$cfg['Servers'][$i]['auth_type']":[13,0,1,"cfg_Servers_auth_type"],"$cfg['RowActionLinks']":[13,0,1,"cfg_RowActionLinks"],"$cfg['Servers'][$i]['StatusCacheDatabases']":[13,0,1,"cfg_Servers_StatusCacheDatabases"],"$cfg['ProxyPass']":[13,0,1,"cfg_ProxyPass"],"$cfg['Servers'][$i]['column_info']":[13,0,1,"cfg_Servers_column_info"],"$cfg['TitleDefault']":[13,0,1,"cfg_TitleDefault"],"$cfg['DefaultFunctions']":[13,0,1,"cfg_DefaultFunctions"],"$cfg['GridEditing']":[13,0,1,"cfg_GridEditing"],"$cfg['QueryHistoryDB']":[13,0,1,"cfg_QueryHistoryDB"],"$cfg['SQLQuery']['ShowAsPHP']":[13,0,1,"cfg_SQLQuery_ShowAsPHP"],"$cfg['SaveDir']":[13,0,1,"cfg_SaveDir"],"$cfg['TempDir']":[13,0,1,"cfg_TempDir"],"$cfg['NavigationTreeDisplayDbFilterMinimum']":[13,0,1,"cfg_NavigationTreeDisplayDbFilterMinimum"],"$cfg['NavigationTreeTableSeparator']":[13,0,1,"cfg_NavigationTreeTableSeparator"],"$cfg['TranslationWarningThreshold']":[13,0,1,"cfg_TranslationWarningThreshold"],"$cfg['Servers'][$i]['usergroups']":[13,0,1,"cfg_Servers_usergroups"],"$cfg['Servers'][$i]['tracking_add_drop_database']":[13,0,1,"cfg_Servers_tracking_add_drop_database"],"$cfg['Servers'][$i]['controlpass']":[13,0,1,"cfg_Servers_controlpass"],"$cfg['Servers'][$i]['tracking']":[13,0,1,"cfg_Servers_tracking"],"$cfg['ShowHint']":[13,0,1,"cfg_ShowHint"],"$cfg['MainBackground']":[13,0,1,"cfg_MainBackground"],"$cfg['Export']":[13,0,1,"cfg_Export"],"$cfg['Servers'][$i]['auth_http_realm']":[13,0,1,"cfg_Servers_auth_http_realm"],"$cfg['DisplayBinaryAsHex']":[13,0,1,"cfg_DisplayBinaryAsHex"],"$cfg['UseDbSearch']":[13,0,1,"cfg_UseDbSearch"],"$cfg['IconvExtraParams']":[13,0,1,"cfg_IconvExtraParams"],"$cfg['DisableMultiTableMaintenance']":[13,0,1,"cfg_DisableMultiTableMaintenance"],"$cfg['LoginCookieRecall']":[13,0,1,"cfg_LoginCookieRecall"],"$cfg['TabsMode']":[13,0,1,"cfg_TabsMode"],"$cfg['Servers'][$i]['ssl_key']":[13,0,1,"cfg_Servers_ssl_key"],"$cfg['Servers'][$i]['tracking_add_drop_table']":[13,0,1,"cfg_Servers_tracking_add_drop_table"],"$cfg['DefaultTabDatabase']":[13,0,1,"cfg_DefaultTabDatabase"],"$cfg['LongtextDoubleTextarea']":[13,0,1,"cfg_LongtextDoubleTextarea"],"$cfg['NaviWidth']":[13,0,1,"cfg_NaviWidth"],"$cfg['MaxTableList']":[13,0,1,"cfg_MaxTableList"],"$cfg['RepeatCells']":[13,0,1,"cfg_RepeatCells"],"$cfg['TitleTable']":[13,0,1,"cfg_TitleTable"],"$cfg['AllowArbitraryServer']":[13,0,1,"cfg_AllowArbitraryServer"],"$cfg['RecodingEngine']":[13,0,1,"cfg_RecodingEngine"],"$cfg['PDFPageSizes']":[13,0,1,"cfg_PDFPageSizes"],"$cfg['DBG']['demo']":[13,0,1,"cfg_DBG_demo"],"$cfg['NaviBackground']":[13,0,1,"cfg_NaviBackground"],"$cfg['VersionCheck']":[13,0,1,"cfg_VersionCheck"],"$cfg['SuhosinDisableWarning']":[13,0,1,"cfg_SuhosinDisableWarning"],"$cfg['BrowsePointerBackground']":[13,0,1,"cfg_BrowsePointerBackground"],"$cfg['IgnoreMultiSubmitErrors']":[13,0,1,"cfg_IgnoreMultiSubmitErrors"],"$cfg['BrowseMIME']":[13,0,1,"cfg_BrowseMIME"],"$cfg['Servers'][$i]['designer_coords']":[13,0,1,"cfg_Servers_designer_coords"],"$cfg['Servers'][$i]['controlhost']":[13,0,1,"cfg_Servers_controlhost"],"$cfg['ZipDump']":[13,0,1,"cfg_ZipDump"],"$cfg['CodemirrorEnable']":[13,0,1,"cfg_CodemirrorEnable"],"$cfg['ForeignKeyMaxLimit']":[13,0,1,"cfg_ForeignKeyMaxLimit"],"$cfg['ForeignKeyDropdownOrder']":[13,0,1,"cfg_ForeignKeyDropdownOrder"],"$cfg['Servers'][$i]['host']":[13,0,1,"cfg_Servers_host"],"$cfg['QueryWindowDefTab']":[13,0,1,"cfg_QueryWindowDefTab"],"$cfg['Servers'][$i]['StatusCacheLifetime']":[13,0,1,"cfg_Servers_StatusCacheLifetime"],"$cfg['TextareaRows']":[13,0,1,"cfg_TextareaRows"],"$cfg['Servers'][$i]['tracking_default_statements']":[13,0,1,"cfg_Servers_tracking_default_statements"],"$cfg['TableNavigationLinksMode']":[13,0,1,"cfg_TableNavigationLinksMode"],"$cfg['ServerDefault']":[13,0,1,"cfg_ServerDefault"],"$cfg['ShowDisplayDirection']":[13,0,1,"cfg_ShowDisplayDirection"],"$cfg['ProxyUrl']":[13,0,1,"cfg_ProxyUrl"],"$cfg['SessionSavePath']":[13,0,1,"cfg_SessionSavePath"],"$cfg['ShowCreateDb']":[13,0,1,"cfg_ShowCreateDb"],"$cfg['InsertRows']":[13,0,1,"cfg_InsertRows"],"$cfg['LoginCookieDeleteAll']":[13,0,1,"cfg_LoginCookieDeleteAll"],"$cfg['CaptchaLoginPrivateKey']":[13,0,1,"cfg_CaptchaLoginPrivateKey"],"$cfg['Servers'][$i]['ssl_ca_path']":[13,0,1,"cfg_Servers_ssl_ca_path"],"$cfg['PersistentConnections']":[13,0,1,"cfg_PersistentConnections"],"$cfg['ServerLibraryDifference_DisableWarning']":[13,0,1,"cfg_ServerLibraryDifference_DisableWarning"],"$cfg['Servers'][$i]['only_db']":[13,0,1,"cfg_Servers_only_db"],"$cfg['QueryWindowHeight']":[13,0,1,"cfg_QueryWindowHeight"],"$cfg['SQLValidator']['use']":[13,0,1,"cfg_SQLValidator_use"],"$cfg['CharTextareaCols']":[13,0,1,"cfg_CharTextareaCols"],"$cfg['NavigationTreePointerEnable']":[13,0,1,"cfg_NavigationTreePointerEnable"],"$cfg['PDFDefaultPageSize']":[13,0,1,"cfg_PDFDefaultPageSize"],"$cfg['SQLQuery']['Edit']":[13,0,1,"cfg_SQLQuery_Edit"],"$cfg['Servers'][$i]['SignonSession']":[13,0,1,"cfg_Servers_SignonSession"],"$cfg['NavigationTreeDbSeparator']":[13,0,1,"cfg_NavigationTreeDbSeparator"],"$cfg['Servers'][$i]['LogoutURL']":[13,0,1,"cfg_Servers_LogoutURL"],"$cfg['ThBackground']":[13,0,1,"cfg_ThBackground"],"$cfg['CSPAllow']":[13,0,1,"cfg_CSPAllow"],"$cfg['ShowFunctionFields']":[13,0,1,"cfg_ShowFunctionFields"],"$cfg['SQLValidator']['password']":[13,0,1,"cfg_SQLValidator_password"],"$cfg['SQLQuery']['Validate']":[13,0,1,"cfg_SQLQuery_Validate"],"$cfg['AllowUserDropDatabase']":[13,0,1,"cfg_AllowUserDropDatabase"],"$cfg['MaxExactCountViews']":[13,0,1,"cfg_MaxExactCountViews"],"$cfg['Servers'][$i]['table_coords']":[13,0,1,"cfg_Servers_table_coords"],"$cfg['Import']":[13,0,1,"cfg_Import"],"$cfg['DBG']":[13,0,1,"cfg_DBG"],"$cfg['AvailableCharsets']":[13,0,1,"cfg_AvailableCharsets"],"$cfg['MaxSizeForInputField']":[13,0,1,"cfg_MaxSizeForInputField"],"$cfg['Servers'][$i]['SignonScript']":[13,0,1,"cfg_Servers_SignonScript"],"$cfg['NaviPointerColor']":[13,0,1,"cfg_NaviPointerColor"],"$cfg['CharEditing']":[13,0,1,"cfg_CharEditing"],"$cfg['Servers'][$i]['table_info']":[13,0,1,"cfg_Servers_table_info"],"$cfg['MaxExactCount']":[13,0,1,"cfg_MaxExactCount"],"$cfg['PmaNoRelation_DisableWarning']":[13,0,1,"cfg_PmaNoRelation_DisableWarning"],"$cfg['MaxNavigationItems']":[13,0,1,"cfg_MaxNavigationItems"],"$cfg['Servers'][$i]['tracking_add_drop_view']":[13,0,1,"cfg_Servers_tracking_add_drop_view"],"$cfg['BrowseMarkerColor']":[13,0,1,"cfg_BrowseMarkerColor"],"$cfg['NavigationDisplayServers']":[13,0,1,"cfg_NavigationDisplayServers"],"$cfg['Error_Handler']['display']":[13,0,1,"cfg_Error_Handler_display"],"$cfg['PropertiesNumColumns']":[13,0,1,"cfg_PropertiesNumColumns"],"$cfg['ThemePerServer']":[13,0,1,"cfg_ThemePerServer"],"$cfg['MinSizeForInputField']":[13,0,1,"cfg_MinSizeForInputField"],"$cfg['FontFamily']":[13,0,1,"cfg_FontFamily"],"$cfg['Servers'][$i]['users']":[13,0,1,"cfg_Servers_users"],"$cfg['Servers'][$i]['AllowDeny']['rules']":[13,0,1,"cfg_Servers_AllowDeny_rules"],"$cfg['Servers'][$i]['pmadb']":[13,0,1,"cfg_Servers_pmadb"],"$cfg['ProxyUser']":[13,0,1,"cfg_ProxyUser"],"$cfg['TrustedProxies']":[13,0,1,"cfg_TrustedProxies"]}},terms:{represent:8,varfilt:5,uploadprogress:5,prefix:13,forget:[5,13],whose:[4,5],miner:5,tobia:[8,2],usepackag:5,lori:8,under:[4,5,13,14,2],hermann:8,textareaautoselect:13,merchant:2,versioncheck:13,witten:8,everi:[4,5,13],risk:[5,13],dutch:8,max_value_length:5,alberty_at_neptunlab:8,affect:[5,13],opendocu:[3,10],look:[1,3,4,5,10,13],upload:[0,4,5,13,6],cma:8,getmimesubtyp:1,multipurpos:3,zlib:[3,5,10],hord:8,direct:[4,5,13,1],nair:8,consequ:13,second:[5,13,1],chee:8,even:[2,4,5,10,13,14],dialogu:5,hide:[4,13],followsymlink:5,veeven:8,"new":[1,3,4,5,8,11,13,14],net:[2,3,4,5,8,13],ever:5,super_priv:4,told:8,abov:[5,13],readman:8,never:[5,13],here:[4,5,13,1],beck:8,path:[4,5,13,9],interpret:5,michal:[8,4,2],forum:[8,5],robin:[8,2],datetim:13,transformation_opt:[13,1],permit:[14,13],mysqld:5,portabl:3,siramizu_at_gmail:8,phpseclib:8,heritrix:5,unix:[4,5],mike_at_graftonhal:8,mysqldump:5,tbl_select:[8,13],txt:[4,5],unit:8,plot:5,dhima:8,upgrade_tables_mysql_4_1_2:4,describ:[3,13,1],would:[4,5,13,1],mysqladmin:5,kettler:8,call:[13,4,5,10,1],recommend:[3,4,5,13],memoranda:3,type:[3,8,5,13,1],until:[8,4,13],rink_at_initfour:8,relat:[3,8,4,5,13],email_at_c:8,yahoo:5,u4663530_at_anu:8,warn:[4,5,13],mysql_connect:5,exce:13,hole:13,hold:[5,13],unpack:[4,5],addon:5,kuppelwies:8,join:[8,5,13],restor:5,setup:[0,1,4,5,8,9,13],work:[8,5,13,1],memorylimit:13,itself:[4,5,13],root:[4,5,13,14],overrid:4,give:[4,5,9],autodetect:13,delislma_at_collegesherbrook:8,indic:0,pandithawatta:8,captcha:4,unavail:13,want:[4,5,13,1,9],moham:8,unsign:1,horizonta:13,end:[4,5,13,1],chanaka:8,quot:5,travel:4,zzzz:13,march:8,how:[4,5,13,1],func_numb:13,disappear:[5,13],pikto:5,maxnavigationitem:13,config:[4,5,13,9],updat:[8,4,5,13],process_priv:4,chines:8,after:[4,5,13],lump:5,diagram:5,befor:[4,5,13],wrong:[5,13],star_at_origin:8,router:4,allowuserdropdatabas:13,averag:5,autoconnect:13,attempt:[5,13],third:[4,5,13,7],grant:[4,5,14],credenti:[4,13],ssl_ca:13,matiasbellone_at_gmail:8,maintain:[8,4],environ:[4,5,13],enter:[4,5,13,1],nikto:5,order:[0,3,4,5,8,13],oper:[3,5,13,1],feedback:8,softwar:[3,9,5,2],diagnos:5,over:[3,4,5,13,14],becaus:[8,4,5,13,1],jpeg:[3,6,1],supun:8,navigationtreedisplayitemfilterminimum:13,privileg:[4,5,8,10,11,13,14],jordi:8,digest:5,textbox:14,directli:[13,5,10],fit:[8,5,13,2],fix:[8,5],better:5,offic:3,persist:13,auth_swekey_config:[4,13],hidden:13,mydb:[5,13],rachim:8,split:[5,13,1],them:[1,3,4,5,9,13],thei:[1,4,5,8,9,13,14],fragment:5,safe:[4,5,13],"break":5,kirillov:8,choic:[4,5,13],alex:8,changelog:3,telekom:5,arrow:5,rowactionlink:13,side:[3,4,13],mean:[4,5,13,1],cybot_tm_at_us:[8,2],navi:13,allowarbitraryserv:[4,13],neomo:5,arg_separ:5,phpmy:5,forgot:5,logo:[8,13],extract:5,network:[3,5],file_format:3,goe:5,zarubin:8,newli:4,content:[0,1,3,4,5,8,13],rewrit:[3,5],daniel:8,showfunctionfield:13,got:5,ware:8,userprefsdisallow:13,forth:1,"98se":5,http_request:5,navig:[0,5,13,8],situat:5,free:[3,4,5,1,2],standard:[3,8,6,5,13],small:[5,13],kaushalya:8,hand:[4,5],func_spati:13,silent:[5,13],workaround:5,server_address:13,loup:3,isp:[3,0,4,5,13],filter:[5,13],iso:[3,10],unabl:5,martijn:8,kingdom:8,onto:5,user:[0,1,3,4,5,6,8,10,11,13,14],rang:[3,5,13],signonsess:[4,13],independ:[3,8,5],xitami:5,restrict:[4,5,13],instruct:5,alreadi:[4,5],grantor:[4,13],hanut_at_php:8,unmark:13,agre:13,primari:[3,8,5],chathuranga:8,top:[4,5,13],sometim:5,amalesh:8,master:[7,13],too:[5,13,12],pma__designer_coord:13,danish:8,tool:[8,5,13,12],"17a":5,showbrowsecom:13,past:13,max_link:5,provid:[1,3,4,5,9,13],tree:[8,13],project:[3,0,5,8],serverlibrarydifference_disablewarn:13,thu:[4,5],checkconfigurationpermiss:13,fashion:13,php_mysql:5,raj:8,mine:5,seed:13,manner:5,increment:13,seen:13,seem:[4,5,13],incompat:5,minu:5,userprefsdevelopertab:13,querywindowheight:13,realm:13,recreat:5,latter:[13,5,10],sakamoto:8,pma__us:13,derek:8,thoma:[3,8],indrajith:8,hypertext_transfer_protocol:3,davidson:8,simplifi:8,thi:[1,2,4,5,8,9,13],plenti:5,though:13,usernam:[13,4,5,10,14],w3c_valid:5,marin:8,regular:[3,8,13],defaultlang:13,letter:[5,13],jean:3,lithuanian:8,titledefault:13,tradit:8,persistentconnect:13,don:[4,5,13,14],telugu:8,dom:8,doc:[3,5],lose:[5,13],doe:[1,4,5,10,13,6,14],omniexplorer_bot:5,declar:[5,1],wildcard:[5,13],neg:5,winningham:8,mrbendig_at_mrbendig:8,dot:[5,13],ldi_:8,xampp:4,feedfetch:5,synoobot:5,opposit:13,random:[8,13],syntax:[5,13],radio:5,protocol:[3,4,5,13],placella:8,absolut:13,layout:[13,5,10],firstnam:5,alvar:8,menu:[5,13],explain:[4,5,13],configur:[0,1,3,4,5,8,10,13,14],apach:[3,4,5,13,6],burak:8,theme:[0,5,13],busi:3,lubo:8,folder:[4,5],schneier:3,field_:3,tbl_structur:[13,1],likewis:5,stop:[5,13],sqlvalid:13,report:[8,5,13],symmetr:3,"k\u0119stuti":8,volkov:8,bar:[6,5,13],excel:5,signonurl:[4,13],method:[3,4,5,13,1],reload:5,bad:5,ban:5,taceloski:8,mysql:[0,3,4,5,8,9,10,7,13,6,14],forcessl:13,gridedit:13,result:[13,4,5,10],auto_incr:5,corrupt:13,themselv:13,best:[4,5],subject:[5,13],pete:8,awar:[4,5],said:5,hopefulli:1,databas:[0,3,4,5,8,10,11,13,6,14],wikipedia:[3,4],tracking_version_auto_cr:13,bgone:13,wiltave_at_gmail:8,atul:8,yahooseek:5,adnan:8,pma__navigationhid:13,approach:13,n8falke_at_us:8,accord:13,extend:[3,5,1],srnka:8,extens:[3,4,5,8,10,13,6],cut:13,preprocessor:3,wtf:5,subfold:4,protect:[4,5,13],accident:13,shapefil:[5,13],met:5,emerson4br_at_gmail:8,howev:[4,5,13,1],ayush:8,kronsbein:8,against:[4,5],brad:5,ivanlanin_at_us:8,countri:5,fauveau_at_globali:8,browser:[0,1,3,4,5,13,6],com:[1,2,3,5,8,13],"16m":13,clshttp:5,queryhistorydb:13,loader:5,pdf_page:[5,13],column_com:13,trust:13,assum:[4,5],gc_maxlifetim:13,duplic:5,question:[3,0,5],saedi:8,three:[5,13,1],been:[4,5,13,12],trigger:[3,8,10,13],interest:5,basic:[0,1,4,5,9,13],rather:[14,5,13],suppress:13,xxx:[5,13,1],telnet:5,argument:5,multithread:3,dave:8,"kl\u00e4ger":8,http_post_var:5,dash:[5,13],gnu:[3,2],servic:[3,4,5,13],properti:[8,5,13,1],sourceforg:[8,5,2],rouslan:8,pmanorelation_disablewarn:13,contributor:8,need:[4,5,9,10,13,6,14],libxml:6,myadmin:8,conf:[4,5],vendor:13,sever:[9,5,13,1],piotr:8,navigationtreedefaulttabt:13,perform:[14,5,13,6],suggest:[8,4,5,6],make:[4,5,13,1,12],ammar:8,complex:[5,10],descend:13,roszatycki:8,complet:[8,5,13],http_x_forwarded_for:13,"_uri_schem":3,evil:[4,5],kit:[4,5],norwegian:8,nix:13,ownership:5,tune:4,kept:[13,1],scenario:5,execute_priv:4,swekei:[4,13],printview:[8,13],bugzilla:5,client:[3,0,4,5,13],maxtablelist:13,gzip:[3,8,5,10,13],everyth:5,maxexactcount:[5,13],left:[8,5,13],seekbot:5,table_priv:4,bellow:13,identifi:[3,4,5,13],just:[8,4,5,13],bellon:8,textareacol:13,languag:[0,3,4,5,8,10,13],jose:5,easi:[5,13,1],interfer:5,had:[4,5],logformat:5,els:[8,4,5,13],save:[0,4,5,13],hat:4,jan_at_nrw:8,applic:[3,4,5,13],mayb:5,recaptcha:13,credit:[0,2,8],editinwindow:13,file_priv:4,arab:8,rfc1867:5,corazza:8,background:13,sitesearch:5,apart:5,measur:[3,4],specif:[1,3,4,5,11,13,14],arbitrari:[4,13],sublevel:13,controlport:13,manual:[13,4,5,10,1],zoom:[8,5],soap:[3,5,13],create_priv:4,sebastian:[8,2],underli:5,sqlqueri:13,www:[1,2,3,5,8,13],right:[4,5,13,1,14],old:[4,5,13],deal:3,czech:[8,13],"_db":13,foreignkeymaxlimit:[5,13],maxim:8,getmimetyp:1,intern:[3,5,13,1],sure:[4,5,13,1],savecellsatonc:13,borg:8,inact:1,wkito:5,bora:8,transmiss:3,bottom:[14,5,13],net_dim:5,normal:[4,5,13,1],track:[8,4,10,13],tracker:[5,13],t10:13,setoutputfilt:5,iccrawl:5,condit:5,foo:5,localhost:[4,5,13],core:3,plu:[8,5],titleserv:13,connect_typ:13,stefan:8,confer:8,ivan:8,ankit:8,post:[5,13],"super":10,"stanis\u0142aw":8,plug:[4,1],alexand:[8,2],chronolog:[0,8],surround:5,algi:8,produc:[5,13],ppa:4,tbl_dump:5,kissu:8,curiou:5,profession:3,bound:1,repl_client_priv:4,down:[14,5,13,1,9],slurp:5,xavier:8,netscap:5,contrib:4,storag:[3,0,4,13],maxsizeforinputfield:13,accordingli:[5,1],suffici:5,support:[0,4,5,8,10,13,6],pma_usernam:5,transform:[0,1,4,5,8,10,11,13],why:[5,13],avail:[3,9,5,13,1],width:13,request_method:5,editor:[8,4,13],legner:5,form:[3,8,5,13],offer:[3,13],forc:[5,13],tickbox:5,papazogl:8,sensi:5,savedir:13,renam:[13,5,10],cidr:13,"true":[4,13],limitchar:13,textarea:13,bugfix:8,maximum:[5,13],tell:5,toggl:[5,13],absenc:5,opensus:4,webmaster_at_trafficg:8,legenhausen:8,featur:[0,3,4,5,8,10,12,13],classic:[5,13],request:[3,4,5,13],"abstract":[8,1],decrypt:5,zend:5,mysql_fetch_field:1,seoma:5,showdisplaydirect:13,exist:[4,5,8,11,13,14],ship:[3,4,9],browsepointeren:13,db_search:13,interf:5,password:[13,4,5,10,14],encrypt:[4,5,13],showdbstructurelastcheck:13,refactor:8,vserver:[5,13],test:[8,9,5,13],propertiesnumcolumn:13,intend:[9,5],yasitha:8,walton:8,consid:[4,5],sql:[0,3,4,5,8,10,13],receiv:[3,5,1,2],longer:13,furthermor:13,tinyint:5,ignor:[5,13],time:[3,4,5,13],backward:5,inc:[4,5,13,9],concept:[8,10],table_coord:[5,13],skip:[9,13],rob:5,global:[4,5,10,1,14],ba17c1ec07d65003:4,signific:8,horizontalflip:13,delete_priv:4,pma_combin:5,row:[3,8,5,13],hierarch:8,decid:5,middl:13,depend:[4,5],pem:13,crawleradmin:5,decim:5,readabl:5,markt:5,mehbooob:8,must:[13,4,5,10,14],keck:[8,2],sourc:[0,3,5,8,7,11,12,13],string:[8,5,13,1],fando:5,initialslidersst:13,maxcharactersindisplayedsql:13,insertrow:13,word:[13,0,5,10],brows:[13,0,5,10,8],seealso:3,tracking_default_stat:13,administr:[8,4,5,13,14],level:[3,4,5,13],did:5,win2k:5,"gesch\u00e9":8,item:[3,5,13],team:[4,13],cooki:[0,3,4,5,8,13,6],dir:[5,1],prevent:[4,5,13],slower:6,obrador:8,bravo:8,applytransform:1,everytim:13,cost:4,showfieldtypesindataeditview:13,turnitinbot:5,port:[3,5,13],appear:[5,13],session_save_path:13,abdullah:8,uniform:3,current:[3,4,5,8,9,10,13],portugues:8,axel:8,navigationtreepointeren:13,chartextarearow:13,portnumb:13,deriv:4,dropdown:5,gener:[0,1,2,3,4,5,8,9,13,14],french:8,slow:[5,13],address:[3,5,13],along:[4,5,2],madhura:8,box:[0,5,13],invit:12,shift:5,rsedwardian_at_gmail:8,bot:5,bbedit:4,behav:5,seward:3,commonli:3,macintosh:3,fpdf:[3,8],stduser:13,extra:4,circumv:5,tweak:4,modul:[3,5],prefer:[8,4,5,13],get_login_credenti:13,fake:13,marker:13,instal:[0,1,4,5,13,6],bookmarkt:13,market:3,httpd:5,memori:[5,13],sake:5,visit:4,perl:[3,5],handler:3,everywher:5,criteria:5,scope:4,translit:13,maxime_at_fre:8,chapter:5,club:8,peopl:[8,9,5,10,12],claus:[5,13],ctype:6,enhanc:8,visual:[8,13],olivi:[8,2],accept:[3,5,13],examin:5,your_theme_nam:5,easiest:[4,5],fly:13,graphic:[3,4,10],ibm:5,dhtml:8,tokyo:5,prepar:[3,13],focu:13,minimum:13,mjane:8,can:[1,2,3,4,5,7,9,10,12,13,14],lc_messag:13,umlaut:5,purpos:[3,13,2],"c\u00e9dric":8,problemat:5,heart:5,querywindowdeftab:13,stream:5,backslash:5,agent:5,"lo\u00efc":8,heard:5,abort:13,spl:6,mediapartn:5,articl:[5,7],occur:5,swedish:8,alwai:[4,5,13,1],differenti:5,web_brows:3,multipl:[13,8,5,10],hungarian:8,sailboat:8,charset:[8,13,1],nyu:8,write:[13,4,5,10],navigationlogolink:13,navipointerbackground:13,anyon:4,tild:5,protectbinari:13,securer:8,allownopassword:13,product:4,mat:8,"m\u00fcller":[8,2],max:5,sql_mode:5,usabl:5,gailli:3,funck_at_googlemail:8,mac:[3,4,5,13],mai:[13,4,5,10,1],underscor:5,data:[1,3,4,5,10,13],grow:1,practic:13,johnson:[8,2],divid:3,explicit:13,inform:[0,1,3,4,5,10,11,12,13,7],"switch":[3,4,5],combin:[5,13],block:[3,5,13],anoth:[3,4,5,13],ordinari:4,simoncini:5,anticip:3,navigationdisplayserv:13,michal_at_cihar:[8,2],db1:13,still:[4,13],pointer:13,dynam:[3,8,5],"p\u00e9ter":8,group:[3,5,13],thank:[8,5],func_uuid:13,polici:13,jim:8,create_tmp_table_priv:4,platform:[3,4,5,13],window:[0,1,3,4,5,8,13],mail:[3,5],main:[0,1,4,5,8,13,14],non:[5,13],recal:13,encod:[8,5,13],initi:[4,5,13],answer:3,first_timestamp:13,safari:5,now:[4,5,13],thilina:8,discuss:5,nor:5,introduct:[0,10,1,11],term:2,glund_at_silversoft:8,name:[1,3,4,5,8,13,14],exectimelimit:[5,13],drop:[1,14,5,8,10,13],separ:[3,8,5,13],gergo314_at_gmail:8,compressonfli:[5,13],compil:5,domain:[5,13],replac:[4,5,13,9],individu:14,continu:13,wrap:13,redistribut:[9,2],sponsor:5,happen:[4,5],bgtwo:13,foreign_kei:3,canada:5,maxrow:13,shown:[5,13],accomplish:10,jackson:8,madlen:8,space:[5,13],internet:[3,4,5,13],ufpdf:[3,8],correct:[5,13],integr:[13,4,5,10],iusr_machin:5,dbc334_at_gmail:8,state:13,migrat:5,xhtml1:8,recodingengin:13,phpmyadmin_x:4,ajax:8,mime:[3,8,5,13,1],superus:[4,14],ruleant_at_us:8,mysqli:[3,8,5,10,13],"byte":13,care:[5,1],recod:13,thbackground:13,wai:[8,4,5,13],unwil:5,emerson4br:8,synchron:[0,5,8],refus:5,domen:8,thing:[5,13],place:[4,5,13],titledatabas:13,wiegger:8,view:[3,14,5,8,10,13],imposs:13,frequent:[3,0,5,13],first:[4,5,13,1,14],origin:[0,6,13,1,8],info:[8,5,13,2],redhat:5,onc:[4,5,13,14],arrai:[5,13,1],reload_priv:4,yourself:4,submit:[5,13],rink:8,spanish:8,open:[3,4,5,12,13,6],predefin:10,country_cod:5,size:[5,13],given:[14,5,13],ian:8,navigationtreeenablegroup:13,convent:1,bookmark:[13,8,4,5,10],maxexactcountview:13,conveni:4,ysajeepan_at_l:8,especi:[4,5,13],copi:[2,4,5,10,13,14],specifi:[8,4,5,13,1],mod_proxi:5,thai:8,than:[13,8,14,5,10],png:5,themenam:13,serv:3,wide:3,przybylski:8,transformationsplugin:1,tecnick:8,balanc:13,were:[8,5,13],posit:[3,5],seri:[3,5],pre:[5,13,1],sai:[5,1],zipdump:13,san:13,bailout_on_error:5,displayserverslist:13,ani:[1,2,3,4,5,8,10,13,14],subroutin:3,userconfig:13,doctyp:5,suhosin:[5,13],saw:5,chrisj_at_ctel:8,engin:[3,5],advic:9,greek:8,tcpdf:[3,8,9,5],note:[4,5,13,1,9],harald:5,take:[5,13,1],advis:13,noth:5,clickjack:13,begin:[5,13],printer:8,ukrainian:8,buffer:[5,13,1],compress:[3,13,5,10],os_x:3,pr1:5,pair:[4,13],homepag:[5,1],icon:[8,14,5,13,1],latex:[3,5,10],path_to_your_phpmyadmin_directori:13,contacto_at_joaodia:8,later:[5,13],serif:13,typeset:3,meanwhil:5,pmapass:4,pattern:13,axi:5,pma_db:4,john2db:5,event:[3,8,10,13],show:[5,13],german:[8,5],tkl:5,russian:8,concurr:13,permiss:[4,5,13,14],threshold:13,foreign_db:13,help:[8,4,5,13,12],xml:[3,8,6,5,10],onli:[1,4,5,8,10,13,14],explicitli:[4,13],ratio:6,romanian:8,activ:[4,5,13],behind:[4,13],internet_information_servic:3,analyz:[5,13],analys:8,offici:[7,4,5,1],reset:[4,5],nearli:13,variou:[0,3,5,8,9,10,13],get:[7,4,5,13,1],ssl:[3,5,13],kiko:8,cannot:[4,5,13,1],martin_at_whistl:8,wasser:5,sascha:8,requir:[0,4,5,10,13,6],truli:4,home_pag:5,output_compress:5,borrow:8,yield:5,email:[8,5],controlpass:[4,5,13],where:[3,4,5,13],wiki:[3,5,7],parse_url:5,numrecentt:13,proce:5,pma__histori:13,korakot:8,hierarchi:[5,13],yacybot:5,progress:[6,5],server_vari:13,logincookievalid:13,blobstream:8,detect:[8,5,13],innov:3,review:4,chapeaux:8,enumer:5,hide_db:13,label:[3,5],enough:4,between:[5,13],"import":[0,3,4,5,8,10,13,6],across:[3,13],parent:5,lsml_at_liv:8,screen:[4,5],cedric:8,retainquerybox:13,come:[4,5,13],img:5,contol:3,allowroot:13,tutori:[7,5,1,11],kanitchet:8,open_basedir:[5,13],mani:[3,4,5,8,10,12,13],fauveau:8,rel_person:5,among:13,googlebot:5,mmcrawler:5,color:13,overview:[5,1],nicola:8,ultim:5,chaovavanich:8,marc:[8,5,7,2],allowdeni:[4,5,13],hardli:4,mark:[3,8,13],barri:8,filterlanguag:13,table_uipref:13,emphas:13,"50x50":5,phpmysqlformgen:8,andrea:8,tamil:8,marconcini:8,tabbrows:5,"case":[4,5,13,14],blowfish:[3,8,4,13],mount:13,references_priv:4,wigginton:8,advantag:5,ctrl:5,marcel:8,kasun:8,cluster:5,zahra:8,setup_dir_writ:9,duke3d_at_ukr:8,ascii:[5,13,1],insert_priv:4,garvin:[8,2],develop:[0,3,5,8,12,13],author:[8,5,13],media:5,statuscachelifetim:13,check:[1,2,14,5,8,9,10,12,13],binari:13,html:[3,8,5,13,1],pma__table_uipref:13,pai:4,update_priv:4,document:[0,3,4,5,8,9,13,6],pan:5,webserv:[3,4,5,13],screenshot:5,nest:[8,5,13],driven:4,capabl:3,set_magic_quotes_runtim:4,http_host:[5,13],improv:[3,8,5,10,13],extern:[0,9,13],tradition:4,om_at_omni:[8,2],appropri:[4,5,10],moder:8,port_:3,edlund:8,without:[5,13,1,2],dimension:3,summer:8,execut:[3,13,5,10],phpmyadmin:[0,1,4,5,7,8,9,10,12,13,6,14],tip:[5,13],aspect:[6,9],polish:8,passphras:13,speed:[4,5,13],defaulttabserv:13,gigabot:5,hint:[5,13],except:[4,5,13],adsbot:5,typo3_at_dirk:8,blob:[13,5,10],save_path:5,vulner:[4,5],table_nam:4,gd2:[3,6],hover:13,around:[4,5,13],yuval:8,read:[13,4,5,10],grid:13,dbase:5,world:[3,4,13],mod:5,setup_config_fil:9,whitespac:5,rel_town:5,integ:13,server:[0,3,4,5,8,9,10,13,6,14],benefit:[5,13],either:[5,13],output:[8,5,13,1],intenet:13,inter:[3,13],grant_priv:4,manag:[0,3,4,5,7,10,11,13,14],yyyi:13,node:[3,8],maxtableuipref:13,turck:5,longtextdoubletextarea:13,availablecharset:13,ascend:13,ashraf:8,charedit:13,showserverinfo:13,authent:[0,4,5,8,13,6],strickroth:8,column_priv:4,jakub:8,martyna:8,confirm:[5,13],gd2avail:13,piankov:8,definit:[5,13,7],rewriteengin:5,legal:13,nokeepal:5,complic:13,tabsmod:13,column_info:[13,1],schaefer:8,timeout:[5,13],olof:8,each:[1,3,4,5,13,14],xzvf:4,broken:5,stdpass:13,found:[4,5,13],navigationtreedbsepar:13,xvnavarro_at_gmail:8,acl:[3,13],precedessor:3,degre:13,mjaning_at_gmail:8,lucen:5,addtyp:5,backup:5,routin:[8,13],chartextareacol:13,effici:[5,13],logouturl:13,usergroup:13,yyi:13,your:[0,1,4,5,10,13,6,14],lolo_at_phpheaven:8,opengi:8,log:[3,13,4,5,10],her:5,area:[4,5,13],hex:[5,13],ysajeepan:8,overwrit:13,start:[8,4,5,13],compliant:8,interfac:[3,8,6,5,13],low:[4,5,13],mbstring:[6,13],ipv6:[3,13],submiss:13,proxyus:13,machin:[3,4,5,13,6],hindi:8,hard:13,tupl:3,bundl:5,regard:[10,1],upload_max_files:5,cryptograph:3,realli:[4,5,13],adler:3,pull:13,largest:5,possibl:[4,5,13,1,12],"default":[0,1,3,4,5,13,6],textarearow:13,embed:13,expect:5,gone:5,scanner:5,creat:[3,4,5,8,10,11,13,14],certain:[3,13,1],certail:5,mike:8,strongli:[6,5,13],plathei:8,localnetc:13,file:[1,3,4,5,8,9,10,11,13,6],"mickevi\u010diu":8,encompass:3,fill:[4,5,13],incorrect:5,again:[4,5],showdbstructurelastupd:13,googl:[8,4,5,13],commiss:3,corazza_at_wanadoo:8,tbl_row_delet:5,field:[0,1,3,4,5,13],valid:[0,4,5,8,10,13],writabl:[4,13,9],you:[1,2,4,5,7,8,9,10,12,13,6,14],architectur:5,openid:13,resolut:3,sequenc:3,symbol:5,pear:[3,8,5],allowoverrid:5,ansi:5,tables_priv:[4,13],viliu:8,lineup:13,gzencod:5,directori:[0,1,3,4,5,9,13],descript:[5,13,1],papaz_p_at_yahoo:8,chown:[5,13],adam:8,potenti:13,escap:13,unset:5,file_upload:5,all:[1,3,4,5,9,10,12,13],consider:4,gupta:8,browsemarkeren:13,code:[3,8,4,5,13],yavuz:8,follow:[8,4,5,13],disk:4,minsizeforinputfield:13,scp:5,net_url:5,iconv:13,rewrot:8,intext:5,former:[8,13],alberti:8,program:[3,13,2],those:[4,5,13,1,14],introduc:[8,5,13],liter:[5,13],fals:[9,5,13],faq:[3,0,5],suhosindisablewarn:[5,13],util:[3,5,13],leiding:8,ichiro:5,verb:5,mechan:[8,4,5,13],veri:[5,13],longtext:13,list:[3,9,5,13,7],resav:4,adjust:[5,13],plain:[4,5,13,1],pbm:8,tbl_creat:8,enterpris:[4,5],homonym:5,internetwork:3,template_abstract:1,path_to_phpmyadmin:5,sync:8,controlhost:13,tex:[3,5],zero:13,design:[0,3,4,5,8,13],pass:[13,14,5,10,1],further:[8,13],ajaxifi:8,hick:[8,2],cursor:[5,13],what:[1,4,5,10,13,14],sub:5,request_for_com:3,me_at_derrabu:[8,2],sun:8,section:[4,5,13,1,12],abl:[8,4,5,13],u4663530:8,delet:[4,5,13,14,11],version:[0,2,3,4,5,8,9,10,13,6],"public":[4,5,13,2],full:[4,5,13,14],hash:5,berkelei:13,behaviour:5,shouldn:4,modifi:[5,13,2],valu:[3,4,5,13,1],ignoremultisubmiterror:13,d3xter_at_us:8,search:[13,0,5,10,8],thememanag:[5,13],yukihiro:8,amount:[5,13,1],pick:5,bussier:8,showcreatedb:13,lock_tables_priv:4,via:[4,5,13,14],janni:8,put:[4,5,13,1],voyag:5,armel:8,famili:13,select:[1,4,5,8,13,14],esri:[5,13],hexadecim:13,khan:8,ga244_at_is8:8,distinct:[5,13],msnbot:5,two:[3,4,5,13],coverag:5,pmahomm:[5,13],taken:5,remembersort:13,ntf:13,minor:[8,5],more:[3,4,5,13,2],reachabl:13,bulgarian:8,desir:10,kristof:8,hundr:5,mozilla:5,oleg:8,particular:[3,4,14,2],known:[3,0,9,5,13],destin:3,"bokm\u00e5l":8,cach:[5,13],town:5,none:13,der:8,dev:[3,4,5],histori:[8,4,13],remain:[5,13],paragraph:5,rel_countri:5,kosit:8,abram:8,renato:8,omit:13,prompt:[4,13],scan:5,share:4,templat:1,max_totalname_length:5,chimera:5,explor:[4,5,13],tablesepar:5,action:[14,5,13],computer_socket:3,uncheck:[5,13],huge:[5,13],cours:[4,5,13],xxxx:13,newlin:13,secur:[0,1,3,4,5,13],yuichiro_at_pop07:8,hidestructureact:13,spreadsheet:[6,10],reject:13,csv:[3,5,10],simpl:[3,4,5],css:[5,13],resourc:3,unprivileg:13,reflect:3,sven:8,okai:4,mendel:[8,2],mariadb:[6,5],associ:4,pma__rec:13,svec:8,tablenavigationlinksmod:13,mous:[5,13],"short":3,john1db:5,footer:13,themepath:[5,13],caus:[6,5,13],parti:[4,5,7],checkbox:[14,5,13],rotat:13,hypertext:3,soon:5,soom:8,paper:[5,13],through:[3,13,5,10,1],same:[4,5,13,14],urdu:8,ipmask:13,"montr\u00e9al":5,paramet:[5,13,1],style:[4,5,13],arial:13,db_structur:[5,13],ip_address:3,me_at_supergarv:[8,2],ricardo:8,mcryptdisablewarn:13,bypass:[5,13],might:[4,5,13,9],alter:[13,5,10],good:[4,5,13,1],"return":[5,13,1],pma__usergroup:13,timestamp:[4,13],disable_funct:13,defaulttabt:13,libwww:5,rewriterul:5,ukrain:8,framework:9,botelho:8,rwx:[5,13],bigger:5,troubleshoot:5,userid:5,unlik:4,refresh:[5,13],easili:[4,5,13],achiev:13,tamsjadi:8,innodb:[13,5,10],fulli:5,unicod:3,truncat:13,neil:8,wasn:8,idea:[8,4,5,13,1],procedur:[3,5,10],slowdown:5,connect:[0,3,14,5,10,13],http:[2,3,4,5,8,13],beyond:4,orient:[3,5],ftp:[4,5,13],marcin:8,robert:8,publish:[13,2],research:3,footnot:10,print:[7,13,11],occurr:5,msie:5,benjamin:8,difficulti:10,proxi:[4,5,13],advanc:[4,13],browsepointerbackground:13,differ:[1,3,4,5,8,10,13],asc:13,quick:[3,0,4,5,13],reason:[5,13],base:[3,6,5,8,9,13],ask:[3,0,5,13],ash:8,english:[8,4,13],repl_slave_priv:4,bakondi:8,perhap:4,pma__rel:5,vainauska:8,lifetim:13,assign:[14,5,13,11],feed:5,major:5,upper:5,feel:4,exchang:3,lastnam:5,number:[3,5,13],placehold:5,alexandr:8,done:[5,13],least:[4,5,13],blank:[5,13,1],stabl:5,miss:[5,13,1],ssl_ca_path:13,collat:13,mail_mim:5,interact:3,construct:5,http_cooki:3,station:5,adriaenssen:8,statement:[13,4,5,10],cfg:[4,5,13],enclos:5,ton:13,store:[1,3,4,5,8,10,13],schema:[8,5,13],option:[0,4,5,13,1],relationship:5,girish:8,villanueva:8,feryanto:8,part:[3,13,5,10],pars:[5,13,1],naviwidth:13,webadmin:8,exposur:4,kind:[3,5,13],sta:8,whenev:[5,13],remot:3,remov:[8,4,5,13,14],rsedwardian:8,horizont:[3,5,13],jqueri:9,darlow:8,kindli:5,acko:[3,8],pma:4,comput:3,well:[13,8,4,5,10],wysiwyg:8,packag:[0,4,5,9],translationwarningthreshold:13,mod_rewrit:5,"null":[5,13],wilson:8,unintend:13,bz2:[13,5,10],equival:13,self:[4,13],also:[1,3,4,5,8,9,10,13,6,14],build:[4,5,13,1],kraai:8,http_user_ag:5,upload_tmp_dir:5,distribut:[0,2,4,5,8,9,13],func_dat:13,previou:[4,5,13],reach:1,chart:[8,5],most:[1,3,4,5,8,13,6],plai:4,myisam:[5,10],alpha:5,max_var:5,kempf:8,charg:13,filesystem:13,clear:[4,5],qbe:[8,10],usual:[3,4,5,13,1],microsoft:[3,5,10],visibl:[5,13],paul:5,think:5,mimer:[5,13],carefulli:13,consult:1,column_:3,session:[4,5,13,6],marc_at_infomarc:[8,2],font:[5,13],fine:5,find:[4,5,13],impact:[4,5,13],access:[3,4,5,8,13,6],beck_at_web:8,pretti:8,scooter:5,solut:[5,13],clipboard:[5,13],iec:[3,10],first__second__third:13,monospac:13,tyron:8,max_array_index_length:5,express:[3,5,13],setenvif:5,gandon_at_isia:8,nativ:5,mainten:[8,10,13],generator_main_class:1,him:8,restart:5,target:5,browsepointercolor:13,ie6:13,rfc:[3,5,10],pundalik:8,defaulttabdatabas:13,common:[3,5,13],nowher:13,kleemann:8,certif:13,set:[0,1,3,4,5,8,9,10,13],dump:[13,8,5,10],creator:8,psbot0:5,see:[1,2,3,4,5,8,10,13,6],seo:5,reserv:13,browsemarkercolor:13,alexukf:8,lee:8,someth:[4,5,13],won:[5,13],gd_graphics_librari:3,opera7:5,opera6:5,ssl_cipher:13,navigationtreetablesepar:[5,13],altern:[4,5,13],numer:[5,13],serverdefault:13,javascript:[8,6,5,13],navigationhid:13,disallow:[4,13],lowercas:[4,5],solv:5,wiltav:8,both:[4,5,13],cryptic:5,last:[5,13,1],mod_gzip_item_includ:5,foreign:[3,13,5,10],context:5,pdf:[0,3,4,5,8,10,13],whole:[13,4,5,10],load:[13,4,5,10,6],simpli:[4,5,13,14],point:[3,5,13],utf8_general_ci:13,joaotmdia:8,documentclass:5,shutdown:5,suppli:[4,13],desktop:5,shanyan:8,java:5,sessionsavepath:13,navigationlogolinkwindow:13,devic:3,due:[5,13],empti:[5,13],secret:13,senderrorreport:13,memory_limit:5,bzip:5,org:[3,8,5,13,2],repeatcel:13,mind:4,imag:[1,3,6,5,10,13],great:[8,12],coordin:[5,13],understand:[4,5,10],paulei:8,mkkeck_at_us:[8,2],remote_us:5,setfacl:13,batch:10,"while":[5,13,1],match:[5,13],behavior:[9,5,13],error:[8,4,5,13],anonym:[5,13],everyon:8,pack:5,real:[5,13],mac_os_x:3,readi:4,jpg:3,asuni:8,defaultconnectioncol:13,cento:4,alter_priv:4,attach:[3,13],ignacio:8,fedora:4,minim:[4,5],shorten:5,shorter:5,conflict:5,higher:5,signon:[4,13],veeven_at_gmail:8,proxypassreversecookiepath:5,optim:[8,5,13],ssl_kei:13,alert:5,"tom\u00e1\u0161":8,temporari:[4,5,13],winhttp:5,maxdblist:13,winnt4:5,recent:[3,4,5,13],lower:[5,13],drop_priv:4,lib:[4,5,1,9],older:[0,4,5,13],kelli:8,entri:[4,13],searchabl:4,person:[3,5,13],userstatu:5,mandriva:4,vazquez:8,lin:8,propos:[5,10],explan:[4,5],from:[0,1,3,4,5,8,10,13,14],table_:3,administ:10,rutkowski:8,snappi:5,getinfo:1,shortcut:13,mydatabas:13,input:[4,5,13],useless:5,bin:[8,5],checklink:5,thilanka:8,varchar:[5,13],format:[1,3,4,5,8,10,13],big:[5,13],catalan:8,tabl:[0,1,3,4,5,8,10,13,14],lanin:8,backquot:5,insert:[8,4,5,13],bit:[6,13],oleg_at_gmail:8,lost:[5,13],table_info:13,resolv:5,collect:3,"boolean":13,proxypassrevers:5,popular:3,engstrom:8,tbl_sql:13,vendor_config:9,often:[5,13],creation:[8,13],some:[1,3,4,5,8,9,13],back:[3,4,5,14],scratchboard:[8,5],proxyurl:13,sampl:[4,5,13],pmadb:[5,13],mirror:5,scale:5,pratap:8,bruce:3,larg:[5,13],slash:13,reproduc:5,cgi:[3,4,5],object:[3,1],run:[3,4,5,13],zzz:13,ummer:5,weis:8,cochran:8,step:[4,5],bruguera:8,impos:13,blowfish_:3,mmcach:5,delisl:[8,7,2],captchaloginpublickei:[4,13],func_char:13,mcrypt:[3,4,5,13,6],italian:8,gamma:5,allowthirdpartyfram:13,person_nam:5,displaybinaryashex:13,within:[14,5,13],row_:3,slovak:8,klokner:8,steven:8,ensur:[4,5,13],chang:[1,3,4,5,8,9,10,13,14],pcre:[3,5,13],server_:3,cj_at_gmail:8,sock:5,fast:5,custom:[0,1,5,8,9,13],includ:[1,3,4,5,8,9,13],suit:[3,5],forward:[5,13],stoyanst:8,properli:[4,5,10],electrotechn:3,usedbsearch:13,chaudhari:8,link:[1,4,5,8,10,13,14],translat:[0,5,13,8],newer:[4,5,13,6],upright:5,line:[3,4,5,13,1],torsten:8,trustedproxi:13,utf:3,consist:5,mrbendig:8,hamann:8,reorder:5,mj12bot:5,highlight:13,similar:[4,5,13],enlarg:5,parser:8,chao:8,doesn:[5,13,1],repres:[3,5,13],"char":[5,13],thomsen:8,incomplet:13,tar_:3,overcom:5,guarante:13,curl:5,rewritecond:5,titl:[0,13],invalid:[5,13],town_cod:5,nick:5,urltrend:5,tempdir:[5,13],proxypass:[5,13],navipointercolor:13,show_db_priv:4,heis:5,drag:5,captchaloginprivatekei:[4,13],clue:5,lang:[5,13],titlet:13,algorithm:[3,4,13],mimetyp:[5,13,1],rewritebas:5,sherbrook:5,depth:5,foreignkeydropdownord:13,errorcod:5,scroll:5,albiol:8,partial:13,queri:[0,3,4,5,8,10,13],steve:8,broader:3,edu:8,funck:8,privat:[5,13],bzipdump:13,turkish:8,sensit:13,only_db:[4,13],wizard:4,send:[5,13],headerfliptyp:13,becam:13,sens:1,sent:[3,8,5,13],deactiv:[5,13],unzip:4,logincookiedeleteal:13,zigmanta:8,master_db:13,implicitli:3,portable_document_format:3,relev:13,net_socket:5,tri:[4,5,13],ajaxif:8,button:[4,5,13],michael:[8,2],stuffit:4,fewer:13,"try":[4,5,13],mathia:8,alia:8,queryhistorymax:13,refer:[3,5,13],pleas:[4,5,13,1,9],impli:2,smaller:[8,5,13],visualis:5,natur:13,wors:5,noblob:13,uniqu:[3,5],jump:13,defaultdisplai:13,gmbh:5,user_prefer:13,odn:8,tobias_at_ratschil:[8,2],download:[4,10],ratschil:[8,2],click:[14,5,13,1],append:[13,1],victor:8,compat:[3,4,5,8,9,6],index:[0,3,4,5,8,10,13],ia_archiv:5,computer_sci:3,cell:[3,5,13],experiment:13,isapi:[3,4,5],typo3:8,gheni:8,chose:[4,5],tracking_add_drop_databas:13,vinai:8,internet_protocol:3,firewal:[4,5,13],logout:13,ubuntu:[4,13],vertic:[3,8,13],sinc:[8,4,5,13,6],pma_dbi:8,convert:5,didn:13,copyright:[0,2],themedefault:[5,13],baishui:8,larger:[5,13],technolog:[3,5],delorm:8,cert:13,typim:8,auth_kei:4,typic:[3,5],forkik_at_gmail:8,control:[3,4,5,8,13,14],bzip2:[3,13,5,10],firefox:5,haa:8,appli:[4,5,13,1],approxim:[5,13],foundat:2,gatewai:3,api:[3,5],seosearch:5,apc:[5,13],pma__track:13,zerofil:1,usb:4,zip:[3,6,5,8,10,13],commun:[3,10],doubl:[5,13],upgrad:[0,4,5],intitl:5,next:[4,5,13],websit:[3,8,5,12],few:[4,5,13],lesli:8,webcrawl:5,ninad:8,sort:[3,5,13],pencil:14,client_:3,browsemarkerbackground:13,newsblog:5,pdfpages:13,trail:13,defaultqueryt:13,central:[4,5,13],harvest:5,abtract:1,account:[10,13],john_db:5,retriev:13,gabriel:8,ne0x_at_us:8,when:[4,5,8,10,13,6],showal:13,opensource_at_jth:8,obvious:4,"j\u00f8rgen":8,scatter:5,employe:5,user_bas:4,weaker:13,tar:[3,4],process:[3,14,5],lock:[5,13],codemirroren:13,high:[5,13],auth_http_realm:13,tag:5,jeev:5,tab:[0,5,13],opensourc:8,onlin:5,delai:5,andersen:8,siu:8,slovenian:8,subdirectori:[4,5,13],instead:[4,5,13],chri:8,stock:4,pink:5,klau:5,gentoo:4,attent:4,mod_ssl:5,nordenberg:8,alloc:13,ssl_cert:13,walton_at_nordicdm:8,light:13,astarita:8,correspond:[5,13,1],element:[3,5,1],issu:[5,13],encyclopedia:3,allow:[3,4,5,8,10,13,14],subtyp:1,fallback:13,cbb74bc:4,include_path:5,me_at_mynetx:8,move:[4,5,13],iconvextraparam:13,pruett:8,comma:3,korakot_at_inam:8,themeperserv:13,bunch:5,fontfamili:13,edlund_at_upright:8,till:[4,5],chosen:5,wget:5,fujifilm:8,clickabl:[5,13],ukf_at_gmail:8,dorning:5,therefor:13,pixel:13,crash:5,handl:[14,5,13,1],auto:[5,13],dan:8,auth:[4,5,13],repositori:[3,4],mention:[4,5],geert:8,krukowski:8,somewher:5,anyth:4,edit:[0,4,5,10,11,13,14],mode:[0,4,5,8,13,6],beneath:5,register_glob:5,showphpinfo:13,subset:10,strip:[9,5,13],consum:3,drizzl:[8,6,5,13],httrack:5,our:[1,5,8,7,12,13],navigationtreetablelevel:13,patch:[8,13],tracking_add_drop_view:13,special:[3,4,5,13],out:[3,4,5,13,12],variabl:[5,13,1],gandon:8,showstat:13,acunetix:5,naeem:8,index_:3,rel:5,hardwar:13,sp2:5,red:4,statist:[8,5,13],insid:[4,5,13,1,9],manipul:[3,10,13],querywindowwidth:13,transliter:13,releas:[8,5,13],mysql_pconnect:13,designer_coord:13,could:[4,5,13],david:8,navigationdisplaylogo:13,length:[5,13],enforc:13,organis:3,db_sql:13,outsid:[4,13,1],transfer:[3,4,5,13],geometri:5,manuzhai:8,endors:7,baiduspid:5,suffix:13,christoph:8,brute:5,exact:13,date:[5,13],muhammad:8,buggi:[4,5],netcologn:5,owner:[4,13],showchgpassword:13,suffic:4,alioglu:8,"long":[3,5,13,1],strict:13,rouslan_at_placella:8,unknown:5,licens:2,mkdir:[4,5],system:[3,4,5,8,9,13],messag:[4,5,13],ssloption:5,fontfamilyfix:13,attack:[4,5,13],appl:3,lower_case_table_nam:5,pavel:8,rfc2616_header:5,lacina:8,ipv4:3,shell:5,lot:[5,13],methodolog:3,slider:13,obgzip:[5,13],textfield:1,haven:5,gzipdump:13,photograph:3,structur:[0,1,3,5,10,11,13],charact:[3,4,5,13,1],htaccess:[3,4,5],seriou:5,stoyanster_at_gmail:8,upload_progress:5,julian:3,respons:3,robot:[4,5],sysadmin:[5,13],miquel:8,have:[1,2,4,5,8,10,12,13,14],ari:8,close:[5,13],turn:[5,13],border:13,bzcompress:5,afraid:4,rout:13,browsemim:13,which:[1,3,4,5,10,13,6,14],singh:8,exabot:5,singl:[3,13,4,5,10],"5b4":5,brazil:8,untar:4,unless:[4,5],transmit:5,who:[1,4,5,8,9,10,13],discov:4,cipher:[3,13],buddika:8,nutchcv:5,stdenvvar:5,herman:8,placement:5,secure_sockets_lay:3,url:[3,5,13],uploaddir:[5,13],uro:8,showhint:13,face:5,pipe:13,deni:[8,4,5,13],snapshot:13,determin:[14,5,13],built:[8,5],index_priv:4,fact:[5,13],dbm:3,abeyrathna:8,text:[0,1,3,4,5,8,10,13],verbos:[5,13],dbg:13,dbf:5,bring:[4,5],turek:[8,2],"mat\u00eda":8,controlus:[4,5,13],redirect:[5,13],textual:5,locat:[3,4,5],auth_typ:[4,5,13],should:[2,4,5,8,9,13],manufactur:3,bernard:8,tape:3,local:[4,5,13],"lu\u00ed":8,hope:2,contribut:[8,5,12],autom:[8,4],recode_str:13,drawn:1,increas:[5,13],db2:13,awai:5,takahashi:8,shp:5,enabl:[1,3,4,5,13,6],organ:3,showasphp:13,navarro:8,stuff:8,she:5,contain:[4,5,13,1],grab:5,mynam:5,statuscachedatabas:13,kawada:8,conform:3,win98:5,frame:[8,5,13],mod_gzip:5,packet:3,malfunct:5,dialog:[4,5],alessandro:8,temporarili:5,tracking_add_drop_t:13,statu:[8,5,13],convers:[8,13],pdfdefaultpages:13,correctli:[5,13],mainli:3,nopassword:13,dll:5,written:[5,13,12],default_socket:5,boutel:3,navigationtreedisplaydbfilterminimum:13,longtabl:5,neither:[5,13],thumbnail:6,classless:13,kei:[3,4,5,8,10,13],smart:13,job:4,entir:[4,5,6],joe:[8,5],webapp:4,server_privileg:13,addit:[3,8,4,5,13],plugin:[8,5,1],wisenutbot:5,libiconv:13,etc:[4,5,13,1],instanc:13,admir:8,tschopp:8,strftime:5,comment:[3,8,4,5,13],hyphen:5,chmod:[4,5,13],defaultquerydatabas:13,respect:5,gheni_at_yahoo:8,withdrawn:[8,5],unclean:5,quit:[8,5,13],disablemultitablemainten:13,"10_at_windowsl":8,addition:13,compos:13,signonscript:[4,13],json:[6,5],slashdot:13,treat:5,logincookiestor:13,infrastructur:4,filippo:5,adob:3,reservedworddisablewarn:13,moreov:5,togeth:[4,5,13],present:[5,13],hitowerdigit_at_hotmail:8,replic:8,multi:[0,3,4,5,8,13],tbl_chang:[8,13],showdbstructurecr:13,"\u010diha\u0159":[8,4,2],align:5,yuichiro:8,harden:5,defin:[3,14,5,13,1],wilk:8,glossari:[3,0],server_databas:13,ini:[13,5,10],layer:3,purchas:4,dieter:8,almost:13,demo:13,site:[4,5,13,7],skiplockedt:13,archiv:[3,5],incom:4,revis:[3,5],pmaabsoluteuri:[5,13],let:[5,13],welcom:[0,4,5],japanes:8,stored_procedur:3,referenti:[10,13],cross:[3,4,13],python:5,pma_password:5,select_priv:4,abravo_at_hq:8,linklengthlimit:13,probabl:[4,5,9],difficult:5,proxypassreversecookiedomain:5,stankruk_at_neostrada:8,mousewheel:5,hostnam:[3,5,13],expans:5,effect:[7,5,13,1],naturalord:13,thcolor:13,php:[0,1,3,4,5,8,9,10,13,6],codemirror:13,login:[4,5,13],expand:5,keep:[4,5,13],real_us:4,off:[5,13],center:[4,5],nevertheless:4,builder:8,jayaratn:8,real_password:4,exampl:[4,5,8,9,10,13],command:[4,5,13],choos:[4,5,13,1],undefin:5,fail:[5,13],metagerbot:5,yasir:8,latest:[3,5,13],mehboobbugti_at_gmail:8,newest:13,cspallow:13,less:[4,5,13],obtain:13,tcp:[3,5,13],gettext:9,skill:5,robert_readman_at_hotmail:8,web:[0,3,4,5,8,9,13,6],mainbackground:13,smith:5,script:[8,4,5,13,9],add:[4,5,13,14],other:[0,1,3,4,5,7,9,10,11,12,13,14],uighur:8,showsql:13,pma__column_com:13,kick:5,css2:8,bigdump:5,rememb:[5,13],max_request_vari:5,setinputfilt:5,pma__table_info:[5,13],know:[5,1],redesign:8,tick:13,desc:13,konqueror:5,like:[1,4,5,8,10,13],success:5,header:[5,13],pma__column_info:13,necessari:[5,1],navibackground:13,martin:8,separated_valu:3,resiz:5,page:[0,1,3,4,5,6,8,7,13,14],crawler:5,revers:[5,13],prepend:1,convei:3,jan:8,"kozio\u0142":8,linux:[0,4,13,9],"landh\u00e4u\u00df":8,"export":[13,0,5,10,8],unstuff:4,proper:[13,4,5,10,1],home:14,fileinfo:5,peter:8,laurent:8,librari:[0,1,3,4,5,8,9,13,6],tmp:[5,13],win32:[4,5],guid:[0,7,11],my_db:13,lead:5,avoid:[5,13],phpinfo:[5,13],octet:5,tooltip:5,leav:[5,13,1],lord_dark_at_wp:8,backend:5,kiddi:5,getnam:1,"enum":[8,1],usag:[1,4,5,11,13,14],host:[1,3,4,5,8,13],although:[8,5],"7euser":5,panel:[0,4,5,13],about:[0,1,4,5,10,13],misbehav:5,actual:[5,1],socket:[3,5,13],kawada_at_den:8,column:[1,3,4,5,8,10,13],andersen_at_gmail:8,zip_:3,localnetb:13,localneta:13,disabl:[4,5,13],not_nul:1,own:[13,4,5,10,1],sarna:8,actionlinksmod:13,transformation_overview:1,automat:[3,4,5,8,10,13],create_t:4,warranti:2,lund:8,robbat2_at_us:[8,2],generator_plugin:1,sander:8,w3c:[3,5],van:8,panagioti:8,much:1,downgrad:5,kang:8,"var":[9,5],favorit:[4,5],"function":[1,3,5,8,10,13],unexpect:5,alexi:8,lossi:3,soulard:8,eas:1,inlin:6,bug:[8,5,13],piller:8,count:[5,13],made:[3,8,5,13],database_trigg:3,whether:[14,13,9],wish:[4,13,14],defaultfunct:13,displai:[0,1,3,4,5,8,10,13,6],troubl:[5,13],record:[3,14],below:[4,5,13,1],meta:[13,1],limit:[0,4,5,13],shutdown_priv:4,otherwis:[5,13],problem:[3,4,5,13],significantli:6,epel:4,"int":5,ilnytskyi:8,dure:[5,13],filenam:[5,1],implement:[3,13,1],erik:[8,5],sinhala:8,error_handl:13,stefan_at_inkopsforum:8,nakandala:8,detail:[4,5,13,2],logincookierecal:13,book:[7,11],futur:[5,13],branch:[4,5],php4:[8,5],blowfish_secret:[4,13],php3:8,repeat:[5,13],star:8,mysql_upgrad:5,"class":[5,1],june:8,kanichet_at_hotmail:8,server_statu:13,showpropertycom:13,debian:[4,13],gergo314:8,markup:3,outsourc:8,fund:4,reliabl:5,kanji:8,rule:[4,5,13,1],post_max_s:5,portion:13,emerg:4,rajandran:8,decemb:8,phpwizard:[8,5]},objtypes:{"0":"config:option"},titles:["Welcome to phpMyAdmin’s documentation!","Transformations","Copyright","Glossary","Installation","FAQ - Frequently Asked Questions","Requirements","Other sources of information","Credits","Distributing and packaging phpMyAdmin","Introduction","User Guide","Developers Information","Configuration","User management"],objnames:{"0":["config","option","Config config option"]},filenames:["index","transformations","copyright","glossary","setup","faq","require","other","credits","vendors","intro","user","developers","config","privileges"]}) \ No newline at end of file diff --git a/sources/doc/html/setup.html b/sources/doc/html/setup.html new file mode 100644 index 0000000..18e8cce --- /dev/null +++ b/sources/doc/html/setup.html @@ -0,0 +1,549 @@ + + + + + + + + + + Installation — phpMyAdmin 4.1.4 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

      +

      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.

      +
      +
      +

      OpenSUSE

      +

      OpenSUSE already comes with phpMyAdmin package, just install packages from +the openSUSE Build Service.

      +
      +
      +

      Ubuntu

      +

      Ubuntu ships phpMyAdmin package, however if you want to use recent version, you +can use packages from +PPA for Michal Čihař.

      +
      +
      +

      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.

      +
      +
      +

      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. 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.
      4. +
      5. 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.
      6. +
      7. 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.
      8. +
      +
      +

      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 +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 need a few directives to get going, a +simple configuration may look like this:

      +
      <?php
      +$cfg['blowfish_secret'] = 'ba17c1ec07d65003';  // use here a value of your choice
      +
      +$i=0;
      +$i++;
      +$cfg['Servers'][$i]['auth_type']     = 'cookie';
      +?>
      +
      +
      +

      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';
      +?>
      +
      +
      +

      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 the Setup +Script. First you must manually create a folder config +in the phpMyAdmin directory. This is a security measure. On a +Linux/Unix system you can use the following commands:

      +
      cd phpMyAdmin
      +mkdir config                        # create directory for saving
      +chmod o+rw config                   # give it world writable permissions
      +
      +
      +

      And to edit an existing configuration, copy it over first:

      +
      cp config.inc.php config/           # copy current configuration for editing
      +chmod o+w config/config.inc.php     # give it world writable permissions
      +
      +
      +

      On other platforms, simply create the folder and ensure that your web +server has read and write access to it. 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. can help with +this.

      +

      Next, open setup/ in your browser. If you have an existing configuration, +use the Load button to bring its content inside the setup panel. +Note that changes are not saved to disk until explicitly choose ``Save`` +from the Configuration area of the screen. Normally the script saves the new +config.inc.php to the config/ directory, but if the webserver does +not have the proper permissions you may see the error “Cannot load or +save configuration.” Ensure that the config/ directory exists and +has the proper permissions - or use the Download link to save the +config file locally and upload it (via FTP or some similar means) to the +proper location.

      +

      Once the file has been saved, it must be moved out of the config/ +directory and the permissions must be reset, again as a security +measure:

      +
      mv config/config.inc.php .         # move file to current directory
      +chmod o-rw config.inc.php          # remove world read and write permissions
      +rm -rf config                      # remove not needed directory
      +
      +
      +

      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. +
      5. You should deny access to the ./libraries and ./setup/lib +subfolders in your webserver configuration. For Apache you can use +supplied .htaccess file in that folder, for other webservers, you should +configure this yourself. Such configuration prevents from possible +path exposure and cross side scripting vulnerabilities that might +happen to be found in that code.
      6. +
      7. It is generally good idea to protect 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?.
      8. +
      +
      +
      +
      +

      phpMyAdmin configuration storage

      +

      For a whole set of new features (bookmarks, comments, SQL-history, +tracking mechanism, PDF-generation, column contents transformation, +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).

      +

      Please look at your ./examples/ 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 examples/upgrade_tables_mysql_4_1_2+.sql +and then create new tables by importing +examples/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 examples/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 with the proper rights to those tables (see section +Using authentication modes below).

      +
      +
      +

      Upgrading from an older version

      +

      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 6, 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.

      +

      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 +examples/upgrade_tables_mysql_4_1_2+.sql.

      +
      +
      +

      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 blowfish algorithm, in a temporary cookie.

      +

      For ‘HTTP‘ and ‘cookie’ modes, phpMyAdmin needs a controluser that has +only the SELECT privilege on the `mysql`.`user` (all columns except +`Password`), `mysql`.`db` (all columns), `mysql`.`host` (all columns) and +`mysql`.`tables_priv` (all columns except `Grantor` and `Timestamp`) tables. +You must specify the details for the controluser in the config.inc.php +file under the $cfg['Servers'][$i]['controluser'] and +$cfg['Servers'][$i]['controlpass'] settings. The following +example assumes you want to use pma as the controluser and pmapass as +the controlpass, but this is only an example: use something else in your +file! Input these statements from the phpMyAdmin SQL Query window or +mysql command–line client. Of course you have to replace localhost with the +webserver’s host if it’s not the same as the MySQL server’s one.

      +
      GRANT USAGE ON mysql.* TO 'pma'@'localhost' IDENTIFIED BY 'pmapass';
      +GRANT SELECT (
      +Host, User, Select_priv, Insert_priv, Update_priv, Delete_priv,
      +Create_priv, Drop_priv, Reload_priv, Shutdown_priv, Process_priv,
      +File_priv, Grant_priv, References_priv, Index_priv, Alter_priv,
      +Show_db_priv, Super_priv, Create_tmp_table_priv, Lock_tables_priv,
      +Execute_priv, Repl_slave_priv, Repl_client_priv
      +) ON mysql.user TO 'pma'@'localhost';
      +GRANT SELECT ON mysql.db TO 'pma'@'localhost';
      +GRANT SELECT ON mysql.host TO 'pma'@'localhost';
      +GRANT SELECT (Host, Db, User, Table_name, Table_priv, Column_priv)
      +ON mysql.tables_priv TO 'pma'@'localhost';
      +
      +
      +

      If you want to use the many new relation and bookmark features:

      +
      GRANT SELECT, INSERT, UPDATE, DELETE ON <pma_db>.* TO 'pma'@'localhost';
      +
      +
      +

      (this of course requires that your phpMyAdmin configuration storage be set up).

      +

      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

      + +
      + +
      +

      Signon authentication mode

      +
        +
      • This mode is a convenient way of using credentials from another +application to authenticate to phpMyAdmin.
      • +
      • The other application has to store login information into session +data.
      • +
      + +
      +
      +

      Config authentication mode

      +
        +
      • This mode is 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). But you don’t need to setup +a “controluser” here: using the $cfg['Servers'][$i]['only_db'] might be enough.
      • +
      • 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).
      • +
      +
      +
      +

      Swekey authentication mode

      +

      The Swekey is a low cost authentication USB key that can be used in +web applications. When Swekey authentication is activated, phpMyAdmin +requires the users’s Swekey to be plugged before entering the login +page (currently supported for cookie authentication mode only). Swekey +Authentication is disabled by default. To enable it, add the following +line to config.inc.php:

      +
      $cfg['Servers'][$i]['auth_swekey_config'] = '/etc/swekey.conf';
      +
      +
      +

      You then have to create the swekey.conf file that will associate +each user with their Swekey Id. It is important to place this file +outside of your web server’s document root (in the example, it is +located in /etc). A self documented sample file is provided in the +examples directory. Feel free to use it with your own users’ +information. If you want to purchase a Swekey please visit +http://phpmyadmin.net/auth_key +since this link provides funding for phpMyAdmin.

      + +
      +
      +
      +

      Securing your phpMyAdmin installation

      +

      The phpMyAdmin team tries hardly to make the application secure, however there +are always ways to make your installation more secure:

      + +
      +
      + + +
      +
      +
      + +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/transformations.html b/sources/doc/html/transformations.html new file mode 100644 index 0000000..49e50d1 --- /dev/null +++ b/sources/doc/html/transformations.html @@ -0,0 +1,240 @@ + + + + + + + + + + Transformations — phpMyAdmin 4.1.4 documentation + + + + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +

      Transformations

      +
      +

      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’”.
      • +
      +
      +
      +

      File structure

      +

      All specific transformations for mimetypes are defined through class +files in the directory ‘libraries/plugins/transformations/’. Each of +them extends a certain transformation abstract class declared in +libraries/plugins/transformations/abstract.

      +

      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/plugins/transformations/TEMPLATE and +libraries/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 +libraries/plugins/transformations/generator_plugin.sh or +libraries/plugins/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. +
      +
      +
      + + +
      +
      +
      +
      +
      +

      Table Of Contents

      + + +

      Previous topic

      +

      User Guide

      +

      Next topic

      +

      User management

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/user.html b/sources/doc/html/user.html new file mode 100644 index 0000000..0e65f44 --- /dev/null +++ b/sources/doc/html/user.html @@ -0,0 +1,135 @@ + + + + + + + + + + User Guide — phpMyAdmin 4.1.4 documentation + + + + + + + + + + + + + + + + +
      + +
      +
      +

      Previous topic

      +

      Configuration

      +

      Next topic

      +

      Transformations

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/html/vendors.html b/sources/doc/html/vendors.html new file mode 100644 index 0000000..40dd642 --- /dev/null +++ b/sources/doc/html/vendors.html @@ -0,0 +1,147 @@ + + + + + + + + + + Distributing and packaging phpMyAdmin — phpMyAdmin 4.1.4 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.
      +
      libraries/php-gettext
      +
      php-gettext library
      +
      libraries/tcpdf
      +
      tcpdf library, stripped down of not needed files
      +
      +
      +
      + + +
      +
      +
      +
      +
      +

      Table Of Contents

      + + +

      Previous topic

      +

      Developers Information

      +

      Next topic

      +

      Copyright

      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/sources/doc/index.rst b/sources/doc/index.rst new file mode 100644 index 0000000..917ddf8 --- /dev/null +++ b/sources/doc/index.rst @@ -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 + vendors + copyright + credits + glossary + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` +* :ref:`glossary` diff --git a/sources/doc/intro.rst b/sources/doc/intro.rst new file mode 100644 index 0000000..9d81e44 --- /dev/null +++ b/sources/doc/intro.rst @@ -0,0 +1,68 @@ +.. _intro: + +Introduction +============ + +phpMyAdmin can manage a whole MySQL server (needs a super-user) as +well as a single database. To accomplish the latter you'll need a +properly set up MySQL user who can read/write only the desired +database. It's up to you to look up the appropriate part in the MySQL +manual. + + +Supported features +------------------ + +Currently phpMyAdmin can: + +* browse 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 +* manage MySQL users 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 see :ref:`faq3_6` +* 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 `62 different languages + `_ + + +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` :term:`RFC 1952` or + :term:`Bzip2` formats) dumps and :term:`CSV` exports if you use PHP with + :term:`Zlib` support (``--with-zlib``) and/or :term:`Bzip2` support + (``--with-bz2``). Proper support may also need changes in :file:`php.ini`. diff --git a/sources/doc/make.bat b/sources/doc/make.bat new file mode 100644 index 0000000..19b6090 --- /dev/null +++ b/sources/doc/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\phpMyAdmin.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\phpMyAdmin.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/sources/doc/other.rst b/sources/doc/other.rst new file mode 100644 index 0000000..aa95451 --- /dev/null +++ b/sources/doc/other.rst @@ -0,0 +1,18 @@ +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: http://www.phpmyadmin.net/home_page/docs.php?books + +Tutorials +--------- + +Third party tutorials and articles are listed on our `wiki page`_. + +.. _wiki page: http://wiki.phpmyadmin.net/pma/Articles diff --git a/sources/doc/privileges.rst b/sources/doc/privileges.rst new file mode 100644 index 0000000..0c12932 --- /dev/null +++ b/sources/doc/privileges.rst @@ -0,0 +1,50 @@ +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:`Users` link on the home page) not from within the :guilabel:`Users` +link under the table. 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. diff --git a/sources/doc/require.rst b/sources/doc/require.rst new file mode 100644 index 0000000..4582d72 --- /dev/null +++ b/sources/doc/require.rst @@ -0,0 +1,53 @@ +.. _require: + +Requirements +============ + +Web server +---------- + +Since, phpMyAdmin's interface is based entirely in your browser, you'll need a +web server (such as Apache, :term:`IIS`) to install phpMyAdmin's files into. + +PHP +--- + +* You need PHP 5.3.0 or newer, with ``session`` support, the Standard PHP Library + (SPL) extension, JSON support, and the ``mbstring`` extension. + +* 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 `mcrypt + `_ extension is strongly suggested for most + users and is **required** for 64–bit machines. Not using mcrypt will + cause phpMyAdmin to load pages significantly slower. + +* To support upload progress bars, see :ref:`faq2_9`. + +* To support XML and Open Document Spreadsheet importing, you need the + `libxml `_ extension. + +* Performance suggestion: install the ``ctype`` extension. + +.. seealso:: :ref:`faq1_31`, :ref:`authentication_modes` + +Database +-------- + +phpMyAdmin supports MySQL-compatible databases. + +* MySQL 5.5 or newer +* MariaDB 5.5 or newer +* Drizzle + +.. seealso:: :ref:`faq1_17` + +Web browser +----------- + +To access phpMyAdmin you need a web browser with cookies and javascript +enabled. + diff --git a/sources/doc/setup.rst b/sources/doc/setup.rst new file mode 100644 index 0000000..783cffd --- /dev/null +++ b/sources/doc/setup.rst @@ -0,0 +1,493 @@ +.. _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 +------ + +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. + +OpenSUSE +-------- + +OpenSUSE already comes with phpMyAdmin package, just install packages from +the `openSUSE Build Service `_. + +Ubuntu +------ + +Ubuntu ships phpMyAdmin package, however if you want to use recent version, you +can use packages from +`PPA for Michal Čihař `_. + +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 `_. + + +.. _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``. +#. 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 +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 need 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 + + + + +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 the `Setup +Script `_. First you must manually create a folder ``config`` +in the phpMyAdmin directory. This is a security measure. On a +Linux/Unix system you can use the following commands: + +.. code-block:: sh + + + cd phpMyAdmin + mkdir config # create directory for saving + chmod o+rw config # give it world writable permissions + +And to edit an existing configuration, copy it over first: + +.. code-block:: sh + + + cp config.inc.php config/ # copy current configuration for editing + chmod o+w config/config.inc.php # give it world writable permissions + +On other platforms, simply create the folder and ensure that your web +server has read and write access to it. :ref:`faq1_26` can help with +this. + +Next, open ``setup/`` in your browser. If you have an existing configuration, +use the ``Load`` button to bring its content inside the setup panel. +Note that **changes are not saved to disk until explicitly choose ``Save``** +from the *Configuration* area of the screen. Normally the script saves the new +:file:`config.inc.php` to the ``config/`` directory, but if the webserver does +not have the proper permissions you may see the error "Cannot load or +save configuration." Ensure that the ``config/`` directory exists and +has the proper permissions - or use the ``Download`` link to save the +config file locally and upload it (via FTP or some similar means) to the +proper location. + +Once the file has been saved, it must be moved out of the ``config/`` +directory and the permissions must be reset, again as a security +measure: + +.. code-block:: sh + + + mv config/config.inc.php . # move file to current directory + chmod o-rw config.inc.php # remove world read and write permissions + rm -rf config # remove not needed directory + +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. +#. You should deny access to the ``./libraries`` and ``./setup/lib`` + subfolders in your webserver configuration. For Apache you can use + supplied :term:`.htaccess` file in that folder, for other webservers, you should + configure this yourself. Such configuration prevents from possible + path exposure and cross side scripting vulnerabilities that might + happen to be found in that code. +#. It is generally good idea to protect 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`. + +.. index:: + single: Configuration storage + single: phpMyAdmin configuration storage + single: pmadb + +.. _linked-tables: + +phpMyAdmin configuration storage +++++++++++++++++++++++++++++++++ + +For a whole set of new features (bookmarks, comments, :term:`SQL`-history, +tracking mechanism, :term:`PDF`-generation, column contents transformation, +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). + +Please look at your ``./examples/`` 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:`examples/upgrade_tables_mysql_4_1_2+.sql` +and then create new tables by importing +:file:`examples/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:`examples/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 with the proper rights to those tables (see section +:ref:`authentication_modes` below). + +.. _upgrading: + +Upgrading from an older version ++++++++++++++++++++++++++++++++ + +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 6, 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. + +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:`examples/upgrade_tables_mysql_4_1_2+.sql`. + +.. 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 blowfish algorithm, in a temporary cookie. + +.. note: + + This section is only applicable if your MySQL server is running + with ``--skip-show-database``. + +For ':term:`HTTP`' and 'cookie' modes, phpMyAdmin needs a controluser that has +**only** the ``SELECT`` privilege on the *`mysql`.`user` (all columns except +`Password`)*, *`mysql`.`db` (all columns)*, *`mysql`.`host` (all columns)* and +*`mysql`.`tables\_priv` (all columns except `Grantor` and `Timestamp`)* tables. +You must specify the details for the controluser in the :file:`config.inc.php` +file under the :config:option:`$cfg['Servers'][$i]['controluser']` and +:config:option:`$cfg['Servers'][$i]['controlpass']` settings. The following +example assumes you want to use ``pma`` as the controluser and ``pmapass`` as +the controlpass, but **this is only an example: use something else in your +file!** Input these statements from the phpMyAdmin :term:`SQL` Query window or +mysql command–line client. Of course you have to replace ``localhost`` with the +webserver's host if it's not the same as the MySQL server's one. + +.. code-block:: mysql + + GRANT USAGE ON mysql.* TO 'pma'@'localhost' IDENTIFIED BY 'pmapass'; + GRANT SELECT ( + Host, User, Select_priv, Insert_priv, Update_priv, Delete_priv, + Create_priv, Drop_priv, Reload_priv, Shutdown_priv, Process_priv, + File_priv, Grant_priv, References_priv, Index_priv, Alter_priv, + Show_db_priv, Super_priv, Create_tmp_table_priv, Lock_tables_priv, + Execute_priv, Repl_slave_priv, Repl_client_priv + ) ON mysql.user TO 'pma'@'localhost'; + GRANT SELECT ON mysql.db TO 'pma'@'localhost'; + GRANT SELECT ON mysql.host TO 'pma'@'localhost'; + GRANT SELECT (Host, Db, User, Table_name, Table_priv, Column_priv) + ON mysql.tables_priv TO 'pma'@'localhost'; + +If you want to use the many new relation and bookmark features: + +.. code-block:: mysql + + GRANT SELECT, INSERT, UPDATE, DELETE ON .* TO 'pma'@'localhost'; + +(this of course requires that your :ref:`linked-tables` be set up). + +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']`. + +.. index:: pair: HTTP; Authentication mode + +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`. +* See also :ref:`faq4_4` about not using the :term:`.htaccess` mechanism along with + ':term:`HTTP`' authentication mode. + +.. index:: pair: Cookie; Authentication mode + +.. _cookie: + +Cookie authentication mode +-------------------------- + +* You can use this method as a replacement for the :term:`HTTP` authentication + (for example, if you're running :term:`IIS`). +* Obviously, the user must enable cookies in the browser, but this is + now a requirement for all authentication modes. +* With this mode, the user can truly log out of phpMyAdmin and log in + back with the same username. +* If you want to log in to arbitrary server see :config:option:`$cfg['AllowArbitraryServer']` directive. +* As mentioned in the :ref:`require` section, having the ``mcrypt`` extension will + speed up access considerably, but is not required. + +.. index:: pair: Signon; Authentication mode + +Signon authentication mode +-------------------------- + +* This mode is a convenient way of using credentials from another + application to authenticate to phpMyAdmin. +* The other application has to store login information into session + data. + +.. seealso:: + :config:option:`$cfg['Servers'][$i]['auth_type']`, + :config:option:`$cfg['Servers'][$i]['SignonSession']`, + :config:option:`$cfg['Servers'][$i]['SignonScript']`, + :config:option:`$cfg['Servers'][$i]['SignonURL']` + + +.. index:: pair: Config; Authentication mode + +Config authentication mode +-------------------------- + +* This mode is 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). But you don't need to setup + a "controluser" here: using the :config:option:`$cfg['Servers'][$i]['only_db']` might be enough. +* 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). + +.. index:: pair: Swekey; Authentication mode + +.. _swekey: + +Swekey authentication mode +-------------------------- + +The Swekey is a low cost authentication USB key that can be used in +web applications. When Swekey authentication is activated, phpMyAdmin +requires the users's Swekey to be plugged before entering the login +page (currently supported for cookie authentication mode only). Swekey +Authentication is disabled by default. To enable it, add the following +line to :file:`config.inc.php`: + +.. code-block:: php + + $cfg['Servers'][$i]['auth_swekey_config'] = '/etc/swekey.conf'; + +You then have to create the ``swekey.conf`` file that will associate +each user with their Swekey Id. It is important to place this file +outside of your web server's document root (in the example, it is +located in ``/etc``). A self documented sample file is provided in the +``examples`` directory. Feel free to use it with your own users' +information. If you want to purchase a Swekey please visit +`http://phpmyadmin.net/auth\_key `_ +since this link provides funding for phpMyAdmin. + +.. seealso:: :config:option:`$cfg['Servers'][$i]['auth_swekey_config']` + + +Securing your phpMyAdmin installation ++++++++++++++++++++++++++++++++++++++ + +The phpMyAdmin team tries hardly to make the application secure, however there +are always ways to make your installation more secure: + +* remove ``setup`` directory from phpMyAdmin, you will probably not + use it after initial setup +* properly choose authentication method - :ref:`cookie` + is probably the best choice for shared hosting +* 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 +* consider hiding phpMyAdmin behind authentication proxy, so that + MySQL credentials are not all users need to login +* if you are afraid of automated attacks, enabling Captcha by + :config:option:`$cfg['CaptchaLoginPublicKey']` and + :config:option:`$cfg['CaptchaLoginPrivateKey']` might be an option. diff --git a/sources/doc/transformations.rst b/sources/doc/transformations.rst new file mode 100644 index 0000000..4c3ce46 --- /dev/null +++ b/sources/doc/transformations.rst @@ -0,0 +1,138 @@ +.. _transformations: + +Transformations +=============== + +.. _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'". + +.. _transformationsfiles: + +File structure +++++++++++++++ + +All specific transformations for mimetypes are defined through class +files in the directory 'libraries/plugins/transformations/'. Each of +them extends a certain transformation abstract class declared in +libraries/plugins/transformations/abstract. + +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/plugins/transformations/TEMPLATE and +libraries/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:`libraries/plugins/transformations/generator_plugin.sh` or +:file:`libraries/plugins/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/sources/doc/user.rst b/sources/doc/user.rst new file mode 100644 index 0000000..ceaf723 --- /dev/null +++ b/sources/doc/user.rst @@ -0,0 +1,9 @@ +User Guide +========== + +.. toctree:: + :maxdepth: 2 + + transformations + privileges + other diff --git a/sources/doc/vendors.rst b/sources/doc/vendors.rst new file mode 100644 index 0000000..13d7e55 --- /dev/null +++ b/sources/doc/vendors.rst @@ -0,0 +1,34 @@ +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. + +libraries/php-gettext + php-gettext library +libraries/tcpdf + tcpdf library, stripped down of not needed files + diff --git a/sources/error_report.php b/sources/error_report.php new file mode 100644 index 0000000..3ed045b --- /dev/null +++ b/sources/error_report.php @@ -0,0 +1,95 @@ +addJSON( + 'message', + PMA_Message::error( + __( + 'An error has been detected and an error report has been ' + .'automatically submitted based on your settings.' + ) + . '
      ' + . __('You may want to refresh the page.') + ) + ); + } else { + $response->addJSON( + 'message', + PMA_Message::error( + __( + '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.' + ) + . '
      ' + . __('You may want to refresh the page.') + ) + ); + } + } else { + if ($success) { + $response->addJSON( + 'message', + PMA_Message::success( + __('Thank you for submitting this report.') + . '
      ' + . __('You may want to refresh the page.') + ) + ); + } else { + $response->addJSON( + 'message', + PMA_Message::error( + __('Thank you for submitting this report.') + . ' ' + . __('Unfortunately the submission failed.') + . ' ' + . __( + 'If you experience any ' + . 'problems please submit a bug report manually.' + ) + . '
      ' + . __('You may want to refresh the page.') + ) + ); + } + if (isset($_REQUEST['always_send']) + && $_REQUEST['always_send'] === "true" + ) { + PMA_persistOption("SendErrorReports", "always", "ask"); + } + } +} elseif (! empty($_REQUEST['get_settings'])) { + $response->addJSON('report_setting', $GLOBALS['cfg']['SendErrorReports']); +} else { + $response->addHTML(PMA_getErrorReportForm()); +} diff --git a/sources/examples/config.manyhosts.inc.php b/sources/examples/config.manyhosts.inc.php new file mode 100644 index 0000000..3ab6572 --- /dev/null +++ b/sources/examples/config.manyhosts.inc.php @@ -0,0 +1,51 @@ + 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; + ?> + + + +OpenID:
      + + + +'; + Show_page($content); + exit; +} + +/* Grab identifier */ +if (isset($_POST['identifier'])) { + $identifier = $_POST['identifier']; +} else if (isset($_SESSION['identifier'])) { + $identifier = $_SESSION['identifier']; +} else { + $identifier = null; +} + +/* Create OpenID object */ +try { + $o = new OpenID_RelyingParty($returnTo, $realm, $identifier); +} catch (OpenID_Exception $e) { + $contents = "
      \n"; + $contents .= "
      " . $e->getMessage() . "
      \n"; + $contents .= "
      "; + Show_page($contents); + exit; +} + +/* Redirect to OpenID provider */ +if (isset($_POST['start'])) { + try { + $authRequest = $o->prepare(); + } catch (OpenID_Exception $e) { + $contents = "
      \n"; + $contents .= "
      " . $e->getMessage() . "
      \n"; + $contents .= "
      "; + Show_page($contents); + exit; + } + + $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 */ + $message = new OpenID_Message($queryString, OpenID_Message::FORMAT_HTTP); + + $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/sources/examples/signon-script.php b/sources/examples/signon-script.php new file mode 100644 index 0000000..e333f2e --- /dev/null +++ b/sources/examples/signon-script.php @@ -0,0 +1,29 @@ + diff --git a/sources/examples/signon.php b/sources/examples/signon.php new file mode 100644 index 0000000..57e989d --- /dev/null +++ b/sources/examples/signon.php @@ -0,0 +1,69 @@ + '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/sources/examples/swekey.sample.conf b/sources/examples/swekey.sample.conf new file mode 100644 index 0000000..ebf1aed --- /dev/null +++ b/sources/examples/swekey.sample.conf @@ -0,0 +1,44 @@ +# This is a typical file used to enable Swekey hardware authentication. +# +# To activate the Swekey authentication add the following line to your config.inc.php file. +# $cfg['Servers'][$i]['auth_swekey_config'] = '/etc/swekey-pma.conf'; +# Then rename this file "swekey-pma.conf" and copy it to the /etc directory. +# Add all the Swekey ids you want to grant access to in the file. +# After each Swekey id put the corresponding user name. +# +# If you don't know the id of a Swekey just visit http://www.swekey.com?sel=support +# while your Swekey is connected. +# +# If you need to purchase a Swekey please visit http://phpmyadmin.net/auth_key +# since this link provides funding to PhpMyAdmin. +# + +0000000000000000000000000000763A:root +000000000000000000000000000089E4:steve +0000000000000000000000000000231E:scott + +# +# It is recommended to include the following lines to contact the +# authentication servers in SSL mode. +# + +SERVER_CHECK=https://auth-check-ssl.musbe.net +SERVER_RNDTOKEN=https://auth-rnd-gen-ssl.musbe.net +SERVER_STATUS=https://auth-status-ssl.musbe.net + +# +# The path of the root certificate file used to ensure a secure +# communication with the authentication servers in SSL mode. +# If not specified, will use musbe-ca.crt found in your +# phpMyAdmin/libraries/auth/swekey. +# + +#CA_FILE=/var/http-root/phpmyadmin/libraries/auth/swekey/musbe-ca.crt + +# +# If your server receives many login requests, you can enable the random +# token caching to accelerate the authentication process. +# Token caching is enabled by default. +# + +#ENABLE_TOKEN_CACHE=0 diff --git a/sources/examples/upgrade_tables_mysql_4_1_2+.sql b/sources/examples/upgrade_tables_mysql_4_1_2+.sql new file mode 100644 index 0000000..df4046a --- /dev/null +++ b/sources/examples/upgrade_tables_mysql_4_1_2+.sql @@ -0,0 +1,144 @@ +-- ------------------------------------------------------------- +-- SQL Commands to upgrade pmadb for normal phpMyAdmin operation +-- with MySQL 4.1.2 and above. +-- +-- This file is meant for use with MySQL 4.1.2 and above! +-- For older MySQL releases, please use create_tables.sql +-- +-- If you are running one MySQL 4.1.0 or 4.1.1, please create the tables using +-- create_tables.sql, then use this script. +-- +-- Please don't forget to set up the tablenames in config.inc.php +-- + +-- -------------------------------------------------------- + +-- +-- Database : `phpmyadmin` +-- +ALTER DATABASE `phpmyadmin` + DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; +USE phpmyadmin; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `pma__bookmark` +-- +ALTER TABLE `pma__bookmark` + DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; + +ALTER TABLE `pma__bookmark` + CHANGE `dbase` `dbase` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__bookmark` + CHANGE `user` `user` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__bookmark` + CHANGE `label` `label` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ''; +ALTER TABLE `pma__bookmark` + CHANGE `query` `query` TEXT CHARACTER SET utf8 COLLATE utf8_bin NOT NULL; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `pma__column_info` +-- + +ALTER TABLE `pma__column_info` + DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; + +ALTER TABLE `pma__column_info` + CHANGE `db_name` `db_name` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__column_info` + CHANGE `table_name` `table_name` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__column_info` + CHANGE `column_name` `column_name` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__column_info` + CHANGE `comment` `comment` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ''; +ALTER TABLE `pma__column_info` + CHANGE `mimetype` `mimetype` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ''; +ALTER TABLE `pma__column_info` + CHANGE `transformation` `transformation` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__column_info` + CHANGE `transformation_options` `transformation_options` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `pma__history` +-- +ALTER TABLE `pma__history` + DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; + +ALTER TABLE `pma__history` + CHANGE `username` `username` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__history` + CHANGE `db` `db` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__history` + CHANGE `table` `table` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__history` + CHANGE `sqlquery` `sqlquery` TEXT CHARACTER SET utf8 COLLATE utf8_bin NOT NULL; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `pma__pdf_pages` +-- + +ALTER TABLE `pma__pdf_pages` + DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; + +ALTER TABLE `pma__pdf_pages` + CHANGE `db_name` `db_name` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__pdf_pages` + CHANGE `page_descr` `page_descr` VARCHAR(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL default ''; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `pma__relation` +-- +ALTER TABLE `pma__relation` + DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; + +ALTER TABLE `pma__relation` + CHANGE `master_db` `master_db` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__relation` + CHANGE `master_table` `master_table` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__relation` + CHANGE `master_field` `master_field` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__relation` + CHANGE `foreign_db` `foreign_db` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__relation` + CHANGE `foreign_table` `foreign_table` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__relation` + CHANGE `foreign_field` `foreign_field` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `pma__table_coords` +-- + +ALTER TABLE `pma__table_coords` + DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; + +ALTER TABLE `pma__table_coords` + CHANGE `db_name` `db_name` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__table_coords` + CHANGE `table_name` `table_name` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `pma__table_info` +-- + +ALTER TABLE `pma__table_info` + DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; + +ALTER TABLE `pma__table_info` + CHANGE `db_name` `db_name` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__table_info` + CHANGE `table_name` `table_name` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; +ALTER TABLE `pma__table_info` + CHANGE `display_field` `display_field` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ''; diff --git a/sources/export.php b/sources/export.php new file mode 100644 index 0000000..18aae1c --- /dev/null +++ b/sources/export.php @@ -0,0 +1,1042 @@ + $export_type, + 'single_table' => isset($single_table) + ) + ); + + // Backward compatbility + $type = $what; + + // Check export type + if (! isset($export_plugin)) { + PMA_fatalError(__('Bad type!')); + } + + /** + * valid compression methods + */ + $compression_methods = array( + 'zip', + 'gzip', + 'bzip2', + ); + + /** + * init and variable checking + */ + $compression = false; + $onserver = false; + $save_on_server = false; + $buffer_needed = false; + + // Is it a quick or custom export? + if ($_REQUEST['quick_or_custom'] == 'quick') { + $quick_export = true; + } else { + $quick_export = false; + } + + if ($_REQUEST['output_format'] == 'astext') { + $asfile = false; + } else { + $asfile = true; + if (in_array($_REQUEST['compression'], $compression_methods)) { + $compression = $_REQUEST['compression']; + $buffer_needed = true; + } + if (($quick_export && ! empty($_REQUEST['quick_export_onserver'])) + || (! $quick_export && ! empty($_REQUEST['onserver'])) + ) { + if ($quick_export) { + $onserver = $_REQUEST['quick_export_onserver']; + } else { + $onserver = $_REQUEST['onserver']; + } + // Will we save dump on server? + $save_on_server = ! empty($cfg['SaveDir']) && $onserver; + } + } + + // Does export require to be into file? + if ($export_plugin->getProperties()->getForceFile() != null && ! $asfile) { + $message = PMA_Message::error( + __('Selected export type has to be saved in file!') + ); + if ($export_type == 'server') { + $active_page = 'server_export.php'; + include 'server_export.php'; + } elseif ($export_type == 'database') { + $active_page = 'db_export.php'; + include 'db_export.php'; + } else { + $active_page = 'tbl_export.php'; + include 'tbl_export.php'; + } + exit(); + } + + // Generate error url and check for needed variables + if ($export_type == 'server') { + $err_url = 'server_export.php?' . PMA_URL_getCommon(); + } elseif ($export_type == 'database' && strlen($db)) { + $err_url = 'db_export.php?' . PMA_URL_getCommon($db); + // Check if we have something to export + if (isset($table_select)) { + $tables = $table_select; + } else { + $tables = array(); + } + } elseif ($export_type == 'table' && strlen($db) && strlen($table)) { + $err_url = 'tbl_export.php?' . PMA_URL_getCommon($db, $table); + } else { + PMA_fatalError(__('Bad parameters!')); + } + + /** + * Increase time limit for script execution and initializes some variables + */ + @set_time_limit($cfg['ExecTimeLimit']); + if (! empty($cfg['MemoryLimit'])) { + @ini_set('memory_limit', $cfg['MemoryLimit']); + } + register_shutdown_function('PMA_shutdown'); + // Start with empty buffer + $dump_buffer = ''; + $dump_buffer_len = 0; + + // We send fake headers to avoid browser timeout when buffering + $time_start = time(); +} + +/** + * Sets a session variable upon a possible fatal error during export + * + * @return void + */ +function PMA_shutdown() +{ + $a = error_get_last(); + if ($a != null && strpos($a['message'], "execution time")) { + //write in partially downloaded file for future reference of user + print_r($a); + //set session variable to check if there was error while exporting + $_SESSION['pma_export_error'] = $a['message']; + } +} +/** + * Detect ob_gzhandler + * + * @return bool + */ +function PMA_isGzHandlerEnabled() +{ + return in_array('ob_gzhandler', ob_list_handlers()); +} + +/** + * Detect whether gzencode is needed; it might not be needed if + * the server is already compressing by itself + * + * @return bool Whether gzencode is needed + */ +function PMA_gzencodeNeeded() +{ + /* + * We should gzencode only if the function exists + * but we don't want to compress twice, therefore + * gzencode only if transparent compression is not enabled + * and gz compression was not asked via $cfg['OBGzip'] + * but transparent compression does not apply when saving to server + */ + if (@function_exists('gzencode') + && ((! @ini_get('zlib.output_compression') + && ! PMA_isGzHandlerEnabled()) + || $GLOBALS['save_on_server']) + ) { + return true; + } else { + return false; + } +} + +/** + * Output handler for all exports, if needed buffering, it stores data into + * $dump_buffer, otherwise it prints thems out. + * + * @param string $line the insert statement + * + * @return bool Whether output succeeded + */ +function PMA_exportOutputHandler($line) +{ + global $time_start, $dump_buffer, $dump_buffer_len, $save_filename; + + // Kanji encoding convert feature + if ($GLOBALS['output_kanji_conversion']) { + $line = PMA_Kanji_strConv( + $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 = PMA_convertString( + 'utf-8', + $GLOBALS['charset_of_file'], + $dump_buffer + ); + } + // as bzipped + if ($GLOBALS['compression'] == 'bzip2' + && @function_exists('bzcompress') + ) { + $dump_buffer = bzcompress($dump_buffer); + } elseif ($GLOBALS['compression'] == 'gzip' + && PMA_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); + if ($write_result != strlen($dump_buffer)) { + $GLOBALS['message'] = PMA_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 = PMA_convertString( + 'utf-8', + $GLOBALS['charset_of_file'], + $line + ); + } + if ($GLOBALS['save_on_server'] && strlen($line) > 0) { + $write_result = @fwrite($GLOBALS['file_handle'], $line); + if (! $write_result || ($write_result != strlen($line))) { + $GLOBALS['message'] = PMA_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 'PMA_exportOutputHandler()' function + +// Defines the default format. +// For SQL always use \n as MySQL wants this on all platforms. +if (!defined('TESTSUITE')) { + if ($what == 'sql') { + $crlf = "\n"; + } else { + $crlf = PMA_Util::whichCrlf(); + } + + $output_kanji_conversion = function_exists('PMA_Kanji_strConv') + && $type != 'xls'; + + // Do we need to convert charset? + $output_charset_conversion = $asfile + && $GLOBALS['PMA_recoding_engine'] != PMA_CHARSET_NONE + && isset($charset_of_file) && $charset_of_file != 'utf-8' + && $type != 'xls'; + + // Use on the fly compression? + $onfly_compression = $GLOBALS['cfg']['CompressOnFly'] + && ($compression == 'gzip' || $compression == 'bzip2'); + if ($onfly_compression) { + $memory_limit = trim(@ini_get('memory_limit')); + $memory_limit_num = (int)substr($memory_limit, 0, -1); + // 2 MB as default + if (empty($memory_limit) || '-1' == $memory_limit) { + $memory_limit = 2 * 1024 * 1024; + } elseif (strtolower(substr($memory_limit, -1)) == 'm') { + $memory_limit = $memory_limit_num * 1024 * 1024; + } elseif (strtolower(substr($memory_limit, -1)) == 'k') { + $memory_limit = $memory_limit_num * 1024; + } elseif (strtolower(substr($memory_limit, -1)) == '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; + } + + // Generate filename and mime type if needed + if ($asfile) { + $pma_uri_parts = parse_url($cfg['PmaAbsoluteUri']); + if ($export_type == 'server') { + if (isset($remember_template)) { + $GLOBALS['PMA_Config']->setUserValue( + 'pma_server_filename_template', + 'Export/file_template_server', + $filename_template + ); + } + } elseif ($export_type == 'database') { + if (isset($remember_template)) { + $GLOBALS['PMA_Config']->setUserValue( + 'pma_db_filename_template', + 'Export/file_template_database', + $filename_template + ); + } + } else { + if (isset($remember_template)) { + $GLOBALS['PMA_Config']->setUserValue( + 'pma_table_filename_template', + 'Export/file_template_table', + $filename_template + ); + } + } + $filename = PMA_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 = PMA_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 = strlen($filename) - strlen( + $export_plugin->getProperties()->getExtension() + ) - 1; + $user_extension = substr($filename, $extension_start_pos, strlen($filename)); + $required_extension = "." . $export_plugin->getProperties()->getExtension(); + if (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 == 'bzip2') { + $filename .= '.bz2'; + $mime_type = 'application/x-bzip2'; + } elseif ($compression == 'gzip') { + $filename .= '.gz'; + $mime_type = 'application/x-gzip'; + } elseif ($compression == 'zip') { + $filename .= '.zip'; + $mime_type = 'application/zip'; + } + } + + // Open file on server if needed + if ($save_on_server) { + $save_filename = PMA_Util::userDir($cfg['SaveDir']) + . preg_replace('@[/\\\\]@', '_', $filename); + unset($message); + if (file_exists($save_filename) + && ((! $quick_export && empty($_REQUEST['onserverover'])) + || ($quick_export + && $_REQUEST['quick_export_onserverover'] != 'saveitover')) + ) { + $message = PMA_Message::error( + __( + 'File %s already exists on server, ' + . 'change filename or check overwrite option.' + ) + ); + $message->addParam($save_filename); + } else { + if (is_file($save_filename) && ! is_writable($save_filename)) { + $message = PMA_Message::error( + __( + 'The web server does not have permission ' + . 'to save the file %s.' + ) + ); + $message->addParam($save_filename); + } else { + if (! $file_handle = @fopen($save_filename, 'w')) { + $message = PMA_Message::error( + __( + 'The web server does not have permission ' + . 'to save the file %s.' + ) + ); + $message->addParam($save_filename); + } + } + } + if (isset($message)) { + if ($export_type == 'server') { + $active_page = 'server_export.php'; + include 'server_export.php'; + } elseif ($export_type == 'database') { + $active_page = 'db_export.php'; + include 'db_export.php'; + } else { + $active_page = 'tbl_export.php'; + include 'tbl_export.php'; + } + exit(); + } + } + + /** + * Send headers depending on whether the user chose to download a dump file + * or not + */ + if (! $save_on_server) { + 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 = PMA_sanitizeFilename($filename); + + PMA_downloadHeader($filename, $mime_type); + } else { + // HTML + if ($export_type == 'database') { + $num_tables = count($tables); + if ($num_tables == 0) { + $message = PMA_Message::error( + __('No tables found in database.') + ); + $active_page = 'db_export.php'; + include 'db_export.php'; + exit(); + } + } + $backup_cfgServer = $cfg['Server']; + $cfg['Server'] = $backup_cfgServer; + unset($backup_cfgServer); + echo "\n" . '
      ' . "\n"; + //echo '
      ' . "\n";
      +
      +            /**
      +             * Displays a back button with all the $_REQUEST data in the URL
      +             * (store in a variable to also display after the textarea)
      +             */
      +            $back_button = '

      [ $value) { + $back_button .= '&' . urlencode($name) . '=' . urlencode($value); + } + $back_button .= '&repopulate=1">Back ]

      '; + + echo $back_button; + echo '
      ' . "\n"; + echo '' . "\n" + . '
      ' . "\n"; + echo $back_button; + + echo "\n"; + echo '
      ' . "\n"; + echo "\n"; + ?> + + diff --git a/sources/favicon.ico b/sources/favicon.ico new file mode 100644 index 0000000..2352b5f Binary files /dev/null and b/sources/favicon.ico differ diff --git a/sources/file_echo.php b/sources/file_echo.php new file mode 100644 index 0000000..d910b74 --- /dev/null +++ b/sources/file_echo.php @@ -0,0 +1,71 @@ + 'png', + 'image/svg+xml' => 'svg', + ); + + /* Check whether MIME type is allowed */ + if (! isset($allowed[$_REQUEST['type']])) { + PMA_fatalError(__('Invalid export type')); + } + + /* + * Check file name to match mime type and not contain new lines + * to prevent response splitting. + */ + $extension = $allowed[$_REQUEST['type']]; + $valid_match = '/^[^\n\r]*\.' . $extension . '$/'; + if (! preg_match($valid_match, $_REQUEST['filename'])) { + if (! preg_match('/^[^\n\r]*$/', $_REQUEST['filename'])) { + /* Filename is unsafe, discard it */ + $filename = 'download.' . $extension; + } else { + /* Add extension */ + $filename = $_REQUEST['filename'] . '.' . $extension; + } + } else { + /* Filename from request should be safe here */ + $filename = $_REQUEST['filename']; + } + + /* Decode data */ + if ($extension != 'svg') { + $data = substr($_REQUEST['image'], strpos($_REQUEST['image'], ',') + 1); + $data = base64_decode($data); + } else { + $data = $_REQUEST['image']; + } + + /* Send download header */ + PMA_downloadHeader($filename, $_REQUEST['type'], strlen($data)); + + /* Send data */ + echo $data; + +} else if (isset($_REQUEST['monitorconfig'])) { + /* For monitor chart config export */ + PMA_downloadHeader('monitor.cfg', 'application/force-download'); + echo urldecode($_REQUEST['monitorconfig']); + +} else if (isset($_REQUEST['import'])) { + /* For monitor chart config import */ + header('Content-type: text/plain'); + if (!file_exists($_FILES['file']['tmp_name'])) { + exit(); + } + echo file_get_contents($_FILES['file']['tmp_name']); +} +?> diff --git a/sources/gis_data_editor.php b/sources/gis_data_editor.php new file mode 100644 index 0000000..621dbfa --- /dev/null +++ b/sources/gis_data_editor.php @@ -0,0 +1,418 @@ +generateParams($_REQUEST['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 PNG or SVG based visualization +$format = (PMA_USR_BROWSER_AGENT == 'IE' && PMA_USR_BROWSER_VER <= 8) + ? 'png' : 'svg'; +$visualizationSettings = array( + 'width' => 450, + 'height' => 300, + 'spatialColumn' => 'wkt' +); +$data = array(array('wkt' => $wkt_with_zero, 'srid' => $srid)); +$visualization = PMA_GIS_visualizationResults( + $data, $visualizationSettings, $format +); +$open_layers = PMA_GIS_visualizationResults($data, $visualizationSettings, 'ol'); + +// If the call is to update the WKT and visualization make an AJAX response +if (isset($_REQUEST['generate']) && $_REQUEST['generate'] == true) { + $extra_data = array( + 'result' => $result, + 'visualization' => $visualization, + 'openLayers' => $open_layers, + ); + $response = PMA_Response::getInstance(); + $response->addJSON($extra_data); + exit; +} + +ob_start(); + +echo '
      '; +echo ''; +echo '
      '; + +echo '

      '; +printf( + __('Value for the column "%s"'), + htmlspecialchars($_REQUEST['field']) +); +echo '

      '; + +echo ''; +// The input field to which the final result should be added +// and corresponding null checkbox +if (isset($_REQUEST['input_name'])) { + echo ''; +} +echo PMA_URL_getHiddenInputs(); + +echo ''; +echo '
      '; +echo $visualization; +echo '
      '; + +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'])) + ? $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 ($geom_type == 'GEOMETRYCOLLECTION') { + echo '

      '; + printf(__('Geometry %d:'), $a + 1); + echo '
      '; + if (isset($gis_data[$a]['gis_type'])) { + $type = $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']) + ? $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']) + ? $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']) + ? $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']) + ? $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']) + ? $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']) + ? $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 "GeomFromText" from the "Function" column and paste the' + . ' string below into the "Value" field' +); +echo '

      '; +echo ''; +echo '
      '; + +echo '
      '; +echo '
      '; + +PMA_Response::getInstance()->addJSON('gis_editor', ob_get_contents()); +ob_end_clean(); +?> diff --git a/sources/import.php b/sources/import.php new file mode 100644 index 0000000..ec56476 --- /dev/null +++ b/sources/import.php @@ -0,0 +1,648 @@ +addParam('[doc@faq1-16]'); + $message->addParam('[/doc]'); + + // so we can obtain the message + $_SESSION['Import_message']['message'] = $message->getDisplay(); + $_SESSION['Import_message']['go_back_url'] = $goto; + + $message->display(); + exit; // the footer is displayed automatically +} + +/** + * 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 + PMA_fatalError('Incorrect format parameter'); +} + +$post_patterns = array( + '/^force_file_/', + '/^'. $format . '_/' +); +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]; + } + } +} + +// Check needed parameters +PMA_Util::checkParameters(array('import_type', 'format')); + +// We don't want anything special in format +$format = PMA_securePath($format); + +// Import functions +require_once 'libraries/import.lib.php'; + +// Create error and goto url +if ($import_type == 'table') { + $err_url = 'tbl_import.php?' . PMA_URL_getCommon($db, $table); + $_SESSION['Import_message']['go_back_url'] = $err_url; + $goto = 'tbl_import.php'; +} elseif ($import_type == 'database') { + $err_url = 'db_import.php?' . PMA_URL_getCommon($db); + $_SESSION['Import_message']['go_back_url'] = $err_url; + $goto = 'db_import.php'; +} elseif ($import_type == 'server') { + $err_url = 'server_import.php?' . PMA_URL_getCommon(); + $_SESSION['Import_message']['go_back_url'] = $err_url; + $goto = 'server_import.php'; +} else { + if (empty($goto) || !preg_match('@^(server|db|tbl)(_[a-z]*)*\.php$@i', $goto)) { + if (strlen($table) && strlen($db)) { + $goto = 'tbl_structure.php'; + } elseif (strlen($db)) { + $goto = 'db_structure.php'; + } else { + $goto = 'server_sql.php'; + } + } + if (strlen($table) && strlen($db)) { + $common = PMA_URL_getCommon($db, $table); + } elseif (strlen($db)) { + $common = PMA_URL_getCommon($db); + } else { + $common = PMA_URL_getCommon(); + } + $err_url = $goto . '?' . $common + . (preg_match('@^tbl_[a-z]*\.php$@', $goto) + ? '&table=' . htmlspecialchars($table) + : ''); + $_SESSION['Import_message']['go_back_url'] = $err_url; +} + + +if (strlen($db)) { + $GLOBALS['dbi']->selectDb($db); +} + +@set_time_limit($cfg['ExecTimeLimit']); +if (! empty($cfg['MemoryLimit'])) { + @ini_set('memory_limit', $cfg['MemoryLimit']); +} + +$timestamp = time(); +if (isset($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; + +// Bookmark Support: get a query back from bookmark if required +if (! empty($id_bookmark)) { + $id_bookmark = (int)$id_bookmark; + include_once 'libraries/bookmark.lib.php'; + switch ($action_bookmark) { + case 0: // bookmarked query that have to be run + $import_text = PMA_Bookmark_get( + $db, + $id_bookmark, + 'id', + isset($action_bookmark_all) + ); + if (isset($bookmark_variable) && ! empty($bookmark_variable)) { + $import_text = preg_replace( + '|/\*(.*)\[VARIABLE\](.*)\*/|imsU', + '${1}' . PMA_Util::sqlAddSlashes($bookmark_variable) . '${2}', + $import_text + ); + } + + // refresh navigation and main panels + if (preg_match( + '/^(DROP)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i', + $import_text + )) { + $GLOBALS['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 + $import_text = PMA_Bookmark_get($db, $id_bookmark); + if ($GLOBALS['is_ajax_request'] == true) { + $message = PMA_Message::success(__('Showing bookmark')); + $response = PMA_Response::getInstance(); + $response->isSuccess($message->isSuccess()); + $response->addJSON('message', $message); + $response->addJSON('sql_query', $import_text); + $response->addJSON('action_bookmark', $action_bookmark); + exit; + } else { + $run_query = false; + } + break; + case 2: // bookmarked query that have to be deleted + $import_text = PMA_Bookmark_get($db, $id_bookmark); + PMA_Bookmark_delete($db, $id_bookmark); + if ($GLOBALS['is_ajax_request'] == true) { + $message = PMA_Message::success(__('The bookmark has been deleted.')); + $response = PMA_Response::getInstance(); + $response->isSuccess($message->isSuccess()); + $response->addJSON('message', $message); + $response->addJSON('action_bookmark', $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 +if (strtolower(substr($memory_limit, -1)) == 'm') { + $memory_limit = (int)substr($memory_limit, 0, -1) * 1024 * 1024; +} elseif (strtolower(substr($memory_limit, -1)) == 'k') { + $memory_limit = (int)substr($memory_limit, 0, -1) * 1024; +} elseif (strtolower(substr($memory_limit, -1)) == 'g') { + $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 = PMA_securePath($local_import_file); + + $import_file = PMA_Util::userDir($cfg['UploadDir']) + . $local_import_file; + +} elseif (empty($import_file) || ! is_uploaded_file($import_file)) { + $import_file = 'none'; +} + +// Do we have file to import? + +if ($import_file != 'none' && ! $error) { + // work around open_basedir and other limitations + $open_basedir = @ini_get('open_basedir'); + + // If we are on a server with open_basedir, we must move the file + // before opening it. The doc explains how to create the "./tmp" + // directory + + if (! empty($open_basedir)) { + + $tmp_subdir = (PMA_IS_WINDOWS ? '.\\tmp\\' : 'tmp/'); + + if (is_writable($tmp_subdir)) { + + + $import_file_new = $tmp_subdir . basename($import_file) . uniqid(); + if (move_uploaded_file($import_file, $import_file_new)) { + $import_file = $import_file_new; + $file_to_unlink = $import_file_new; + } + + $size = filesize($import_file); + } + } + + /** + * Handle file compression + * @todo duplicate code exists in File.class.php + */ + $compression = PMA_detectCompression($import_file); + if ($compression === false) { + $message = PMA_Message::error(__('File could not be read')); + $error = true; + } else { + switch ($compression) { + case 'application/bzip2': + if ($cfg['BZipDump'] && @function_exists('bzopen')) { + $import_handle = @bzopen($import_file, 'r'); + } else { + $message = PMA_Message::error( + __('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.') + ); + $message->addParam($compression); + $error = true; + } + break; + case 'application/gzip': + if ($cfg['GZipDump'] && @function_exists('gzopen')) { + $import_handle = @gzopen($import_file, 'r'); + } else { + $message = PMA_Message::error( + __('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.') + ); + $message->addParam($compression); + $error = true; + } + break; + case 'application/zip': + if ($cfg['ZipDump'] && @function_exists('zip_open')) { + /** + * Load interface for zip extension. + */ + include_once 'libraries/zip_extension.lib.php'; + $result = PMA_getZipContents($import_file); + if (! empty($result['error'])) { + $message = PMA_Message::rawError($result['error']); + $error = true; + } else { + $import_text = $result['data']; + } + } else { + $message = PMA_Message::error( + __('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.') + ); + $message->addParam($compression); + $error = true; + } + break; + case 'none': + $import_handle = @fopen($import_file, 'r'); + break; + default: + $message = PMA_Message::error( + __('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.') + ); + $message->addParam($compression); + $error = true; + break; + } + } + // use isset() because zip compression type does not use a handle + if (! $error && isset($import_handle) && $import_handle === false) { + $message = PMA_Message::error(__('File could not be read')); + $error = true; + } +} elseif (! $error) { + if (! isset($import_text) || empty($import_text)) { + $message = PMA_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].') + ); + $error = true; + } +} + +// so we can obtain the message +//$_SESSION['Import_message'] = $message->getDisplay(); + +// Convert the file's charset if necessary +if ($GLOBALS['PMA_recoding_engine'] != PMA_CHARSET_NONE && isset($charset_of_file)) { + if ($charset_of_file != 'utf-8') { + $charset_conversion = true; + } +} elseif (isset($charset_of_file) && $charset_of_file != 'utf8') { + if (PMA_DRIZZLE) { + // Drizzle doesn't support other character sets, + // so we can't fallback to SET NAMES - throw an error + $error = true; + $message = PMA_Message::error( + __('Cannot convert file\'s character set without character set conversion library') + ); + } else { + $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? +if (! $error && isset($skip)) { + $original_skip = $skip; + while ($skip > 0) { + PMA_importGetNextChunk($skip < $read_limit ? $skip : $read_limit); + // Disable read progresivity, 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) { + // Check for file existance + include_once "libraries/plugin_interface.lib.php"; + $import_plugin = PMA_getPlugin( + "import", + $format, + 'libraries/plugins/import/', + $import_type + ); + if ($import_plugin == null) { + $error = true; + $message = PMA_Message::error( + __('Could not load import plugins, please check your installation!') + ); + } else { + // Do the real import + $import_plugin->doImport($sql_data); + } +} + +if (! $error && false !== $import_handle && null !== $import_handle) { + fclose($import_handle); +} + +// 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 utf8'); + $GLOBALS['dbi']->query( + 'SET SESSION collation_connection =\'' . $collation_connection . '\'' + ); +} + +// Show correct message +if (! empty($id_bookmark) && $action_bookmark == 2) { + $message = PMA_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) && $action_bookmark == 1) { + $message = PMA_Message::notice(__('Showing bookmark')); +} elseif ($bookmark_created) { + $special_message = '[br]' . sprintf( + __('Bookmark %s created'), + htmlspecialchars($bkm_label) + ); +} elseif ($finished && ! $error) { + if ($import_type == 'query') { + $message = PMA_Message::success(); + } else { + if ($import_notice) { + $message = PMA_Message::success( + '' + . __('Import has been successfully finished, %d queries executed.') + . '' + ); + $message->addParam($executed_queries); + + $message->addString($import_notice); + if (isset($local_import_file)) { + $message->addString('(' . $local_import_file . ')'); + } else { + $message->addString('(' . $_FILES['import_file']['name'] . ')'); + } + } else { + $message = PMA_Message::success( + __('Import has been successfully finished, %d queries executed.') + ); + $message->addParam($executed_queries); + if (isset($local_import_file)) { + $message->addString('(' . $local_import_file . ')'); + } else { + $message->addString('(' . $_FILES['import_file']['name'] . ')'); + } + } + } +} + +// Did we hit timeout? Tell it user. +if ($timeout_passed) { + $message = PMA_Message::error( + __('Script timeout passed, if you want to finish import, please resubmit same file and import will resume.') + ); + if ($offset == 0 || (isset($original_skip) && $original_skip == $offset)) { + $message->addString( + __('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) +if (strlen($sql_query) <= $GLOBALS['cfg']['MaxCharactersInDisplayedSQL']) { + include_once 'libraries/parse_analyze.inc.php'; +} + +// There was an error? +if (isset($my_die)) { + foreach ($my_die as $key => $die) { + PMA_Util::mysqlDie( + $die['error'], $die['sql'], '', $err_url, $error + ); + } +} + +if ($go_sql) { + // parse sql query + include_once 'libraries/parse_analyze.inc.php'; + + PMA_executeQueryAndSendQueryResponse( + $analyzed_sql_results, false, $db, $table, null, $import_text, null, + $analyzed_sql_results['is_affected'], null, + null, null, null, $goto, $pmaThemeImage, null, null, null, $sql_query, + null, null + ); +} else if ($result) { + $response = PMA_Response::getInstance(); + $response->isSuccess(true); + $response->addJSON('message', PMA_Message::success($msg)); + $response->addJSON( + 'sql_query', + PMA_Util::getMessage($msg, $sql_query, 'success') + ); +} else if ($result == false) { + $response = PMA_Response::getInstance(); + $response->isSuccess(false); + $response->addJSON('message', PMA_Message::error($msg)); +} else { + $active_page = $goto; + include '' . $goto; +} +?> diff --git a/sources/import_status.php b/sources/import_status.php new file mode 100644 index 0000000..f31e424 --- /dev/null +++ b/sources/import_status.php @@ -0,0 +1,93 @@ +=') + && ini_get('session.upload_progress.enabled') +) { + + $sessionupload = array(); + define('UPLOAD_PREFIX', ini_get('session.upload_progress.prefix')); + + session_start(); + foreach ($_SESSION as $key => $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'; +require_once 'libraries/display_import_ajax.lib.php'; + +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]); + } + } +} + +// AJAX requests can't be cached! +PMA_noCacheHeader(); + +// $_GET["message"] is used for asking for an import message +if (isset($_GET["message"]) && $_GET["message"]) { + + header('Content-type: text/html'); + + // wait 0.3 sec before we check for $_SESSION variable, + // which is set inside import.php + usleep(300000); + + // wait until message is available + while ($_SESSION['Import_message']['message'] == null) { + usleep(250000); // 0.25 sec + } + + echo $_SESSION['Import_message']['message']; + echo '
      ' . "\n"; + echo ' [ ' . __('Back') . ' ]' . "\n"; + echo '
      '."\n"; + +} else { + PMA_importAjaxStatus($_GET["id"]); +} +?> diff --git a/sources/index.php b/sources/index.php new file mode 100644 index 0000000..61328fd --- /dev/null +++ b/sources/index.php @@ -0,0 +1,653 @@ +addJSON( + 'options', + PMA_RecentTable::getInstance()->getHtmlSelectOption() + ); + exit; +} + +if ($GLOBALS['PMA_Config']->isGitRevision()) { + if (isset($_REQUEST['git_revision']) && $GLOBALS['is_ajax_request'] == true) { + PMA_printGitRevision(); + exit; + } + 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 PMA_Util::getMessage($message); + unset($message); +} + +$common_url_query = PMA_URL_getCommon('', ''); + +// 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'; + include 'libraries/StorageEngine.class.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"; +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 '
      • '; + include_once 'libraries/select_server.lib.php'; + echo PMA_Util::getImage('s_host.png') . " " . PMA_selectServer(true, true); + echo '
      • '; + } + + /** + * Displays the mysql server related links + */ + if ($server > 0 && ! PMA_DRIZZLE) { + include_once 'libraries/check_user_privileges.lib.php'; + + // Logout for advanced authentication + if ($cfg['Server']['auth_type'] != 'config') { + if ($cfg['ShowChgPassword']) { + $conditional_class = 'ajax'; + PMA_printListItem( + PMA_Util::getImage('s_passwd.png') . " " . __('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" + . PMA_URL_getHiddenInputs(null, null, 4, 'collation_connection') + . ' ' . "\n" + + . PMA_generateCharsetDropdownBox( + PMA_CSDROPDOWN_COLLATION, + 'collation_connection', + 'select_collation_connection', + $collation_connection, + true, + 4, + true + ) + . '
        ' . "\n" + . '
      • ' . "\n"; + } // end of if ($server > 0 && !PMA_DRIZZLE) + echo '
      '; + echo '
      '; +} + +echo '
      '; +echo '

      ' . __('Appearance Settings') . '

      '; +echo '
        '; + +// Displays language selection combo +if (empty($cfg['Lang']) && count($GLOBALS['available_languages']) > 1) { + echo '
      • '; + include_once 'libraries/display_select_lang.lib.php'; + echo PMA_Util::getImage('s_lang.png') . " " . PMA_getLanguageSelectorHtml(); + echo '
      • '; +} + +// ThemeManager if available + +if ($GLOBALS['cfg']['ThemeManager']) { + echo '
      • '; + echo PMA_Util::getImage('s_theme.png') . " " + . $_SESSION['PMA_Theme_Manager']->getHtmlSelectBox(); + echo '
      • '; +} +echo '
      • '; +echo PMA_Config::getFontsizeForm(); +echo '
      • '; + +echo '
      '; + +// User preferences + +if ($server > 0) { + echo '
        '; + PMA_printListItem( + PMA_Util::getImage('b_tblops.png')." " .__('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"; + PMA_printListItem( + __('Server:') . ' ' . $server_info, + 'li_server_info' + ); + PMA_printListItem( + __('Server type:') . ' ' . PMA_Util::getServerType(), + 'li_server_type' + ); + PMA_printListItem( + __('Server version:') + . ' ' + . PMA_MYSQL_STR_VERSION . ' - ' . PMA_MYSQL_VERSION_COMMENT, + 'li_server_version' + ); + PMA_printListItem( + __('Protocol version:') . ' ' . $GLOBALS['dbi']->getProtoInfo(), + 'li_mysql_proto' + ); + PMA_printListItem( + __('User:') . ' ' . htmlspecialchars($mysql_cur_user_and_host), + 'li_user_info' + ); + + echo '
      • '; + echo ' ' . __('Server charset:') . ' ' + . ' '; + if (! PMA_DRIZZLE) { + echo ' ' . $mysql_charsets_descriptions[$mysql_charset_map['utf-8']]; + } + echo ' (' . $mysql_charset_map['utf-8'] . ')' + . ' ' + . '
      • ' + . '
      ' + . '
      '; +} + +if ($GLOBALS['cfg']['ShowServerInfo'] || $GLOBALS['cfg']['ShowPhpInfo']) { + echo '
      '; + echo '

      ' . __('Web server') . '

      '; + echo '
        '; + if ($GLOBALS['cfg']['ShowServerInfo']) { + PMA_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) + && in_array( + $GLOBALS['cfg']['Server']['extension'], + array('mysql', 'mysqli') + ) + ) { + $client_version_str = 'libmysql - ' . $client_version_str; + } + PMA_printListItem( + __('Database client version:') . ' ' . $client_version_str, + 'li_mysql_client_version' + ); + + $php_ext_string = __('PHP extension:') . ' ' + . $GLOBALS['cfg']['Server']['extension'] . ' ' + . PMA_Util::showPHPDocu( + 'book.' . $GLOBALS['cfg']['Server']['extension'] . '.php' + ); + PMA_printListItem( + $php_ext_string, + 'li_used_php_extension' + ); + } + } + + if ($cfg['ShowPhpInfo']) { + PMA_printListItem( + __('Show PHP information'), + 'li_phpinfo', + 'phpinfo.php?' . $common_url_query, + null, + '_blank' + ); + } + echo '
      '; + echo '
      '; +} + +echo '
      '; +echo '

      phpMyAdmin

      '; +echo '
        '; +$class = null; +// We rely on CSP to allow access to http://www.phpmyadmin.net, but IE lacks +// support here and does not allow request to http once using https. +if ($GLOBALS['cfg']['VersionCheck'] + && (! $GLOBALS['PMA_Config']->get('is_https') || PMA_USR_BROWSER_AGENT != 'IE') +) { + $class = 'jsversioncheck'; +} +PMA_printListItem( + __('Version information:') . ' ' . PMA_VERSION, + 'li_pma_version', + null, + null, + null, + null, + $class +); +PMA_printListItem( + __('Documentation'), + 'li_pma_docs', + PMA_Util::getDocuLink('index'), + null, + '_blank' +); +PMA_printListItem( + __('Wiki'), + 'li_pma_wiki', + PMA_linkURL('http://wiki.phpmyadmin.net/'), + null, + '_blank' +); + +// does not work if no target specified, don't know why +PMA_printListItem( + __('Official Homepage'), + 'li_pma_homepage', + PMA_linkURL('http://www.phpMyAdmin.net/'), + null, + '_blank' +); +PMA_printListItem( + __('Contribute'), + 'li_pma_contribute', + PMA_linkURL('http://www.phpmyadmin.net/home_page/improve.php'), + null, + '_blank' +); +PMA_printListItem( + __('Get support'), + 'li_pma_support', + PMA_linkURL('http://www.phpmyadmin.net/home_page/support.php'), + null, + '_blank' +); +PMA_printListItem( + __('List of changes'), + 'li_pma_changes', + PMA_linkURL('changelog.php'), + null, + '_blank' +); +echo '
      '; +echo '
      '; + +echo '
      '; + +echo '
      '; + +/** + * Warning if using the default MySQL privileged account + */ +if ($server != 0 + && $cfg['Server']['user'] == 'root' + && $cfg['Server']['password'] == '' +) { + trigger_error( + __( + 'Your configuration file contains settings (root with no password)' + . ' that correspond to the default MySQL privileged account.' + . ' Your MySQL server is running with this default, is open to' + . ' intrusion, and you really should fix this security hole by' + . ' setting a password for user \'root\'.' + ), + E_USER_WARNING + ); +} + +/** + * As we try to handle charsets by ourself, mbstring overloads just + * break it, see bug 1063821. + */ +if (@extension_loaded('mbstring') && @ini_get('mbstring.func_overload') > 1) { + trigger_error( + __( + 'You have enabled mbstring.func_overload in your PHP ' + . 'configuration. This option is incompatible with phpMyAdmin ' + . 'and might cause some data to be corrupted!' + ), + E_USER_WARNING + ); +} + +/** + * mbstring is used for handling multibyte 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 + ); +} + +/** + * 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@http://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 will 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['auto_blowfish_secret']) + && empty($GLOBALS['cfg']['blowfish_secret']) +) { + trigger_error( + __('The configuration file now needs a secret passphrase (blowfish_secret).'), + 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 + ); +} + +if ($server > 0) { + $cfgRelation = PMA_getRelationsParam(); + if (! $cfgRelation['allworks'] + && $cfg['PmaNoRelation_DisableWarning'] == false + ) { + $msg = PMA_Message::notice(__('The phpMyAdmin configuration storage is not completely configured, some extended features have been deactivated. To find out why click %shere%s.')); + $msg->addParam( + '', + false + ); + $msg->addParam('', false); + /* Show error if user has configured something, notice elsewhere */ + if (!empty($cfg['Servers'][$server]['pmadb'])) { + $msg->isError(true); + } + $msg->display(); + } // end if +} + +/** + * Warning about different MySQL library and server version + * (a difference on the third digit does not count). + * If someday there is a constant that we can check about mysqlnd, + * we can use it instead of strpos(). + * If no default server is set, $GLOBALS['dbi'] is not defined yet. + * Drizzle can speak MySQL protocol, so don't warn about version mismatch for + * Drizzle servers. + */ +if (isset($GLOBALS['dbi']) + && !PMA_DRIZZLE + && $cfg['ServerLibraryDifference_DisableWarning'] == false +) { + $_client_info = $GLOBALS['dbi']->getClientInfo(); + if ($server > 0 + && strpos($_client_info, 'mysqlnd') === false + && substr(PMA_MYSQL_CLIENT_API, 0, 3) != substr(PMA_MYSQL_INT_VERSION, 0, 3) + ) { + trigger_error( + PMA_sanitize( + sprintf( + __('Your PHP MySQL library version %s differs from your MySQL server version %s. This may cause unpredictable behavior.'), + $_client_info, + substr( + PMA_MYSQL_STR_VERSION, + 0, + strpos(PMA_MYSQL_STR_VERSION . '-', '-') + ) + ) + ), + E_USER_NOTICE + ); + } + unset($_client_info); +} + +/** + * 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 + ); +} + +/** + * Warning about mcrypt. + */ +if (! function_exists('mcrypt_encrypt') + && ! $GLOBALS['cfg']['McryptDisableWarning'] +) { + PMA_warnMissingExtension('mcrypt'); +} + +/** + * 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@http://www.phpmyadmin.net/home_page/improve.php' + . '#translate@_blank]contributing[/a].', + E_USER_NOTICE + ); + } +} + +/** + * prints list item for main page + * + * @param string $name displayed text + * @param string $id 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 + */ +function PMA_printListItem($name, $id = null, $url = null, + $mysql_help_page = null, $target = null, $a_id = null, $class = null, + $a_class = null +) { + echo '
    • '; + if (null !== $url) { + echo ''; + } + + echo $name; + + if (null !== $url) { + echo '' . "\n"; + } + if (null !== $mysql_help_page) { + echo PMA_Util::showMySQLDocu($mysql_help_page); + } + echo '
    • '; +} +?> diff --git a/sources/js/OpenStreetMap.js b/sources/js/OpenStreetMap.js new file mode 100644 index 0000000..51694bc --- /dev/null +++ b/sources/js/OpenStreetMap.js @@ -0,0 +1,126 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Namespace: Util.OSM + */ +OpenLayers.Util.OSM = {}; + +/** + * Constant: MISSING_TILE_URL + * {String} URL of image to display for missing tiles + */ +OpenLayers.Util.OSM.MISSING_TILE_URL = "http://www.openstreetmap.org/openlayers/img/404.png"; + +/** + * Property: originalOnImageLoadError + * {Function} Original onImageLoadError function. + */ +OpenLayers.Util.OSM.originalOnImageLoadError = OpenLayers.Util.onImageLoadError; + +/** + * Function: onImageLoadError + */ +OpenLayers.Util.onImageLoadError = function () { + if (this.src.match(/^http:\/\/[abc]\.[a-z]+\.openstreetmap\.org\//)) { + this.src = OpenLayers.Util.OSM.MISSING_TILE_URL; + } else if (this.src.match(/^http:\/\/[def]\.tah\.openstreetmap\.org\//)) { + // do nothing - this layer is transparent + } else { + OpenLayers.Util.OSM.originalOnImageLoadError; + } +}; + +/** + * Class: OpenLayers.Layer.OSM.Mapnik + * + * Inherits from: + * - + */ +OpenLayers.Layer.OSM.Mapnik = OpenLayers.Class(OpenLayers.Layer.OSM, { + /** + * Constructor: OpenLayers.Layer.OSM.Mapnik + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function (name, options) { + var url = [ + "http://a.tile.openstreetmap.org/${z}/${x}/${y}.png", + "http://b.tile.openstreetmap.org/${z}/${x}/${y}.png", + "http://c.tile.openstreetmap.org/${z}/${x}/${y}.png" + ]; + options = OpenLayers.Util.extend({ + numZoomLevels: 19, + buffer: 0, + transitionEffect: "resize" + }, options); + var newArguments = [name, url, options]; + OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments); + }, + + CLASS_NAME: "OpenLayers.Layer.OSM.Mapnik" +}); + +/** + * Class: OpenLayers.Layer.OSM.Osmarender + * + * Inherits from: + * - + */ +OpenLayers.Layer.OSM.Osmarender = OpenLayers.Class(OpenLayers.Layer.OSM, { + /** + * Constructor: OpenLayers.Layer.OSM.Osmarender + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function (name, options) { + var url = [ + "http://a.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png", + "http://b.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png", + "http://c.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png" + ]; + options = OpenLayers.Util.extend({ + numZoomLevels: 18, + buffer: 0, + transitionEffect: "resize" + }, options); + var newArguments = [name, url, options]; + OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments); + }, + + CLASS_NAME: "OpenLayers.Layer.OSM.Osmarender" +}); + +/** + * Class: OpenLayers.Layer.OSM.CycleMap + * + * Inherits from: + * - + */ +OpenLayers.Layer.OSM.CycleMap = OpenLayers.Class(OpenLayers.Layer.OSM, { + /** + * Constructor: OpenLayers.Layer.OSM.CycleMap + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function (name, options) { + var url = [ + "http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png" + ]; + options = OpenLayers.Util.extend({ + numZoomLevels: 19, + buffer: 0, + transitionEffect: "resize" + }, options); + var newArguments = [name, url, options]; + OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments); + }, + + CLASS_NAME: "OpenLayers.Layer.OSM.CycleMap" +}); diff --git a/sources/js/ajax.js b/sources/js/ajax.js new file mode 100644 index 0000000..5ff0a68 --- /dev/null +++ b/sources/js/ajax.js @@ -0,0 +1,839 @@ +/* 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 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 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, hash = 0, 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 string file The filename for which to register the event + * @param 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).bind(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).bind(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 + ); + } + }, + /** + * 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')) { + 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(); + } + if (AJAX.active === true) { + // Silently bail out, there is already a request in progress. + // TODO: save a reference to the request and cancel the old request + // when the user requests something else. Something like this is + // already implemented in the PMA_fastFilter object in navigation.js + return false; + } + + AJAX.source = $(this); + + $('html, body').animate({scrollTop: 0}, 'fast'); + + var isLink = !! href || false; + var url = isLink ? href : $(this).attr('action'); + var params = 'ajax_request=true&ajax_page_request=true'; + if (! isLink) { + params += '&' + $(this).serialize(); + } + // Add a list of menu hashes that we have in the cache to the request + params += AJAX.cache.menus.getRequestParam(); + + if (AJAX._debug) { + console.log("Loading: " + url); // no need to translate + } + + if (isLink) { + AJAX.active = true; + AJAX.$msgbox = PMA_ajaxShowMessage(); + $.get(url, params, AJAX.responseHandler); + } 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 (data.success) { + $table_clone = false; + PMA_ajaxRemoveMessage(AJAX.$msgbox); + + if (data._redirect) { + PMA_ajaxShowMessage(data._redirect, false); + AJAX.active = false; + return; + } + + AJAX.scriptHandler.reset(function () { + if (data._reloadNavigation) { + PMA_reloadNavigation(); + } + if (data._reloadQuerywindow) { + var params = data._reloadQuerywindow; + PMA_querywindow.reload( + params.db, + params.table, + params.sql_query + ); + } + if (data._focusQuerywindow) { + PMA_querywindow.focus( + data._focusQuerywindow + ); + } + if (data._title) { + $('title').replaceWith(data._title); + } + if (data._menu) { + AJAX.cache.menus.replace(data._menu); + AJAX.cache.menus.add(data._menuHash, data._menu); + } else if (data._menuHash) { + AJAX.cache.menus.replace(AJAX.cache.menus.get(data._menuHash)); + } + + // Remove all containers that may have + // been added outside of #page_content + $('body').children() + .not('#pma_navigation') + .not('#floating_menubar') + .not('#goto_pagetop') + .not('#page_content') + .not('#selflink') + .not('#session_debug') + .not('#pma_header') + .not('#pma_footer') + .not('#pma_demo') + .remove(); + // Replace #page_content with new content + if (data.message && data.message.length > 0) { + $('#page_content').replaceWith( + "
      " + data.message + "
      " + ); + PMA_highlightSQL($('#page_content')); + } + + if (data._selflink) { + $('#selflink > a').attr('href', data._selflink); + } + if (data._scripts) { + AJAX.scriptHandler.load(data._scripts, data._params.token); + } + if (data._selflink && data._scripts && data._menuHash && data._params) { + AJAX.cache.add( + data._selflink, + data._scripts, + data._menuHash, + data._params, + AJAX.source.attr('rel') + ); + } + if (data._params) { + PMA_commonParams.setAll(data._params); + } + if (data._displayMessage) { + $('#page_content').prepend(data._displayMessage); + PMA_highlightSQL($('#page_content')); + } + + $('#pma_errors').remove(); + if (data._errors) { + $('
      ', {id : 'pma_errors'}) + .insertAfter('#selflink') + .append(data._errors); + } + + if (typeof AJAX._callback === 'function') { + AJAX._callback.call(); + } + AJAX._callback = function () {}; + }); + } else { + PMA_ajaxShowMessage(data.error, false); + AJAX.active = false; + } + }, + /** + * 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 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 event + */ + _scriptsToBeFired: [], + /** + * 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, token) { + var self = this; + self._scriptsToBeLoaded = []; + self._scriptsToBeFired = []; + for (var i in files) { + self._scriptsToBeLoaded.push(files[i].name); + if (files[i].fire) { + self._scriptsToBeFired.push(files[i].name); + } + } + // Generate a request string + var request = []; + var needRequest = false; + for (var index in self._scriptsToBeLoaded) { + var script = self._scriptsToBeLoaded[index]; + // Only for scripts that we don't already have + if ($.inArray(script, self._scripts) == -1) { + needRequest = true; + this.add(script); + request.push("scripts[]=" + script); + } + } + request.push("token=" + token); + request.push("call_done=1"); + // Download the composite js file, if necessary + if (needRequest) { + this.appendScript("js/get_scripts.js.php?" + request.join("&")); + } else { + self.done(); + } + }, + /** + * Called whenever all files are loaded + * + * @return void + */ + done: function () { + if (typeof ErrorReport !== 'undefined') { + ErrorReport.wrap_global_functions(); + } + for (var i in this._scriptsToBeFired) { + AJAX.fireOnload(this._scriptsToBeFired[i]); + } + AJAX.active = false; + }, + /** + * Appends a script element to the head to load the scripts + * + * @return void + */ + appendScript: function (url) { + var head = document.head || document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = url; + 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 + */ + $('a').die('click').live('click', AJAX.requestHandler); + $('form').die('submit').live('submit', AJAX.requestHandler); + AJAX.cache.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', ''); + } + }); +}); + +/** + * 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 + */ +AJAX.cache = { + /** + * @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 requured 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 > AJAX.cache.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 + }); + AJAX.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' + || ! AJAX.cache.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); + AJAX.cache.menus.replace(AJAX.cache.menus.get(record.menu)); + PMA_commonParams.setAll(record.params); + AJAX.scriptHandler.load(record.scripts, record.params ? record.params.token : PMA_commonParams.get('token')); + AJAX.cache.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, 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) > AJAX.cache.MAX) { + // when the cache grows, we remove the oldest entry + var oldest, key, 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 = '&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. + */ +AJAX.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 (jQuery.browser.mozilla) { + // 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 + */ + if (window.location.hash.substring(0, 8) == '#PMAURL-') { + // We have a valid hash, let's redirect the user + // to the page that it's pointing to + window.location = window.location.hash.substring( + window.location.hash.indexOf(':') + 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(':') + ); + AJAX.cache.navigate(index); + } + }); + }); + /** + * Publicly exposes a reference to the otherwise private setUrlHash function + */ + return setUrlHash; +})(jQuery, window); + +/** + * Page load event handler + */ +$(function () { + // Add the menu from the initial page into the cache + // The cache primer is set by the footer class + if (AJAX.cache.primer.url) { + AJAX.cache.menus.add( + AJAX.cache.primer.menuHash, + $('
      ') + .append('
      ') + .append($('#serverinfo').clone()) + .append($('#topmenucontainer').clone()) + .html() + ); + } + $(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 (AJAX.cache.primer.url) { + AJAX.cache.add( + AJAX.cache.primer.url, + AJAX.cache.primer.scripts, + AJAX.cache.primer.menuHash + ); + } + }); +}); + +/** + * Attach a generic event handler to clicks + * on pages and submissions of forms + */ +$('a').live('click', AJAX.requestHandler); +$('form').live('submit', AJAX.requestHandler); + +/** + * Gracefully handle fatal server errors + * (e.g: 500 - Internal server error) + */ +$(document).ajaxError(function (event, request, settings) { + if (request.status !== 0) { // Don't handle aborted requests + var errorCode = $.sprintf(PMA_messages.strErrorCode, request.status); + var errorText = $.sprintf(PMA_messages.strErrorText, request.statusText); + PMA_ajaxShowMessage( + '
      ' + + PMA_messages.strErrorProcessingRequest + + '
      ' + errorCode + '
      ' + + '
      ' + errorText + '
      ' + + '
      ', + false + ); + AJAX.active = false; + } +}); diff --git a/sources/js/canvg/MIT-LICENSE.txt b/sources/js/canvg/MIT-LICENSE.txt new file mode 100644 index 0000000..40f19bd --- /dev/null +++ b/sources/js/canvg/MIT-LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2010-2011 Gabe Lerner (gabelerner@gmail.com) - http://code.google.com/p/canvg/ + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/sources/js/canvg/canvg.js b/sources/js/canvg/canvg.js new file mode 100644 index 0000000..dbc5a9d --- /dev/null +++ b/sources/js/canvg/canvg.js @@ -0,0 +1,2509 @@ +/* + * canvg.js - Javascript SVG parser and renderer on Canvas + * MIT Licensed + * Gabe Lerner (gabelerner@gmail.com) + * http://code.google.com/p/canvg/ + * + * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/ + */ +if(!window.console) { + window.console = {}; + window.console.log = function(str) {}; + window.console.dir = function(str) {}; +} + +// <3 IE +if(!Array.indexOf){ + Array.prototype.indexOf = function(obj){ + for(var i=0; i ignore mouse events + // ignoreAnimation: true => ignore animations + // ignoreDimensions: true => does not try to resize canvas + // ignoreClear: true => does not clear canvas + // offsetX: int => draws at a x offset + // offsetY: int => draws at a y offset + // scaleWidth: int => scales horizontally to width + // scaleHeight: int => scales vertically to height + // renderCallback: function => will call the function after the first render is completed + // forceRedraw: function => will call the function on every frame, if it returns true, will redraw + this.canvg = function (target, s, opts) { + // no parameters + if (target == null && s == null && opts == null) { + var svgTags = document.getElementsByTagName('svg'); + for (var i=0; i]*>/, ''); + var xmlDoc = new ActiveXObject('Microsoft.XMLDOM'); + xmlDoc.async = 'false'; + xmlDoc.loadXML(xml); + return xmlDoc; + } + } + + svg.Property = function(name, value) { + this.name = name; + this.value = value; + + this.hasValue = function() { + return (this.value != null && this.value != ''); + } + + // return the numerical value of the property + this.numValue = function() { + if (!this.hasValue()) return 0; + + var n = parseFloat(this.value); + if ((this.value + '').match(/%$/)) { + n = n / 100.0; + } + return n; + } + + this.valueOrDefault = function(def) { + if (this.hasValue()) return this.value; + return def; + } + + this.numValueOrDefault = function(def) { + if (this.hasValue()) return this.numValue(); + return def; + } + + /* EXTENSIONS */ + var that = this; + + // color extensions + this.Color = { + // augment the current color value with the opacity + addOpacity: function(opacity) { + var newValue = that.value; + if (opacity != null && opacity != '') { + var color = new RGBColor(that.value); + if (color.ok) { + newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')'; + } + } + return new svg.Property(that.name, newValue); + } + } + + // definition extensions + this.Definition = { + // get the definition from the definitions table + getDefinition: function() { + var name = that.value.replace(/^(url\()?#([^\)]+)\)?$/, '$2'); + return svg.Definitions[name]; + }, + + isUrl: function() { + return that.value.indexOf('url(') == 0 + }, + + getFillStyle: function(e) { + var def = this.getDefinition(); + + // gradient + if (def != null && def.createGradient) { + return def.createGradient(svg.ctx, e); + } + + // pattern + if (def != null && def.createPattern) { + return def.createPattern(svg.ctx, e); + } + + return null; + } + } + + // length extensions + this.Length = { + DPI: function(viewPort) { + return 96.0; // TODO: compute? + }, + + EM: function(viewPort) { + var em = 12; + + var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize); + if (fontSize.hasValue()) em = fontSize.Length.toPixels(viewPort); + + return em; + }, + + // get the length as pixels + toPixels: function(viewPort) { + if (!that.hasValue()) return 0; + var s = that.value+''; + if (s.match(/em$/)) return that.numValue() * this.EM(viewPort); + if (s.match(/ex$/)) return that.numValue() * this.EM(viewPort) / 2.0; + if (s.match(/px$/)) return that.numValue(); + if (s.match(/pt$/)) return that.numValue() * 1.25; + if (s.match(/pc$/)) return that.numValue() * 15; + if (s.match(/cm$/)) return that.numValue() * this.DPI(viewPort) / 2.54; + if (s.match(/mm$/)) return that.numValue() * this.DPI(viewPort) / 25.4; + if (s.match(/in$/)) return that.numValue() * this.DPI(viewPort); + if (s.match(/%$/)) return that.numValue() * svg.ViewPort.ComputeSize(viewPort); + return that.numValue(); + } + } + + // time extensions + this.Time = { + // get the time as milliseconds + toMilliseconds: function() { + if (!that.hasValue()) return 0; + var s = that.value+''; + if (s.match(/s$/)) return that.numValue() * 1000; + if (s.match(/ms$/)) return that.numValue(); + return that.numValue(); + } + } + + // angle extensions + this.Angle = { + // get the angle as radians + toRadians: function() { + if (!that.hasValue()) return 0; + var s = that.value+''; + if (s.match(/deg$/)) return that.numValue() * (Math.PI / 180.0); + if (s.match(/grad$/)) return that.numValue() * (Math.PI / 200.0); + if (s.match(/rad$/)) return that.numValue(); + return that.numValue() * (Math.PI / 180.0); + } + } + } + + // fonts + svg.Font = new (function() { + this.Styles = ['normal','italic','oblique','inherit']; + this.Variants = ['normal','small-caps','inherit']; + this.Weights = ['normal','bold','bolder','lighter','100','200','300','400','500','600','700','800','900','inherit']; + + this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) { + var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font); + return { + fontFamily: fontFamily || f.fontFamily, + fontSize: fontSize || f.fontSize, + fontStyle: fontStyle || f.fontStyle, + fontWeight: fontWeight || f.fontWeight, + fontVariant: fontVariant || f.fontVariant, + toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') } + } + } + + var that = this; + this.Parse = function(s) { + var f = {}; + var d = svg.trim(svg.compressSpaces(s || '')).split(' '); + var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false } + var ff = ''; + for (var i=0; i this.x2) this.x2 = x; + } + + if (y != null) { + if (isNaN(this.y1) || isNaN(this.y2)) { + this.y1 = y; + this.y2 = y; + } + if (y < this.y1) this.y1 = y; + if (y > this.y2) this.y2 = y; + } + } + this.addX = function(x) { this.addPoint(x, null); } + this.addY = function(y) { this.addPoint(null, y); } + + this.addBoundingBox = function(bb) { + this.addPoint(bb.x1, bb.y1); + this.addPoint(bb.x2, bb.y2); + } + + this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) { + var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0) + var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0) + var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0) + var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0) + this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y); + } + + this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) { + // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html + var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y]; + this.addPoint(p0[0], p0[1]); + this.addPoint(p3[0], p3[1]); + + for (i=0; i<=1; i++) { + var f = function(t) { + return Math.pow(1-t, 3) * p0[i] + + 3 * Math.pow(1-t, 2) * t * p1[i] + + 3 * (1-t) * Math.pow(t, 2) * p2[i] + + Math.pow(t, 3) * p3[i]; + } + + var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; + var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; + var c = 3 * p1[i] - 3 * p0[i]; + + if (a == 0) { + if (b == 0) continue; + var t = -c / b; + if (0 < t && t < 1) { + if (i == 0) this.addX(f(t)); + if (i == 1) this.addY(f(t)); + } + continue; + } + + var b2ac = Math.pow(b, 2) - 4 * c * a; + if (b2ac < 0) continue; + var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); + if (0 < t1 && t1 < 1) { + if (i == 0) this.addX(f(t1)); + if (i == 1) this.addY(f(t1)); + } + var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); + if (0 < t2 && t2 < 1) { + if (i == 0) this.addX(f(t2)); + if (i == 1) this.addY(f(t2)); + } + } + } + + this.isPointInBox = function(x, y) { + return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2); + } + + this.addPoint(x1, y1); + this.addPoint(x2, y2); + } + + // transforms + svg.Transform = function(v) { + var that = this; + this.Type = {} + + // translate + this.Type.translate = function(s) { + this.p = svg.CreatePoint(s); + this.apply = function(ctx) { + ctx.translate(this.p.x || 0.0, this.p.y || 0.0); + } + this.applyToPoint = function(p) { + p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]); + } + } + + // rotate + this.Type.rotate = function(s) { + var a = svg.ToNumberArray(s); + this.angle = new svg.Property('angle', a[0]); + this.cx = a[1] || 0; + this.cy = a[2] || 0; + this.apply = function(ctx) { + ctx.translate(this.cx, this.cy); + ctx.rotate(this.angle.Angle.toRadians()); + ctx.translate(-this.cx, -this.cy); + } + this.applyToPoint = function(p) { + var a = this.angle.Angle.toRadians(); + p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]); + p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]); + p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]); + } + } + + this.Type.scale = function(s) { + this.p = svg.CreatePoint(s); + this.apply = function(ctx) { + ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0); + } + this.applyToPoint = function(p) { + p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]); + } + } + + this.Type.matrix = function(s) { + this.m = svg.ToNumberArray(s); + this.apply = function(ctx) { + ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]); + } + this.applyToPoint = function(p) { + p.applyTransform(this.m); + } + } + + this.Type.SkewBase = function(s) { + this.base = that.Type.matrix; + this.base(s); + this.angle = new svg.Property('angle', s); + } + this.Type.SkewBase.prototype = new this.Type.matrix; + + this.Type.skewX = function(s) { + this.base = that.Type.SkewBase; + this.base(s); + this.m = [1, 0, Math.tan(this.angle.Angle.toRadians()), 1, 0, 0]; + } + this.Type.skewX.prototype = new this.Type.SkewBase; + + this.Type.skewY = function(s) { + this.base = that.Type.SkewBase; + this.base(s); + this.m = [1, Math.tan(this.angle.Angle.toRadians()), 0, 1, 0, 0]; + } + this.Type.skewY.prototype = new this.Type.SkewBase; + + this.transforms = []; + + this.apply = function(ctx) { + for (var i=0; i= this.tokens.length - 1; + } + + this.isCommandOrEnd = function() { + if (this.isEnd()) return true; + return this.tokens[this.i + 1].match(/[A-Za-z]/) != null; + } + + this.isRelativeCommand = function() { + return this.command == this.command.toLowerCase(); + } + + this.getToken = function() { + this.i = this.i + 1; + return this.tokens[this.i]; + } + + this.getScalar = function() { + return parseFloat(this.getToken()); + } + + this.nextCommand = function() { + this.previousCommand = this.command; + this.command = this.getToken(); + } + + this.getPoint = function() { + var p = new svg.Point(this.getScalar(), this.getScalar()); + return this.makeAbsolute(p); + } + + this.getAsControlPoint = function() { + var p = this.getPoint(); + this.control = p; + return p; + } + + this.getAsCurrentPoint = function() { + var p = this.getPoint(); + this.current = p; + return p; + } + + this.getReflectedControlPoint = function() { + if (this.previousCommand.toLowerCase() != 'c' && this.previousCommand.toLowerCase() != 's') { + return this.current; + } + + // reflect point + var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y); + return p; + } + + this.makeAbsolute = function(p) { + if (this.isRelativeCommand()) { + p.x = this.current.x + p.x; + p.y = this.current.y + p.y; + } + return p; + } + + this.addMarker = function(p, from) { + this.addMarkerAngle(p, from == null ? null : from.angleTo(p)); + } + + this.addMarkerAngle = function(p, a) { + this.points.push(p); + this.angles.push(a); + } + + this.getMarkerPoints = function() { return this.points; } + this.getMarkerAngles = function() { + for (var i=0; i 1) { + rx *= Math.sqrt(l); + ry *= Math.sqrt(l); + } + // cx', cy' + var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt( + ((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) / + (Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2)) + ); + if (isNaN(s)) s = 0; + var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx); + // cx, cy + var centp = new svg.Point( + (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y, + (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y + ); + // vector magnitude + var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); } + // ratio between two vectors + var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) } + // angle between two vectors + var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); } + // initial angle + var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]); + // angle delta + var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]; + var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry]; + var ad = a(u, v); + if (r(u,v) <= -1) ad = Math.PI; + if (r(u,v) >= 1) ad = 0; + + if (sweepFlag == 0 && ad > 0) ad = ad - 2 * Math.PI; + if (sweepFlag == 1 && ad < 0) ad = ad + 2 * Math.PI; + + // for markers + var halfWay = new svg.Point( + centp.x - rx * Math.cos((a1 + ad) / 2), + centp.y - ry * Math.sin((a1 + ad) / 2) + ); + pp.addMarkerAngle(halfWay, (a1 + ad) / 2 + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2); + pp.addMarkerAngle(cp, ad + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2); + + bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better + if (ctx != null) { + var r = rx > ry ? rx : ry; + var sx = rx > ry ? 1 : rx / ry; + var sy = rx > ry ? ry / rx : 1; + + ctx.translate(centp.x, centp.y); + ctx.rotate(xAxisRotation); + ctx.scale(sx, sy); + ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag); + ctx.scale(1/sx, 1/sy); + ctx.rotate(-xAxisRotation); + ctx.translate(-centp.x, -centp.y); + } + } + break; + case 'Z': + if (ctx != null) ctx.closePath(); + pp.current = pp.start; + } + } + + return bb; + } + + this.getMarkers = function() { + var points = this.PathParser.getMarkerPoints(); + var angles = this.PathParser.getMarkerAngles(); + + var markers = []; + for (var i=0; i this.maxDuration) { + // loop for indefinitely repeating animations + if (this.attribute('repeatCount').value == 'indefinite') { + this.duration = 0.0 + } + else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) { + this.removed = true; + this.getProperty().value = this.initialValue; + return true; + } + else { + return false; // no updates made + } + } + this.duration = this.duration + delta; + + // if we're past the begin time + var updated = false; + if (this.begin < this.duration) { + var newValue = this.calcValue(); // tween + + if (this.attribute('type').hasValue()) { + // for transform, etc. + var type = this.attribute('type').value; + newValue = type + '(' + newValue + ')'; + } + + this.getProperty().value = newValue; + updated = true; + } + + return updated; + } + + // fraction of duration we've covered + this.progress = function() { + return ((this.duration - this.begin) / (this.maxDuration - this.begin)); + } + } + svg.Element.AnimateBase.prototype = new svg.Element.ElementBase; + + // animate element + svg.Element.animate = function(node) { + this.base = svg.Element.AnimateBase; + this.base(node); + + this.calcValue = function() { + var from = this.attribute('from').numValue(); + var to = this.attribute('to').numValue(); + + // tween value linearly + return from + (to - from) * this.progress(); + }; + } + svg.Element.animate.prototype = new svg.Element.AnimateBase; + + // animate color element + svg.Element.animateColor = function(node) { + this.base = svg.Element.AnimateBase; + this.base(node); + + this.calcValue = function() { + var from = new RGBColor(this.attribute('from').value); + var to = new RGBColor(this.attribute('to').value); + + if (from.ok && to.ok) { + // tween color linearly + var r = from.r + (to.r - from.r) * this.progress(); + var g = from.g + (to.g - from.g) * this.progress(); + var b = from.b + (to.b - from.b) * this.progress(); + return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')'; + } + return this.attribute('from').value; + }; + } + svg.Element.animateColor.prototype = new svg.Element.AnimateBase; + + // animate transform element + svg.Element.animateTransform = function(node) { + this.base = svg.Element.animate; + this.base(node); + } + svg.Element.animateTransform.prototype = new svg.Element.animate; + + // text element + svg.Element.text = function(node) { + this.base = svg.Element.RenderedElementBase; + this.base(node); + + if (node != null) { + // add children + this.children = []; + for (var i=0; i + * @link http://www.phpied.com/rgb-color-parser-in-javascript/ + * @license Use it if you like it + */ +function RGBColor(color_string) +{ + this.ok = false; + + // strip any leading # + if (color_string.charAt(0) == '#') { // remove # if any + color_string = color_string.substr(1,6); + } + + color_string = color_string.replace(/ /g,''); + color_string = color_string.toLowerCase(); + + // before getting into regexps, try simple matches + // and overwrite the input + var simple_colors = { + aliceblue: 'f0f8ff', + antiquewhite: 'faebd7', + aqua: '00ffff', + aquamarine: '7fffd4', + azure: 'f0ffff', + beige: 'f5f5dc', + bisque: 'ffe4c4', + black: '000000', + blanchedalmond: 'ffebcd', + blue: '0000ff', + blueviolet: '8a2be2', + brown: 'a52a2a', + burlywood: 'deb887', + cadetblue: '5f9ea0', + chartreuse: '7fff00', + chocolate: 'd2691e', + coral: 'ff7f50', + cornflowerblue: '6495ed', + cornsilk: 'fff8dc', + crimson: 'dc143c', + cyan: '00ffff', + darkblue: '00008b', + darkcyan: '008b8b', + darkgoldenrod: 'b8860b', + darkgray: 'a9a9a9', + darkgreen: '006400', + darkkhaki: 'bdb76b', + darkmagenta: '8b008b', + darkolivegreen: '556b2f', + darkorange: 'ff8c00', + darkorchid: '9932cc', + darkred: '8b0000', + darksalmon: 'e9967a', + darkseagreen: '8fbc8f', + darkslateblue: '483d8b', + darkslategray: '2f4f4f', + darkturquoise: '00ced1', + darkviolet: '9400d3', + deeppink: 'ff1493', + deepskyblue: '00bfff', + dimgray: '696969', + dodgerblue: '1e90ff', + feldspar: 'd19275', + firebrick: 'b22222', + floralwhite: 'fffaf0', + forestgreen: '228b22', + fuchsia: 'ff00ff', + gainsboro: 'dcdcdc', + ghostwhite: 'f8f8ff', + gold: 'ffd700', + goldenrod: 'daa520', + gray: '808080', + green: '008000', + greenyellow: 'adff2f', + honeydew: 'f0fff0', + hotpink: 'ff69b4', + indianred : 'cd5c5c', + indigo : '4b0082', + ivory: 'fffff0', + khaki: 'f0e68c', + lavender: 'e6e6fa', + lavenderblush: 'fff0f5', + lawngreen: '7cfc00', + lemonchiffon: 'fffacd', + lightblue: 'add8e6', + lightcoral: 'f08080', + lightcyan: 'e0ffff', + lightgoldenrodyellow: 'fafad2', + lightgrey: 'd3d3d3', + lightgreen: '90ee90', + lightpink: 'ffb6c1', + lightsalmon: 'ffa07a', + lightseagreen: '20b2aa', + lightskyblue: '87cefa', + lightslateblue: '8470ff', + lightslategray: '778899', + lightsteelblue: 'b0c4de', + lightyellow: 'ffffe0', + lime: '00ff00', + limegreen: '32cd32', + linen: 'faf0e6', + magenta: 'ff00ff', + maroon: '800000', + mediumaquamarine: '66cdaa', + mediumblue: '0000cd', + mediumorchid: 'ba55d3', + mediumpurple: '9370d8', + mediumseagreen: '3cb371', + mediumslateblue: '7b68ee', + mediumspringgreen: '00fa9a', + mediumturquoise: '48d1cc', + mediumvioletred: 'c71585', + midnightblue: '191970', + mintcream: 'f5fffa', + mistyrose: 'ffe4e1', + moccasin: 'ffe4b5', + navajowhite: 'ffdead', + navy: '000080', + oldlace: 'fdf5e6', + olive: '808000', + olivedrab: '6b8e23', + orange: 'ffa500', + orangered: 'ff4500', + orchid: 'da70d6', + palegoldenrod: 'eee8aa', + palegreen: '98fb98', + paleturquoise: 'afeeee', + palevioletred: 'd87093', + papayawhip: 'ffefd5', + peachpuff: 'ffdab9', + peru: 'cd853f', + pink: 'ffc0cb', + plum: 'dda0dd', + powderblue: 'b0e0e6', + purple: '800080', + red: 'ff0000', + rosybrown: 'bc8f8f', + royalblue: '4169e1', + saddlebrown: '8b4513', + salmon: 'fa8072', + sandybrown: 'f4a460', + seagreen: '2e8b57', + seashell: 'fff5ee', + sienna: 'a0522d', + silver: 'c0c0c0', + skyblue: '87ceeb', + slateblue: '6a5acd', + slategray: '708090', + snow: 'fffafa', + springgreen: '00ff7f', + steelblue: '4682b4', + tan: 'd2b48c', + teal: '008080', + thistle: 'd8bfd8', + tomato: 'ff6347', + turquoise: '40e0d0', + violet: 'ee82ee', + violetred: 'd02090', + wheat: 'f5deb3', + white: 'ffffff', + whitesmoke: 'f5f5f5', + yellow: 'ffff00', + yellowgreen: '9acd32' + }; + for (var key in simple_colors) { + if (color_string == key) { + color_string = simple_colors[key]; + } + } + // emd of simple type-in colors + + // array of color definition objects + var color_defs = [ + { + re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/, + example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'], + process: function (bits){ + return [ + parseInt(bits[1]), + parseInt(bits[2]), + parseInt(bits[3]) + ]; + } + }, + { + re: /^(\w{2})(\w{2})(\w{2})$/, + example: ['#00ff00', '336699'], + process: function (bits){ + return [ + parseInt(bits[1], 16), + parseInt(bits[2], 16), + parseInt(bits[3], 16) + ]; + } + }, + { + re: /^(\w{1})(\w{1})(\w{1})$/, + example: ['#fb0', 'f0f'], + process: function (bits){ + return [ + parseInt(bits[1] + bits[1], 16), + parseInt(bits[2] + bits[2], 16), + parseInt(bits[3] + bits[3], 16) + ]; + } + } + ]; + + // search through the definitions to find a match + for (var i = 0; i < color_defs.length; i++) { + var re = color_defs[i].re; + var processor = color_defs[i].process; + var bits = re.exec(color_string); + if (bits) { + channels = processor(bits); + this.r = channels[0]; + this.g = channels[1]; + this.b = channels[2]; + this.ok = true; + } + + } + + // validate/cleanup values + this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r); + this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g); + this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b); + + // some getters + this.toRGB = function () { + return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')'; + } + this.toHex = function () { + var r = this.r.toString(16); + var g = this.g.toString(16); + var b = this.b.toString(16); + if (r.length == 1) r = '0' + r; + if (g.length == 1) g = '0' + g; + if (b.length == 1) b = '0' + b; + return '#' + r + g + b; + } + + // help + this.getHelpXML = function () { + + var examples = new Array(); + // add regexps + for (var i = 0; i < color_defs.length; i++) { + var example = color_defs[i].example; + for (var j = 0; j < example.length; j++) { + examples[examples.length] = example[j]; + } + } + // add type-in colors + for (var sc in simple_colors) { + examples[examples.length] = sc; + } + + var xml = document.createElement('ul'); + xml.setAttribute('id', 'rgbcolor-examples'); + for (var i = 0; i < examples.length; i++) { + try { + var list_item = document.createElement('li'); + var list_color = new RGBColor(examples[i]); + var example_div = document.createElement('div'); + example_div.style.cssText = + 'margin: 3px; ' + + 'border: 1px solid black; ' + + 'background:' + list_color.toHex() + '; ' + + 'color:' + list_color.toHex() + ; + example_div.appendChild(document.createTextNode('test')); + var list_item_value = document.createTextNode( + ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex() + ); + list_item.appendChild(example_div); + list_item.appendChild(list_item_value); + xml.appendChild(list_item); + + } catch(e){} + } + return xml; + + } + +} \ No newline at end of file diff --git a/sources/js/canvg/flashcanvas.js b/sources/js/canvg/flashcanvas.js new file mode 100644 index 0000000..6ecaa94 --- /dev/null +++ b/sources/js/canvg/flashcanvas.js @@ -0,0 +1,1093 @@ +/* + * FlashCanvas + * + * Copyright (c) 2009 Tim Cameron Ryan + * Copyright (c) 2009-2011 FlashCanvas Project + * Released under the MIT/X License + */ + +// Reference: +// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html +// http://dev.w3.org/html5/spec/the-canvas-element.html + +// If the browser is IE and does not support HTML5 Canvas +if (window["ActiveXObject"] && !window["CanvasRenderingContext2D"]) { + +(function(window, document, undefined) { + +/* + * Constant + */ + +var NULL = null; +var CANVAS = "canvas"; +var CANVAS_RENDERING_CONTEXT_2D = "CanvasRenderingContext2D"; +var CANVAS_GRADIENT = "CanvasGradient"; +var CANVAS_PATTERN = "CanvasPattern"; +var FLASH_CANVAS = "FlashCanvas"; +var G_VML_CANVAS_MANAGER = "G_vmlCanvasManager"; +var OBJECT_ID_PREFIX = "external"; +var ON_FOCUS = "onfocus"; +var ON_PROPERTY_CHANGE = "onpropertychange"; +var ON_READY_STATE_CHANGE = "onreadystatechange"; +var ON_UNLOAD = "onunload"; + +var config = window[FLASH_CANVAS + "Options"] || {}; +var BASE_URL = config["swfPath"] || getScriptUrl().replace(/[^\/]+$/, ""); +var SWF_URL = BASE_URL + "flashcanvas.swf"; + +// DOMException code +var INDEX_SIZE_ERR = 1; +var NOT_SUPPORTED_ERR = 9; +var INVALID_STATE_ERR = 11; +var SYNTAX_ERR = 12; +var TYPE_MISMATCH_ERR = 17; +var SECURITY_ERR = 18; + +/** + * @constructor + */ +function Lookup(array) { + for (var i = 0, n = array.length; i < n; i++) + this[array[i]] = i; +} + +var properties = new Lookup([ + // Canvas element + "toDataURL", + + // CanvasRenderingContext2D + "save", + "restore", + "scale", + "rotate", + "translate", + "transform", + "setTransform", + "globalAlpha", + "globalCompositeOperation", + "strokeStyle", + "fillStyle", + "createLinearGradient", + "createRadialGradient", + "createPattern", + "lineWidth", + "lineCap", + "lineJoin", + "miterLimit", + "shadowOffsetX", + "shadowOffsetY", + "shadowBlur", + "shadowColor", + "clearRect", + "fillRect", + "strokeRect", + "beginPath", + "closePath", + "moveTo", + "lineTo", + "quadraticCurveTo", + "bezierCurveTo", + "arcTo", + "rect", + "arc", + "fill", + "stroke", + "clip", + "isPointInPath", +// "drawFocusRing", + "font", + "textAlign", + "textBaseline", + "fillText", + "strokeText", + "measureText", + "drawImage", + "createImageData", + "getImageData", + "putImageData", + + // CanvasGradient + "addColorStop", + + // Internal use + "direction", + "resize" +]); + +// Whether swf is ready for use +var isReady = {}; + +// Monitor the number of loading files +var lock = {}; + +// Canvas elements +var canvases = {}; + +// SPAN element embedded in the canvas +var spans = {}; + +/** + * 2D context + * @constructor + */ +var CanvasRenderingContext2D = function(canvas, swf) { + // back-reference to the canvas + this.canvas = canvas; + + // back-reference to the swf + this._swf = swf; + + // unique ID of canvas + this._canvasId = swf.id.slice(8); + + // initialize drawing states + this._initialize(); + + // Count CanvasGradient and CanvasPattern objects + this._gradientPatternId = 0; + + // Directionality of the canvas element + this._direction = ""; + + // frame update interval + var self = this; + setInterval(function() { + if (lock[self._canvasId] === 0) { + self._executeCommand(); + } + }, 30); +}; + +CanvasRenderingContext2D.prototype = { + /* + * state + */ + + save: function() { + // write all properties + this._setCompositing(); + this._setShadows(); + this._setStrokeStyle(); + this._setFillStyle(); + this._setLineStyles(); + this._setFontStyles(); + + // push state + this._stateStack.push([ + this._globalAlpha, + this._globalCompositeOperation, + this._strokeStyle, + this._fillStyle, + this._lineWidth, + this._lineCap, + this._lineJoin, + this._miterLimit, + this._shadowOffsetX, + this._shadowOffsetY, + this._shadowBlur, + this._shadowColor, + this._font, + this._textAlign, + this._textBaseline + ]); + + this._queue.push(properties.save); + }, + + restore: function() { + // pop state + var stateStack = this._stateStack; + if (stateStack.length) { + var state = stateStack.pop(); + this.globalAlpha = state[0]; + this.globalCompositeOperation = state[1]; + this.strokeStyle = state[2]; + this.fillStyle = state[3]; + this.lineWidth = state[4]; + this.lineCap = state[5]; + this.lineJoin = state[6]; + this.miterLimit = state[7]; + this.shadowOffsetX = state[8]; + this.shadowOffsetY = state[9]; + this.shadowBlur = state[10]; + this.shadowColor = state[11]; + this.font = state[12]; + this.textAlign = state[13]; + this.textBaseline = state[14]; + } + + this._queue.push(properties.restore); + }, + + /* + * transformations + */ + + scale: function(x, y) { + this._queue.push(properties.scale, x, y); + }, + + rotate: function(angle) { + this._queue.push(properties.rotate, angle); + }, + + translate: function(x, y) { + this._queue.push(properties.translate, x, y); + }, + + transform: function(m11, m12, m21, m22, dx, dy) { + this._queue.push(properties.transform, m11, m12, m21, m22, dx, dy); + }, + + setTransform: function(m11, m12, m21, m22, dx, dy) { + this._queue.push(properties.setTransform, m11, m12, m21, m22, dx, dy); + }, + + /* + * compositing + */ + + _setCompositing: function() { + var queue = this._queue; + if (this._globalAlpha !== this.globalAlpha) { + this._globalAlpha = this.globalAlpha; + queue.push(properties.globalAlpha, this._globalAlpha); + } + if (this._globalCompositeOperation !== this.globalCompositeOperation) { + this._globalCompositeOperation = this.globalCompositeOperation; + queue.push(properties.globalCompositeOperation, this._globalCompositeOperation); + } + }, + + /* + * colors and styles + */ + + _setStrokeStyle: function() { + if (this._strokeStyle !== this.strokeStyle) { + var style = this._strokeStyle = this.strokeStyle; + this._queue.push(properties.strokeStyle, (typeof style === "object") ? style.id : style); + } + }, + + _setFillStyle: function() { + if (this._fillStyle !== this.fillStyle) { + var style = this._fillStyle = this.fillStyle; + this._queue.push(properties.fillStyle, (typeof style === "object") ? style.id : style); + } + }, + + createLinearGradient: function(x0, y0, x1, y1) { + // If any of the arguments are not finite numbers, throws a + // NOT_SUPPORTED_ERR exception. + if (!(isFinite(x0) && isFinite(y0) && isFinite(x1) && isFinite(y1))) { + throwException(NOT_SUPPORTED_ERR); + } + + this._queue.push(properties.createLinearGradient, x0, y0, x1, y1); + return new CanvasGradient(this); + }, + + createRadialGradient: function(x0, y0, r0, x1, y1, r1) { + // If any of the arguments are not finite numbers, throws a + // NOT_SUPPORTED_ERR exception. + if (!(isFinite(x0) && isFinite(y0) && isFinite(r0) && + isFinite(x1) && isFinite(y1) && isFinite(r1))) { + throwException(NOT_SUPPORTED_ERR); + } + + // If either of the radii are negative, throws an INDEX_SIZE_ERR + // exception. + if (r0 < 0 || r1 < 0) { + throwException(INDEX_SIZE_ERR); + } + + this._queue.push(properties.createRadialGradient, x0, y0, r0, x1, y1, r1); + return new CanvasGradient(this); + }, + + createPattern: function(image, repetition) { + // If the image is null, the implementation must raise a + // TYPE_MISMATCH_ERR exception. + if (!image) { + throwException(TYPE_MISMATCH_ERR); + } + + var tagName = image.tagName, src; + var canvasId = this._canvasId; + + // If the first argument isn't an img, canvas, or video element, + // throws a TYPE_MISMATCH_ERR exception. + if (tagName) { + tagName = tagName.toLowerCase(); + if (tagName === "img") { + src = image.getAttribute("src", 2); + } else if (tagName === CANVAS || tagName === "video") { + // For now, only HTMLImageElement is supported. + return; + } else { + throwException(TYPE_MISMATCH_ERR); + } + } + + // Additionally, we accept any object that has a src property. + // This is useful when you'd like to specify a long data URI. + else if (image.src) { + src = image.src; + } else { + throwException(TYPE_MISMATCH_ERR); + } + + // If the second argument isn't one of the allowed values, throws a + // SYNTAX_ERR exception. + if (!(repetition === "repeat" || repetition === "no-repeat" || + repetition === "repeat-x" || repetition === "repeat-y" || + repetition === "" || repetition === NULL)) { + throwException(SYNTAX_ERR); + } + + // Special characters in the filename need escaping. + this._queue.push(properties.createPattern, encodeXML(src), repetition); + + if (isReady[canvasId]) { + this._executeCommand(); + ++lock[canvasId]; + } + + return new CanvasPattern(this); + }, + + /* + * line caps/joins + */ + + _setLineStyles: function() { + var queue = this._queue; + if (this._lineWidth !== this.lineWidth) { + this._lineWidth = this.lineWidth; + queue.push(properties.lineWidth, this._lineWidth); + } + if (this._lineCap !== this.lineCap) { + this._lineCap = this.lineCap; + queue.push(properties.lineCap, this._lineCap); + } + if (this._lineJoin !== this.lineJoin) { + this._lineJoin = this.lineJoin; + queue.push(properties.lineJoin, this._lineJoin); + } + if (this._miterLimit !== this.miterLimit) { + this._miterLimit = this.miterLimit; + queue.push(properties.miterLimit, this._miterLimit); + } + }, + + /* + * shadows + */ + + _setShadows: function() { + var queue = this._queue; + if (this._shadowOffsetX !== this.shadowOffsetX) { + this._shadowOffsetX = this.shadowOffsetX; + queue.push(properties.shadowOffsetX, this._shadowOffsetX); + } + if (this._shadowOffsetY !== this.shadowOffsetY) { + this._shadowOffsetY = this.shadowOffsetY; + queue.push(properties.shadowOffsetY, this._shadowOffsetY); + } + if (this._shadowBlur !== this.shadowBlur) { + this._shadowBlur = this.shadowBlur; + queue.push(properties.shadowBlur, this._shadowBlur); + } + if (this._shadowColor !== this.shadowColor) { + this._shadowColor = this.shadowColor; + queue.push(properties.shadowColor, this._shadowColor); + } + }, + + /* + * rects + */ + + clearRect: function(x, y, w, h) { + this._queue.push(properties.clearRect, x, y, w, h); + }, + + fillRect: function(x, y, w, h) { + this._setCompositing(); + this._setShadows(); + this._setFillStyle(); + this._queue.push(properties.fillRect, x, y, w, h); + }, + + strokeRect: function(x, y, w, h) { + this._setCompositing(); + this._setShadows(); + this._setStrokeStyle(); + this._setLineStyles(); + this._queue.push(properties.strokeRect, x, y, w, h); + }, + + /* + * path API + */ + + beginPath: function() { + this._queue.push(properties.beginPath); + }, + + closePath: function() { + this._queue.push(properties.closePath); + }, + + moveTo: function(x, y) { + this._queue.push(properties.moveTo, x, y); + }, + + lineTo: function(x, y) { + this._queue.push(properties.lineTo, x, y); + }, + + quadraticCurveTo: function(cpx, cpy, x, y) { + this._queue.push(properties.quadraticCurveTo, cpx, cpy, x, y); + }, + + bezierCurveTo: function(cp1x, cp1y, cp2x, cp2y, x, y) { + this._queue.push(properties.bezierCurveTo, cp1x, cp1y, cp2x, cp2y, x, y); + }, + + arcTo: function(x1, y1, x2, y2, radius) { + // Throws an INDEX_SIZE_ERR exception if the given radius is negative. + if (radius < 0 && isFinite(radius)) { + throwException(INDEX_SIZE_ERR); + } + + this._queue.push(properties.arcTo, x1, y1, x2, y2, radius); + }, + + rect: function(x, y, w, h) { + this._queue.push(properties.rect, x, y, w, h); + }, + + arc: function(x, y, radius, startAngle, endAngle, anticlockwise) { + // Throws an INDEX_SIZE_ERR exception if the given radius is negative. + if (radius < 0 && isFinite(radius)) { + throwException(INDEX_SIZE_ERR); + } + + this._queue.push(properties.arc, x, y, radius, startAngle, endAngle, anticlockwise ? 1 : 0); + }, + + fill: function() { + this._setCompositing(); + this._setShadows(); + this._setFillStyle(); + this._queue.push(properties.fill); + }, + + stroke: function() { + this._setCompositing(); + this._setShadows(); + this._setStrokeStyle(); + this._setLineStyles(); + this._queue.push(properties.stroke); + }, + + clip: function() { + this._queue.push(properties.clip); + }, + + isPointInPath: function(x, y) { + // TODO: Implement + }, + + /* + * text + */ + + _setFontStyles: function() { + var queue = this._queue; + if (this._font !== this.font) { + try { + var span = spans[this._canvasId]; + span.style.font = this._font = this.font; + + var style = span.currentStyle; + var fontSize = span.offsetHeight; + var font = [style.fontStyle, style.fontWeight, fontSize, style.fontFamily].join(" "); + queue.push(properties.font, font); + } catch(e) { + // If this.font cannot be parsed as a CSS font value, then it + // must be ignored. + } + } + if (this._textAlign !== this.textAlign) { + this._textAlign = this.textAlign; + queue.push(properties.textAlign, this._textAlign); + } + if (this._textBaseline !== this.textBaseline) { + this._textBaseline = this.textBaseline; + queue.push(properties.textBaseline, this._textBaseline); + } + if (this._direction !== this.canvas.currentStyle.direction) { + this._direction = this.canvas.currentStyle.direction; + queue.push(properties.direction, this._direction); + } + }, + + fillText: function(text, x, y, maxWidth) { + this._setCompositing(); + this._setFillStyle(); + this._setShadows(); + this._setFontStyles(); + this._queue.push(properties.fillText, encodeXML(text), x, y, + maxWidth === undefined ? Infinity : maxWidth); + }, + + strokeText: function(text, x, y, maxWidth) { + this._setCompositing(); + this._setStrokeStyle(); + this._setShadows(); + this._setFontStyles(); + this._queue.push(properties.strokeText, encodeXML(text), x, y, + maxWidth === undefined ? Infinity : maxWidth); + }, + + measureText: function(text) { + var span = spans[this._canvasId]; + try { + span.style.font = this.font; + } catch(e) { + // If this.font cannot be parsed as a CSS font value, then it must + // be ignored. + } + + // Replace space characters with tab characters because innerText + // removes trailing white spaces. + span.innerText = text.replace(/[ \n\f\r]/g, "\t"); + + return new TextMetrics(span.offsetWidth); + }, + + /* + * drawing images + */ + + drawImage: function(image, x1, y1, w1, h1, x2, y2, w2, h2) { + // If the image is null, the implementation must raise a + // TYPE_MISMATCH_ERR exception. + if (!image) { + throwException(TYPE_MISMATCH_ERR); + } + + var tagName = image.tagName, src, argc = arguments.length; + var canvasId = this._canvasId; + + // If the first argument isn't an img, canvas, or video element, + // throws a TYPE_MISMATCH_ERR exception. + if (tagName) { + tagName = tagName.toLowerCase(); + if (tagName === "img") { + src = image.getAttribute("src", 2); + } else if (tagName === CANVAS || tagName === "video") { + // For now, only HTMLImageElement is supported. + return; + } else { + throwException(TYPE_MISMATCH_ERR); + } + } + + // Additionally, we accept any object that has a src property. + // This is useful when you'd like to specify a long data URI. + else if (image.src) { + src = image.src; + } else { + throwException(TYPE_MISMATCH_ERR); + } + + this._setCompositing(); + this._setShadows(); + + // Special characters in the filename need escaping. + src = encodeXML(src); + + if (argc === 3) { + this._queue.push(properties.drawImage, argc, src, x1, y1); + } else if (argc === 5) { + this._queue.push(properties.drawImage, argc, src, x1, y1, w1, h1); + } else if (argc === 9) { + // If one of the sw or sh arguments is zero, the implementation + // must raise an INDEX_SIZE_ERR exception. + if (w1 === 0 || h1 === 0) { + throwException(INDEX_SIZE_ERR); + } + + this._queue.push(properties.drawImage, argc, src, x1, y1, w1, h1, x2, y2, w2, h2); + } else { + return; + } + + if (isReady[canvasId]) { + this._executeCommand(); + ++lock[canvasId]; + } + }, + + /* + * pixel manipulation + */ + + // ImageData createImageData(in float sw, in float sh); + // ImageData createImageData(in ImageData imagedata); + createImageData: function() { + // TODO: Implement + }, + + // ImageData getImageData(in float sx, in float sy, in float sw, in float sh); + getImageData: function(sx, sy, sw, sh) { + // TODO: Implement + }, + + // void putImageData(in ImageData imagedata, in float dx, in float dy, [Optional] in float dirtyX, in float dirtyY, in float dirtyWidth, in float dirtyHeight); + putImageData: function(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) { + // TODO: Implement + }, + + /* + * private methods + */ + + _initialize: function() { + // compositing + this.globalAlpha = this._globalAlpha = 1.0; + this.globalCompositeOperation = this._globalCompositeOperation = "source-over"; + + // colors and styles + this.strokeStyle = this._strokeStyle = "#000000"; + this.fillStyle = this._fillStyle = "#000000"; + + // line caps/joins + this.lineWidth = this._lineWidth = 1.0; + this.lineCap = this._lineCap = "butt"; + this.lineJoin = this._lineJoin = "miter"; + this.miterLimit = this._miterLimit = 10.0; + + // shadows + this.shadowOffsetX = this._shadowOffsetX = 0; + this.shadowOffsetY = this._shadowOffsetY = 0; + this.shadowBlur = this._shadowBlur = 0; + this.shadowColor = this._shadowColor = "rgba(0, 0, 0, 0.0)"; + + // text + this.font = this._font = "10px sans-serif"; + this.textAlign = this._textAlign = "start"; + this.textBaseline = this._textBaseline = "alphabetic"; + + // command queue + this._queue = []; + + // stack of drawing states + this._stateStack = []; + }, + + _flush: function() { + var queue = this._queue; + this._queue = []; + return queue; + }, + + _executeCommand: function() { + // execute commands + var commands = this._flush(); + if (commands.length > 0) { + return eval(this._swf.CallFunction( + '' + + commands.join("�") + "" + )); + } + }, + + _resize: function(width, height) { + // Flush commands in the queue + this._executeCommand(); + + // Clear back to the initial state + this._initialize(); + + // Adjust the size of Flash to that of the canvas + if (width > 0) { + this._swf.width = width; + } + if (height > 0) { + this._swf.height = height; + } + + // Execute a resize command at the start of the next frame + this._queue.push(properties.resize, width, height); + } +}; + +/** + * CanvasGradient stub + * @constructor + */ +var CanvasGradient = function(ctx) { + this._ctx = ctx; + this.id = ctx._gradientPatternId++; +}; + +CanvasGradient.prototype = { + addColorStop: function(offset, color) { + // Throws an INDEX_SIZE_ERR exception if the offset is out of range. + if (isNaN(offset) || offset < 0 || offset > 1) { + throwException(INDEX_SIZE_ERR); + } + + this._ctx._queue.push(properties.addColorStop, this.id, offset, color); + } +}; + +/** + * CanvasPattern stub + * @constructor + */ +var CanvasPattern = function(ctx) { + this.id = ctx._gradientPatternId++; +}; + +/** + * TextMetrics stub + * @constructor + */ +var TextMetrics = function(width) { + this.width = width; +}; + +/** + * DOMException + * @constructor + */ +var DOMException = function(code) { + this.code = code; + this.message = DOMExceptionNames[code]; +}; + +DOMException.prototype = new Error; + +var DOMExceptionNames = { + 1: "INDEX_SIZE_ERR", + 9: "NOT_SUPPORTED_ERR", + 11: "INVALID_STATE_ERR", + 12: "SYNTAX_ERR", + 17: "TYPE_MISMATCH_ERR", + 18: "SECURITY_ERR" +}; + +/* + * Event handlers + */ + +function onReadyStateChange() { + if (document.readyState === "complete") { + document.detachEvent(ON_READY_STATE_CHANGE, onReadyStateChange); + + var canvases = document.getElementsByTagName(CANVAS); + for (var i = 0, n = canvases.length; i < n; ++i) { + FlashCanvas.initElement(canvases[i]); + } + } +} + +function onFocus() { + // forward the event to the parent + var swf = event.srcElement, canvas = swf.parentNode; + swf.blur(); + canvas.focus(); +} + +function onPropertyChange() { + var prop = event.propertyName; + if (prop === "width" || prop === "height") { + var canvas = event.srcElement; + var value = canvas[prop]; + var number = parseInt(value, 10); + + if (isNaN(number) || number < 0) { + number = (prop === "width") ? 300 : 150; + } + + if (value === number) { + canvas.style[prop] = number + "px"; + canvas.getContext("2d")._resize(canvas.width, canvas.height); + } else { + canvas[prop] = number; + } + } +} + +function onUnload() { + window.detachEvent(ON_UNLOAD, onUnload); + + for (var canvasId in canvases) { + var canvas = canvases[canvasId], swf = canvas.firstChild, prop; + + // clean up the references of swf.executeCommand and swf.resize + for (prop in swf) { + if (typeof swf[prop] === "function") { + swf[prop] = NULL; + } + } + + // clean up the references of canvas.getContext and canvas.toDataURL + for (prop in canvas) { + if (typeof canvas[prop] === "function") { + canvas[prop] = NULL; + } + } + + // remove event listeners + swf.detachEvent(ON_FOCUS, onFocus); + canvas.detachEvent(ON_PROPERTY_CHANGE, onPropertyChange); + } + + // delete exported symbols + window[CANVAS_RENDERING_CONTEXT_2D] = NULL; + window[CANVAS_GRADIENT] = NULL; + window[CANVAS_PATTERN] = NULL; + window[FLASH_CANVAS] = NULL; + window[G_VML_CANVAS_MANAGER] = NULL; +} + +/* + * FlashCanvas API + */ + +var FlashCanvas = { + initElement: function(canvas) { + // Check whether the initialization is required or not. + if (canvas.getContext) { + return canvas; + } + + // initialize lock + var canvasId = getUniqueId(); + var objectId = OBJECT_ID_PREFIX + canvasId; + isReady[canvasId] = false; + lock[canvasId] = 1; + + // Set the width and height attributes. + setCanvasSize(canvas); + + // embed swf and SPAN element + canvas.innerHTML = + '' + + '' + + '' + + '' + + '' + + '' + + ''; + + canvases[canvasId] = canvas; + var swf = canvas.firstChild; + spans[canvasId] = canvas.lastChild; + + // Check whether the canvas element is in the DOM tree + var documentContains = document.body.contains; + if (documentContains(canvas)) { + // Load swf file immediately + swf["movie"] = SWF_URL; + } else { + // Wait until the element is added to the DOM tree + var intervalId = setInterval(function() { + if (documentContains(canvas)) { + clearInterval(intervalId); + swf["movie"] = SWF_URL; + } + }, 0); + } + + // If the browser is IE6 or in quirks mode + if (document.compatMode === "BackCompat" || !window.XMLHttpRequest) { + spans[canvasId].style.overflow = "hidden"; + } + + // initialize context + var ctx = new CanvasRenderingContext2D(canvas, swf); + + // canvas API + canvas.getContext = function(contextId) { + return contextId === "2d" ? ctx : NULL; + }; + + canvas.toDataURL = function(type, quality) { + if (("" + type).replace(/[A-Z]+/g, toLowerCase) === "image/jpeg") { + ctx._queue.push(properties.toDataURL, type, + typeof quality === "number" ? quality : ""); + } else { + ctx._queue.push(properties.toDataURL, type); + } + return ctx._executeCommand(); + }; + + // add event listener + swf.attachEvent(ON_FOCUS, onFocus); + + return canvas; + }, + + saveImage: function(canvas) { + var swf = canvas.firstChild; + swf.saveImage(); + }, + + setOptions: function(options) { + // TODO: Implement + }, + + trigger: function(canvasId, type) { + var canvas = canvases[canvasId]; + canvas.fireEvent("on" + type); + }, + + unlock: function(canvasId, ready) { + if (lock[canvasId]) { + --lock[canvasId]; + } + if (ready) { + var canvas = canvases[canvasId]; + var swf = canvas.firstChild; + var width; + var height; + + // Set the width and height attributes of the canvas element. + setCanvasSize(canvas); + width = canvas.width; + height = canvas.height; + + canvas.style.width = width + "px"; + canvas.style.height = height + "px"; + + // Adjust the size of Flash to that of the canvas + if (width > 0) { + swf.width = width; + } + if (height > 0) { + swf.height = height; + } + swf.resize(width, height); + + // Add event listener + canvas.attachEvent(ON_PROPERTY_CHANGE, onPropertyChange); + + // ExternalInterface is now ready for use + isReady[canvasId] = true; + } + } +}; + +/* + * Utility methods + */ + +// Get the absolute URL of flashcanvas.js +function getScriptUrl() { + var scripts = document.getElementsByTagName("script"); + var script = scripts[scripts.length - 1]; + + // @see http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx + if (document.documentMode >= 8) { + return script.src; + } else { + return script.getAttribute("src", 4); + } +} + +// Get a unique ID composed of alphanumeric characters. +function getUniqueId() { + return Math.random().toString(36).slice(2) || "0"; +} + +// Escape characters not permitted in XML. +function encodeXML(str) { + return ("" + str).replace(/&/g, "&").replace(/ + *
        + *
      • 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; +}; + +/** + * The data table contains column information and data for the chart. + */ +var DataTable = function () { + var columns = []; + var data; + + 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, column; + 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); + } + } + } + }; +}; + +/** + * Column type enumeration + */ +var ColumnType = { + STRING : 'string', + NUMBER : 'number', + BOOLEAN : 'boolean', + DATE : 'date' +}; + +/******************************************************************************* + * JQPlot specific code + ******************************************************************************/ + +/** + * Chart factory that returns JQPlotCharts + */ +var JQPlotChartFactory = function () { +}; +JQPlotChartFactory.prototype = new ChartFactory(); +JQPlotChartFactory.prototype.createChart = function (type, elementId) { + var chart; + 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; + } + + return chart; +}; + +/** + * 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; +}; +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.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 + } + }, + 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 i = 0; i < data.length; i++) { + optional.axes.xaxis.ticks.push(data[i][0].toString()); + } + } + return optional; +}; + +JQPlotLineChart.prototype.prepareData = function (dataTable) { + var data = dataTable.getData(), row; + var retData = [], 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 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 = JQPlotAreaChart; + +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(), row, d; + var retData = [], 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 + } + }, + 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 i = 1; i < columns.length; i++) { + optional.series.push({ + label : columns[i].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: { + formatString:'%s, %d', + useAxesFormatters: false + } + }; + var compulsory = { + seriesDefaults : { + renderer : $.jqplot.PieRenderer + } + }; + $.extend(true, optional, options, compulsory); + return optional; +}; + +JQPlotPieChart.prototype.prepareData = function (dataTable) { + var data = dataTable.getData(), row; + var retData = []; + for (var i = 0; i < data.length; i++) { + row = data[i]; + retData.push([ row[0], row[1] ]); + } + return [ retData ]; +}; diff --git a/sources/js/codemirror/LICENSE b/sources/js/codemirror/LICENSE new file mode 100644 index 0000000..482d55e --- /dev/null +++ b/sources/js/codemirror/LICENSE @@ -0,0 +1,23 @@ +Copyright (C) 2013 by Marijn Haverbeke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Please note that some subdirectories of the CodeMirror distribution +include their own LICENSE files, and are released under different +licences. diff --git a/sources/js/codemirror/addon/runmode/runmode.js b/sources/js/codemirror/addon/runmode/runmode.js new file mode 100644 index 0000000..a7da6d7 --- /dev/null +++ b/sources/js/codemirror/addon/runmode/runmode.js @@ -0,0 +1,56 @@ +CodeMirror.runMode = function(string, modespec, callback, options) { + var mode = CodeMirror.getMode(CodeMirror.defaults, modespec); + var ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); + + if (callback.nodeType == 1) { + var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize; + var node = callback, col = 0; + node.innerHTML = ""; + callback = function(text, style) { + if (text == "\n") { + // Emitting LF or CRLF on IE8 or earlier results in an incorrect display. + // Emitting a carriage return makes everything ok. + node.appendChild(document.createTextNode(ie_lt9 ? '\r' : text)); + col = 0; + return; + } + var content = ""; + // replace tabs + for (var pos = 0;;) { + var idx = text.indexOf("\t", pos); + if (idx == -1) { + content += text.slice(pos); + col += text.length - pos; + break; + } else { + col += idx - pos; + content += text.slice(pos, idx); + var size = tabSize - col % tabSize; + col += size; + for (var i = 0; i < size; ++i) content += " "; + pos = idx + 1; + } + } + + if (style) { + var sp = node.appendChild(document.createElement("span")); + sp.className = "cm-" + style.replace(/ +/g, " cm-"); + sp.appendChild(document.createTextNode(content)); + } else { + node.appendChild(document.createTextNode(content)); + } + }; + } + + var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode); + for (var i = 0, e = lines.length; i < e; ++i) { + if (i) callback("\n"); + var stream = new CodeMirror.StringStream(lines[i]); + while (!stream.eol()) { + var style = mode.token(stream, state); + callback(stream.current(), style, i, stream.start); + stream.start = stream.pos; + } + } +}; diff --git a/sources/js/codemirror/lib/codemirror.js b/sources/js/codemirror/lib/codemirror.js new file mode 100644 index 0000000..1d0d996 --- /dev/null +++ b/sources/js/codemirror/lib/codemirror.js @@ -0,0 +1,5799 @@ +// CodeMirror version 3.15 +// +// CodeMirror is the only global var we claim +window.CodeMirror = (function() { + "use strict"; + + // BROWSER SNIFFING + + // Crude, but necessary to handle a number of hard-to-feature-detect + // bugs and behavior differences. + var gecko = /gecko\/\d/i.test(navigator.userAgent); + var ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8); + var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); + var webkit = /WebKit\//.test(navigator.userAgent); + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var opera = /Opera\//.test(navigator.userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var khtml = /KHTML\//.test(navigator.userAgent); + var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); + var phantom = /PhantomJS/.test(navigator.userAgent); + + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + // This is woefully incomplete. Suggestions for alternative methods welcome. + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var windows = /windows/i.test(navigator.platform); + + var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/); + if (opera_version) opera_version = Number(opera_version[1]); + if (opera_version && opera_version >= 15) { opera = false; webkit = true; } + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X + var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11)); + var captureMiddleClick = gecko || (ie && !ie_lt9); + + // Optimize some code when these features are not used + var sawReadOnlySpans = false, sawCollapsedSpans = false; + + // CONSTRUCTOR + + function CodeMirror(place, options) { + if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); + + this.options = options = options || {}; + // Determine effective options based on given values and defaults. + for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt)) + options[opt] = defaults[opt]; + setGuttersForLineNumbers(options); + + var docStart = typeof options.value == "string" ? 0 : options.value.first; + var display = this.display = makeDisplay(place, docStart); + display.wrapper.CodeMirror = this; + updateGutters(this); + if (options.autofocus && !mobile) focusInput(this); + + this.state = {keyMaps: [], + overlays: [], + modeGen: 0, + overwrite: false, focused: false, + suppressEdits: false, pasteIncoming: false, + draggingText: false, + highlight: new Delayed()}; + + themeChanged(this); + if (options.lineWrapping) + this.display.wrapper.className += " CodeMirror-wrap"; + + var doc = options.value; + if (typeof doc == "string") doc = new Doc(options.value, options.mode); + operation(this, attachDoc)(this, doc); + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie) setTimeout(bind(resetInput, this, true), 20); + + registerEventHandlers(this); + // IE throws unspecified error in certain cases, when + // trying to access activeElement before onload + var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { } + if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20); + else onBlur(this); + + operation(this, function() { + for (var opt in optionHandlers) + if (optionHandlers.propertyIsEnumerable(opt)) + optionHandlers[opt](this, options[opt], Init); + for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); + })(); + } + + // DISPLAY CONSTRUCTOR + + function makeDisplay(place, docStart) { + var d = {}; + + var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;"); + if (webkit) input.style.width = "1000px"; + else input.setAttribute("wrap", "off"); + // if border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) input.style.border = "1px solid black"; + input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false"); + + // Wraps and hides input textarea + d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The actual fake scrollbars. + d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar"); + d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar"); + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); + // DIVs containing the selection and the actual code + d.lineDiv = elt("div", null, "CodeMirror-code"); + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); + // Blinky cursor, and element used to ensure cursor fits at the end of a line + d.cursor = elt("div", "\u00a0", "CodeMirror-cursor"); + // Secondary cursor, shown when on a 'jump' in bi-directional text + d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"); + // Used to measure text size + d.measure = elt("div", null, "CodeMirror-measure"); + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor], + null, "position: relative; outline: none"); + // Moved around its parent to cover visible view + d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); + // Set to the height of the text, causes scrolling + d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); + // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;"); + // Will contain the gutters, if any + d.gutters = elt("div", null, "CodeMirror-gutters"); + d.lineGutter = null; + // Provides scrolling + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); + d.scroller.setAttribute("tabIndex", "-1"); + // The element in which the editor lives. + d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV, + d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); + // Work around IE7 z-index bug + if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } + if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper); + + // Needed to hide big blue blinking cursor on Mobile Safari + if (ios) input.style.width = "0px"; + if (!webkit) d.scroller.draggable = true; + // Needed to handle Tab key in KHTML + if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; } + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px"; + + // Current visible range (may be bigger than the view window). + d.viewOffset = d.lastSizeC = 0; + d.showingFrom = d.showingTo = docStart; + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; + // See readInput and resetInput + d.prevInput = ""; + // Set to true when a non-horizontal-scrolling widget is added. As + // an optimization, widget aligning is skipped when d is false. + d.alignWidgets = false; + // Flag that indicates whether we currently expect input to appear + // (after some event like 'keypress' or 'input') and are polling + // intensively. + d.pollingFast = false; + // Self-resetting timeout for the poller + d.poll = new Delayed(); + + d.cachedCharWidth = d.cachedTextHeight = null; + d.measureLineCache = []; + d.measureLineCachePos = 0; + + // Tracks when resetInput has punted to just putting a short + // string instead of the (large) selection. + d.inaccurateSelection = false; + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null; + d.maxLineLength = 0; + d.maxLineChanged = false; + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; + + return d; + } + + // STATE UPDATES + + // Used to get the editor into a consistent state again when options change. + + function loadMode(cm) { + cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); + cm.doc.iter(function(line) { + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + }); + cm.doc.frontier = cm.doc.first; + startWorker(cm, 100); + cm.state.modeGen++; + if (cm.curOp) regChange(cm); + } + + function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + cm.display.wrapper.className += " CodeMirror-wrap"; + cm.display.sizer.style.minWidth = ""; + } else { + cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", ""); + computeMaxLength(cm); + } + estimateLineHeights(cm); + regChange(cm); + clearCaches(cm); + setTimeout(function(){updateScrollbars(cm);}, 100); + } + + function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); + return function(line) { + if (lineIsHidden(cm.doc, line)) + return 0; + else if (wrapping) + return (Math.ceil(line.text.length / perLine) || 1) * th; + else + return th; + }; + } + + function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm); + doc.iter(function(line) { + var estHeight = est(line); + if (estHeight != line.height) updateLineHeight(line, estHeight); + }); + } + + function keyMapChanged(cm) { + var map = keyMap[cm.options.keyMap], style = map.style; + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + + (style ? " cm-keymap-" + style : ""); + cm.state.disableInput = map.disableInput; + } + + function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + clearCaches(cm); + } + + function guttersChanged(cm) { + updateGutters(cm); + regChange(cm); + setTimeout(function(){alignHorizontally(cm);}, 20); + } + + function updateGutters(cm) { + var gutters = cm.display.gutters, specs = cm.options.gutters; + removeChildren(gutters); + for (var i = 0; i < specs.length; ++i) { + var gutterClass = specs[i]; + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); + if (gutterClass == "CodeMirror-linenumbers") { + cm.display.lineGutter = gElt; + gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; + } + } + gutters.style.display = i ? "" : "none"; + } + + function lineLength(doc, line) { + if (line.height == 0) return 0; + var len = line.text.length, merged, cur = line; + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(); + cur = getLine(doc, found.from.line); + len += found.from.ch - found.to.ch; + } + cur = line; + while (merged = collapsedSpanAtEnd(cur)) { + var found = merged.find(); + len -= cur.text.length - found.from.ch; + cur = getLine(doc, found.to.line); + len += cur.text.length - found.to.ch; + } + return len; + } + + function computeMaxLength(cm) { + var d = cm.display, doc = cm.doc; + d.maxLine = getLine(doc, doc.first); + d.maxLineLength = lineLength(doc, d.maxLine); + d.maxLineChanged = true; + doc.iter(function(line) { + var len = lineLength(doc, line); + if (len > d.maxLineLength) { + d.maxLineLength = len; + d.maxLine = line; + } + }); + } + + // Make sure the gutters options contains the element + // "CodeMirror-linenumbers" when the lineNumbers option is true. + function setGuttersForLineNumbers(options) { + var found = false; + for (var i = 0; i < options.gutters.length; ++i) { + if (options.gutters[i] == "CodeMirror-linenumbers") { + if (options.lineNumbers) found = true; + else options.gutters.splice(i--, 1); + } + } + if (!found && options.lineNumbers) + options.gutters.push("CodeMirror-linenumbers"); + } + + // SCROLLBARS + + // Re-synchronize the fake scrollbars with the actual size of the + // content. Optionally force a scrollTop. + function updateScrollbars(cm) { + var d = cm.display, docHeight = cm.doc.height; + var totalHeight = docHeight + paddingVert(d); + d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px"; + d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px"; + var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight); + var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1); + var needsV = scrollHeight > (d.scroller.clientHeight + 1); + if (needsV) { + d.scrollbarV.style.display = "block"; + d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0"; + d.scrollbarV.firstChild.style.height = + (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px"; + } else d.scrollbarV.style.display = ""; + if (needsH) { + d.scrollbarH.style.display = "block"; + d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0"; + d.scrollbarH.firstChild.style.width = + (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px"; + } else d.scrollbarH.style.display = ""; + if (needsH && needsV) { + d.scrollbarFiller.style.display = "block"; + d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px"; + } else d.scrollbarFiller.style.display = ""; + if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px"; + d.gutterFiller.style.width = d.gutters.offsetWidth + "px"; + } else d.gutterFiller.style.display = ""; + + if (mac_geLion && scrollbarWidth(d.measure) === 0) + d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; + } + + function visibleLines(display, doc, viewPort) { + var top = display.scroller.scrollTop, height = display.wrapper.clientHeight; + if (typeof viewPort == "number") top = viewPort; + else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;} + top = Math.floor(top - paddingTop(display)); + var bottom = Math.ceil(top + height); + return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)}; + } + + // LINE NUMBERS + + function alignHorizontally(cm) { + var display = cm.display; + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; + var gutterW = display.gutters.offsetWidth, l = comp + "px"; + for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) { + for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l; + } + if (cm.options.fixedGutter) + display.gutters.style.left = (comp + gutterW) + "px"; + } + + function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) return false; + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")); + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; + display.lineGutter.style.width = ""; + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding); + display.lineNumWidth = display.lineNumInnerWidth + padding; + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; + display.lineGutter.style.width = display.lineNumWidth + "px"; + return true; + } + return false; + } + + function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)); + } + function compensateForHScroll(display) { + return getRect(display.scroller).left - getRect(display.sizer).left; + } + + // DISPLAY DRAWING + + function updateDisplay(cm, changes, viewPort, forced) { + var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated; + var visible = visibleLines(cm.display, cm.doc, viewPort); + for (;;) { + if (!updateDisplayInner(cm, changes, visible, forced)) break; + forced = false; + updated = true; + updateSelection(cm); + updateScrollbars(cm); + + // Clip forced viewport to actual scrollable area + if (viewPort) + viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, + typeof viewPort == "number" ? viewPort : viewPort.top); + visible = visibleLines(cm.display, cm.doc, viewPort); + if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo) + break; + changes = []; + } + + if (updated) { + signalLater(cm, "update", cm); + if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo) + signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo); + } + return updated; + } + + // Uses a set of changes plus the current scroll position to + // determine which DOM updates have to be made, and makes the + // updates. + function updateDisplayInner(cm, changes, visible, forced) { + var display = cm.display, doc = cm.doc; + if (!display.wrapper.clientWidth) { + display.showingFrom = display.showingTo = doc.first; + display.viewOffset = 0; + return; + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!forced && changes.length == 0 && + visible.from > display.showingFrom && visible.to < display.showingTo) + return; + + if (maybeUpdateLineNumberWidth(cm)) + changes = [{from: doc.first, to: doc.first + doc.size}]; + var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px"; + display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0"; + + // Used to determine which lines need their line numbers updated + var positionsChangedFrom = Infinity; + if (cm.options.lineNumbers) + for (var i = 0; i < changes.length; ++i) + if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; } + + var end = doc.first + doc.size; + var from = Math.max(visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, visible.to + cm.options.viewportMargin); + if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom); + if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo); + if (sawCollapsedSpans) { + from = lineNo(visualLine(doc, getLine(doc, from))); + while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to; + } + + // Create a range of theoretically intact lines, and punch holes + // in that using the change info. + var intact = [{from: Math.max(display.showingFrom, doc.first), + to: Math.min(display.showingTo, end)}]; + if (intact[0].from >= intact[0].to) intact = []; + else intact = computeIntact(intact, changes); + // When merged lines are present, we might have to reduce the + // intact ranges because changes in continued fragments of the + // intact lines do require the lines to be redrawn. + if (sawCollapsedSpans) + for (var i = 0; i < intact.length; ++i) { + var range = intact[i], merged; + while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) { + var newTo = merged.find().from.line; + if (newTo > range.from) range.to = newTo; + else { intact.splice(i--, 1); break; } + } + } + + // Clip off the parts that won't be visible + var intactLines = 0; + for (var i = 0; i < intact.length; ++i) { + var range = intact[i]; + if (range.from < from) range.from = from; + if (range.to > to) range.to = to; + if (range.from >= range.to) intact.splice(i--, 1); + else intactLines += range.to - range.from; + } + if (!forced && intactLines == to - from && from == display.showingFrom && to == display.showingTo) { + updateViewOffset(cm); + return; + } + intact.sort(function(a, b) {return a.from - b.from;}); + + // Avoid crashing on IE's "unspecified error" when in iframes + try { + var focused = document.activeElement; + } catch(e) {} + if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none"; + patchDisplay(cm, from, to, intact, positionsChangedFrom); + display.lineDiv.style.display = ""; + if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus(); + + var different = from != display.showingFrom || to != display.showingTo || + display.lastSizeC != display.wrapper.clientHeight; + // This is just a bogus formula that detects when the editor is + // resized or the font size changes. + if (different) { + display.lastSizeC = display.wrapper.clientHeight; + startWorker(cm, 400); + } + display.showingFrom = from; display.showingTo = to; + + updateHeightsInViewport(cm); + updateViewOffset(cm); + + return true; + } + + function updateHeightsInViewport(cm) { + var display = cm.display; + var prevBottom = display.lineDiv.offsetTop; + for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) { + if (ie_lt8) { + var bot = node.offsetTop + node.offsetHeight; + height = bot - prevBottom; + prevBottom = bot; + } else { + var box = getRect(node); + height = box.bottom - box.top; + } + var diff = node.lineObj.height - height; + if (height < 2) height = textHeight(display); + if (diff > .001 || diff < -.001) { + updateLineHeight(node.lineObj, height); + var widgets = node.lineObj.widgets; + if (widgets) for (var i = 0; i < widgets.length; ++i) + widgets[i].height = widgets[i].node.offsetHeight; + } + } + } + + function updateViewOffset(cm) { + var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom)); + // Position the mover div to align with the current virtual scroll position + cm.display.mover.style.top = off + "px"; + } + + function computeIntact(intact, changes) { + for (var i = 0, l = changes.length || 0; i < l; ++i) { + var change = changes[i], intact2 = [], diff = change.diff || 0; + for (var j = 0, l2 = intact.length; j < l2; ++j) { + var range = intact[j]; + if (change.to <= range.from && change.diff) { + intact2.push({from: range.from + diff, to: range.to + diff}); + } else if (change.to <= range.from || change.from >= range.to) { + intact2.push(range); + } else { + if (change.from > range.from) + intact2.push({from: range.from, to: change.from}); + if (change.to < range.to) + intact2.push({from: change.to + diff, to: range.to + diff}); + } + } + intact = intact2; + } + return intact; + } + + function getDimensions(cm) { + var d = cm.display, left = {}, width = {}; + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft; + width[cm.options.gutters[i]] = n.offsetWidth; + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth}; + } + + function patchDisplay(cm, from, to, intact, updateNumbersFrom) { + var dims = getDimensions(cm); + var display = cm.display, lineNumbers = cm.options.lineNumbers; + if (!intact.length && (!webkit || !cm.display.currentWheelTarget)) + removeChildren(display.lineDiv); + var container = display.lineDiv, cur = container.firstChild; + + function rm(node) { + var next = node.nextSibling; + if (webkit && mac && cm.display.currentWheelTarget == node) { + node.style.display = "none"; + node.lineObj = null; + } else { + node.parentNode.removeChild(node); + } + return next; + } + + var nextIntact = intact.shift(), lineN = from; + cm.doc.iter(from, to, function(line) { + if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift(); + if (lineIsHidden(cm.doc, line)) { + if (line.height != 0) updateLineHeight(line, 0); + if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) { + var w = line.widgets[i]; + if (w.showIfHidden) { + var prev = cur.previousSibling; + if (/pre/i.test(prev.nodeName)) { + var wrap = elt("div", null, null, "position: relative"); + prev.parentNode.replaceChild(wrap, prev); + wrap.appendChild(prev); + prev = wrap; + } + var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget")); + if (!w.handleMouseEvents) wnode.ignoreEvents = true; + positionLineWidget(w, wnode, prev, dims); + } + } + } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) { + // This line is intact. Skip to the actual node. Update its + // line number if needed. + while (cur.lineObj != line) cur = rm(cur); + if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber) + setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN)); + cur = cur.nextSibling; + } else { + // For lines with widgets, make an attempt to find and reuse + // the existing element, so that widgets aren't needlessly + // removed and re-inserted into the dom + if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling) + if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; } + // This line needs to be generated. + var lineNode = buildLineElement(cm, line, lineN, dims, reuse); + if (lineNode != reuse) { + container.insertBefore(lineNode, cur); + } else { + while (cur != reuse) cur = rm(cur); + cur = cur.nextSibling; + } + + lineNode.lineObj = line; + } + ++lineN; + }); + while (cur) cur = rm(cur); + } + + function buildLineElement(cm, line, lineNo, dims, reuse) { + var lineElement = lineContent(cm, line); + var markers = line.gutterMarkers, display = cm.display, wrap; + + if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets) + return lineElement; + + // Lines with gutter elements, widgets or a background class need + // to be wrapped again, and have the extra elements added to the + // wrapper div + + if (reuse) { + reuse.alignable = null; + var isOk = true, widgetsSeen = 0, insertBefore = null; + for (var n = reuse.firstChild, next; n; n = next) { + next = n.nextSibling; + if (!/\bCodeMirror-linewidget\b/.test(n.className)) { + reuse.removeChild(n); + } else { + for (var i = 0; i < line.widgets.length; ++i) { + var widget = line.widgets[i]; + if (widget.node == n.firstChild) { + if (!widget.above && !insertBefore) insertBefore = n; + positionLineWidget(widget, n, reuse, dims); + ++widgetsSeen; + break; + } + } + if (i == line.widgets.length) { isOk = false; break; } + } + } + reuse.insertBefore(lineElement, insertBefore); + if (isOk && widgetsSeen == line.widgets.length) { + wrap = reuse; + reuse.className = line.wrapClass || ""; + } + } + if (!wrap) { + wrap = elt("div", null, line.wrapClass, "position: relative"); + wrap.appendChild(lineElement); + } + // Kludge to make sure the styled element lies behind the selection (by z-index) + if (line.bgClass) + wrap.insertBefore(elt("div", null, line.bgClass + " CodeMirror-linebackground"), wrap.firstChild); + if (cm.options.lineNumbers || markers) { + var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " + + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), + wrap.firstChild); + if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap); + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + wrap.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineNo), + "CodeMirror-linenumber CodeMirror-gutter-elt", + "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " + + display.lineNumInnerWidth + "px")); + if (markers) + for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; + if (found) + gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); + } + } + if (ie_lt8) wrap.style.zIndex = 2; + if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) node.ignoreEvents = true; + positionLineWidget(widget, node, wrap, dims); + if (widget.above) + wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement); + else + wrap.appendChild(node); + signalLater(widget, "redraw"); + } + return wrap; + } + + function positionLineWidget(widget, node, wrap, dims) { + if (widget.noHScroll) { + (wrap.alignable || (wrap.alignable = [])).push(node); + var width = dims.wrapperWidth; + node.style.left = dims.fixedPos + "px"; + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth; + node.style.paddingLeft = dims.gutterTotalWidth + "px"; + } + node.style.width = width + "px"; + } + if (widget.coverGutter) { + node.style.zIndex = 5; + node.style.position = "relative"; + if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; + } + } + + // SELECTION / CURSOR + + function updateSelection(cm) { + var display = cm.display; + var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to); + if (collapsed || cm.options.showCursorWhenSelecting) + updateSelectionCursor(cm); + else + display.cursor.style.display = display.otherCursor.style.display = "none"; + if (!collapsed) + updateSelectionRange(cm); + else + display.selectionDiv.style.display = "none"; + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, cm.doc.sel.head, "div"); + var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv); + display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)) + "px"; + display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)) + "px"; + } + } + + // No selection, plain cursor + function updateSelectionCursor(cm) { + var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div"); + display.cursor.style.left = pos.left + "px"; + display.cursor.style.top = pos.top + "px"; + display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; + display.cursor.style.display = ""; + + if (pos.other) { + display.otherCursor.style.display = ""; + display.otherCursor.style.left = pos.other.left + "px"; + display.otherCursor.style.top = pos.other.top + "px"; + display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; + } else { display.otherCursor.style.display = "none"; } + } + + // Highlight selection + function updateSelectionRange(cm) { + var display = cm.display, doc = cm.doc, sel = cm.doc.sel; + var fragment = document.createDocumentFragment(); + var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display); + + function add(left, top, width, bottom) { + if (top < 0) top = 0; + fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + + "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) + + "px; height: " + (bottom - top) + "px")); + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias); + } + + iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { + var leftPos = coords(from, "left"), rightPos, left, right; + if (from == to) { + rightPos = leftPos; + left = right = leftPos.left; + } else { + rightPos = coords(to - 1, "right"); + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } + left = leftPos.left; + right = rightPos.right; + } + if (fromArg == null && from == 0) left = pl; + if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part + add(left, leftPos.top, null, leftPos.bottom); + left = pl; + if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); + } + if (toArg == null && to == lineLen) right = clientWidth; + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) + start = leftPos; + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) + end = rightPos; + if (left < pl + 1) left = pl; + add(left, rightPos.top, right - left, rightPos.bottom); + }); + return {start: start, end: end}; + } + + if (sel.from.line == sel.to.line) { + drawForLine(sel.from.line, sel.from.ch, sel.to.ch); + } else { + var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line); + var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine); + var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end; + var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(pl, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); + } + } + if (leftEnd.bottom < rightStart.top) + add(pl, leftEnd.bottom, null, rightStart.top); + } + + removeChildrenAndAdd(display.selectionDiv, fragment); + display.selectionDiv.style.display = ""; + } + + // Cursor-blinking + function restartBlink(cm) { + if (!cm.state.focused) return; + var display = cm.display; + clearInterval(display.blinker); + var on = true; + display.cursor.style.visibility = display.otherCursor.style.visibility = ""; + display.blinker = setInterval(function() { + display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; + }, cm.options.cursorBlinkRate); + } + + // HIGHLIGHT WORKER + + function startWorker(cm, time) { + if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo) + cm.state.highlight.set(time, bind(highlightWorker, cm)); + } + + function highlightWorker(cm) { + var doc = cm.doc; + if (doc.frontier < doc.first) doc.frontier = doc.first; + if (doc.frontier >= cm.display.showingTo) return; + var end = +new Date + cm.options.workTime; + var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); + var changed = [], prevChange; + doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) { + if (doc.frontier >= cm.display.showingFrom) { // Visible + var oldStyles = line.styles; + line.styles = highlightLine(cm, line, state); + var ischange = !oldStyles || oldStyles.length != line.styles.length; + for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; + if (ischange) { + if (prevChange && prevChange.end == doc.frontier) prevChange.end++; + else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1}); + } + line.stateAfter = copyState(doc.mode, state); + } else { + processLine(cm, line, state); + line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; + } + ++doc.frontier; + if (+new Date > end) { + startWorker(cm, cm.options.workDelay); + return true; + } + }); + if (changed.length) + operation(cm, function() { + for (var i = 0; i < changed.length; ++i) + regChange(this, changed[i].start, changed[i].end); + })(); + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc; + for (var search = n, lim = n - 100; search > lim; --search) { + if (search <= doc.first) return doc.first; + var line = getLine(doc, search - 1); + if (line.stateAfter && (!precise || search <= doc.frontier)) return search; + var indented = countColumn(line.text, null, cm.options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + + function getStateBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display; + if (!doc.mode.startState) return true; + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; + if (!state) state = startState(doc.mode); + else state = copyState(doc.mode, state); + doc.iter(pos, n, function(line) { + processLine(cm, line, state); + var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo; + line.stateAfter = save ? copyState(doc.mode, state) : null; + ++pos; + }); + return state; + } + + // POSITION MEASUREMENT + + function paddingTop(display) {return display.lineSpace.offsetTop;} + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} + function paddingLeft(display) { + var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x")); + return e.offsetLeft; + } + + function measureChar(cm, line, ch, data, bias) { + var dir = -1; + data = data || measureLine(cm, line); + + for (var pos = ch;; pos += dir) { + var r = data[pos]; + if (r) break; + if (dir < 0 && pos == 0) dir = 1; + } + bias = pos > ch ? "left" : pos < ch ? "right" : bias; + if (bias == "left" && r.leftSide) r = r.leftSide; + else if (bias == "right" && r.rightSide) r = r.rightSide; + return {left: pos < ch ? r.right : r.left, + right: pos > ch ? r.left : r.right, + top: r.top, + bottom: r.bottom}; + } + + function findCachedMeasurement(cm, line) { + var cache = cm.display.measureLineCache; + for (var i = 0; i < cache.length; ++i) { + var memo = cache[i]; + if (memo.text == line.text && memo.markedSpans == line.markedSpans && + cm.display.scroller.clientWidth == memo.width && + memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass) + return memo; + } + } + + function clearCachedMeasurement(cm, line) { + var exists = findCachedMeasurement(cm, line); + if (exists) exists.text = exists.measure = exists.markedSpans = null; + } + + function measureLine(cm, line) { + // First look in the cache + var cached = findCachedMeasurement(cm, line); + if (cached) return cached.measure; + + // Failing that, recompute and store result in cache + var measure = measureLineInner(cm, line); + var cache = cm.display.measureLineCache; + var memo = {text: line.text, width: cm.display.scroller.clientWidth, + markedSpans: line.markedSpans, measure: measure, + classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass}; + if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo; + else cache.push(memo); + return measure; + } + + function measureLineInner(cm, line) { + var display = cm.display, measure = emptyArray(line.text.length); + var pre = lineContent(cm, line, measure, true); + + // IE does not cache element positions of inline elements between + // calls to getBoundingClientRect. This makes the loop below, + // which gathers the positions of all the characters on the line, + // do an amount of layout work quadratic to the number of + // characters. When line wrapping is off, we try to improve things + // by first subdividing the line into a bunch of inline blocks, so + // that IE can reuse most of the layout information from caches + // for those blocks. This does interfere with line wrapping, so it + // doesn't work when wrapping is on, but in that case the + // situation is slightly better, since IE does cache line-wrapping + // information and only recomputes per-line. + if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) { + var fragment = document.createDocumentFragment(); + var chunk = 10, n = pre.childNodes.length; + for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) { + var wrap = elt("div", null, null, "display: inline-block"); + for (var j = 0; j < chunk && n; ++j) { + wrap.appendChild(pre.firstChild); + --n; + } + fragment.appendChild(wrap); + } + pre.appendChild(fragment); + } + + removeChildrenAndAdd(display.measure, pre); + + var outer = getRect(display.lineDiv); + var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight; + // Work around an IE7/8 bug where it will sometimes have randomly + // replaced our pre with a clone at this point. + if (ie_lt9 && display.measure.first != pre) + removeChildrenAndAdd(display.measure, pre); + + function measureRect(rect) { + var top = rect.top - outer.top, bot = rect.bottom - outer.top; + if (bot > maxBot) bot = maxBot; + if (top < 0) top = 0; + for (var i = vranges.length - 2; i >= 0; i -= 2) { + var rtop = vranges[i], rbot = vranges[i+1]; + if (rtop > bot || rbot < top) continue; + if (rtop <= top && rbot >= bot || + top <= rtop && bot >= rbot || + Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) { + vranges[i] = Math.min(top, rtop); + vranges[i+1] = Math.max(bot, rbot); + break; + } + } + if (i < 0) { i = vranges.length; vranges.push(top, bot); } + return {left: rect.left - outer.left, + right: rect.right - outer.left, + top: i, bottom: null}; + } + function finishRect(rect) { + rect.bottom = vranges[rect.top+1]; + rect.top = vranges[rect.top]; + } + + for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { + var node = cur, rect = null; + // A widget might wrap, needs special care + if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) { + if (cur.firstChild.nodeType == 1) node = cur.firstChild; + var rects = node.getClientRects(); + if (rects.length > 1) { + rect = data[i] = measureRect(rects[0]); + rect.rightSide = measureRect(rects[rects.length - 1]); + } + } + if (!rect) rect = data[i] = measureRect(getRect(node)); + if (cur.measureRight) rect.right = getRect(cur.measureRight).left; + if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide)); + } + for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { + finishRect(cur); + if (cur.leftSide) finishRect(cur.leftSide); + if (cur.rightSide) finishRect(cur.rightSide); + } + return data; + } + + function measureLineWidth(cm, line) { + var hasBadSpan = false; + if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) { + var sp = line.markedSpans[i]; + if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true; + } + var cached = !hasBadSpan && findCachedMeasurement(cm, line); + if (cached) return measureChar(cm, line, line.text.length, cached.measure, "right").right; + + var pre = lineContent(cm, line, null, true); + var end = pre.appendChild(zeroWidthElement(cm.display.measure)); + removeChildrenAndAdd(cm.display.measure, pre); + return getRect(end).right - getRect(cm.display.lineDiv).left; + } + + function clearCaches(cm) { + cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0; + cm.display.cachedCharWidth = cm.display.cachedTextHeight = null; + if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; + cm.display.lineNumChars = null; + } + + function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } + function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } + + // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page" + function intoCoordSystem(cm, lineObj, rect, context) { + if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { + var size = widgetHeight(lineObj.widgets[i]); + rect.top += size; rect.bottom += size; + } + if (context == "line") return rect; + if (!context) context = "local"; + var yOff = heightAtLine(cm, lineObj); + if (context == "local") yOff += paddingTop(cm.display); + else yOff -= cm.display.viewOffset; + if (context == "page" || context == "window") { + var lOff = getRect(cm.display.lineSpace); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); + rect.left += xOff; rect.right += xOff; + } + rect.top += yOff; rect.bottom += yOff; + return rect; + } + + // Context may be "window", "page", "div", or "local"/null + // Result is in "div" coords + function fromCoordSystem(cm, coords, context) { + if (context == "div") return coords; + var left = coords.left, top = coords.top; + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX(); + top -= pageScrollY(); + } else if (context == "local" || !context) { + var localBox = getRect(cm.display.sizer); + left += localBox.left; + top += localBox.top; + } + + var lineSpaceBox = getRect(cm.display.lineSpace); + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; + } + + function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) lineObj = getLine(cm.doc, pos.line); + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context); + } + + function cursorCoords(cm, pos, context, lineObj, measurement) { + lineObj = lineObj || getLine(cm.doc, pos.line); + if (!measurement) measurement = measureLine(cm, lineObj); + function get(ch, right) { + var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left"); + if (right) m.left = m.right; else m.right = m.left; + return intoCoordSystem(cm, lineObj, m, context); + } + function getBidi(ch, partPos) { + var part = order[partPos], right = part.level % 2; + if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { + part = order[--partPos]; + ch = bidiRight(part) - (part.level % 2 ? 0 : 1); + right = true; + } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { + part = order[++partPos]; + ch = bidiLeft(part) - part.level % 2; + right = false; + } + if (right && ch == part.to && ch > part.from) return get(ch - 1); + return get(ch, right); + } + var order = getOrder(lineObj), ch = pos.ch; + if (!order) return get(ch); + var partPos = getBidiPartAt(order, ch); + var val = getBidi(ch, partPos); + if (bidiOther != null) val.other = getBidi(ch, bidiOther); + return val; + } + + function PosWithInfo(line, ch, outside, xRel) { + var pos = new Pos(line, ch); + pos.xRel = xRel; + if (outside) pos.outside = true; + return pos; + } + + // Coords must be lineSpace-local + function coordsChar(cm, x, y) { + var doc = cm.doc; + y += cm.display.viewOffset; + if (y < 0) return PosWithInfo(doc.first, 0, true, -1); + var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1; + if (lineNo > last) + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); + if (x < 0) x = 0; + + for (;;) { + var lineObj = getLine(doc, lineNo); + var found = coordsCharInner(cm, lineObj, lineNo, x, y); + var merged = collapsedSpanAtEnd(lineObj); + var mergedPos = merged && merged.find(); + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) + lineNo = mergedPos.to.line; + else + return found; + } + } + + function coordsCharInner(cm, lineObj, lineNo, x, y) { + var innerOff = y - heightAtLine(cm, lineObj); + var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; + var measurement = measureLine(cm, lineObj); + + function getX(ch) { + var sp = cursorCoords(cm, Pos(lineNo, ch), "line", + lineObj, measurement); + wrongLine = true; + if (innerOff > sp.bottom) return sp.left - adjust; + else if (innerOff < sp.top) return sp.left + adjust; + else wrongLine = false; + return sp.left; + } + + var bidi = getOrder(lineObj), dist = lineObj.text.length; + var from = lineLeft(lineObj), to = lineRight(lineObj); + var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; + + if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); + // Do a binary search between these bounds. + for (;;) { + if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { + var ch = x < fromX || x - fromX <= toX - x ? from : to; + var xDiff = x - (ch == from ? fromX : toX); + while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch; + var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, + xDiff < 0 ? -1 : xDiff ? 1 : 0); + return pos; + } + var step = Math.ceil(dist / 2), middle = from + step; + if (bidi) { + middle = from; + for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); + } + var middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} + else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} + } + } + + var measureText; + function textHeight(display) { + if (display.cachedTextHeight != null) return display.cachedTextHeight; + if (measureText == null) { + measureText = elt("pre"); + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")); + measureText.appendChild(elt("br")); + } + measureText.appendChild(document.createTextNode("x")); + } + removeChildrenAndAdd(display.measure, measureText); + var height = measureText.offsetHeight / 50; + if (height > 3) display.cachedTextHeight = height; + removeChildren(display.measure); + return height || 1; + } + + function charWidth(display) { + if (display.cachedCharWidth != null) return display.cachedCharWidth; + var anchor = elt("span", "x"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(display.measure, pre); + var width = anchor.offsetWidth; + if (width > 2) display.cachedCharWidth = width; + return width || 10; + } + + // OPERATIONS + + // Operations are used to wrap changes in such a way that each + // change won't have to update the cursor and display (which would + // be awkward, slow, and error-prone), but instead updates are + // batched and then all combined and executed at once. + + var nextOpId = 0; + function startOperation(cm) { + cm.curOp = { + // An array of ranges of lines that have to be updated. See + // updateDisplay. + changes: [], + forceUpdate: false, + updateInput: null, + userSelChange: null, + textChanged: null, + selectionChanged: false, + cursorActivity: false, + updateMaxLine: false, + updateScrollPos: false, + id: ++nextOpId + }; + if (!delayedCallbackDepth++) delayedCallbacks = []; + } + + function endOperation(cm) { + var op = cm.curOp, doc = cm.doc, display = cm.display; + cm.curOp = null; + + if (op.updateMaxLine) computeMaxLength(cm); + if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) { + var width = measureLineWidth(cm, display.maxLine); + display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px"; + display.maxLineChanged = false; + var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth); + if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos) + setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true); + } + var newScrollPos, updated; + if (op.updateScrollPos) { + newScrollPos = op.updateScrollPos; + } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible + var coords = cursorCoords(cm, doc.sel.head); + newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom); + } + if (op.changes.length || op.forceUpdate || newScrollPos && newScrollPos.scrollTop != null) { + updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop, op.forceUpdate); + if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop; + } + if (!updated && op.selectionChanged) updateSelection(cm); + if (op.updateScrollPos) { + display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop; + display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft; + alignHorizontally(cm); + if (op.scrollToPos) + scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin); + } else if (newScrollPos) { + scrollCursorIntoView(cm); + } + if (op.selectionChanged) restartBlink(cm); + + if (cm.state.focused && op.updateInput) + resetInput(cm, op.userSelChange); + + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; + if (hidden) for (var i = 0; i < hidden.length; ++i) + if (!hidden[i].lines.length) signal(hidden[i], "hide"); + if (unhidden) for (var i = 0; i < unhidden.length; ++i) + if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); + + var delayed; + if (!--delayedCallbackDepth) { + delayed = delayedCallbacks; + delayedCallbacks = null; + } + if (op.textChanged) + signal(cm, "change", cm, op.textChanged); + if (op.cursorActivity) signal(cm, "cursorActivity", cm); + if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i](); + } + + // Wraps a function in an operation. Returns the wrapped function. + function operation(cm1, f) { + return function() { + var cm = cm1 || this, withOp = !cm.curOp; + if (withOp) startOperation(cm); + try { var result = f.apply(cm, arguments); } + finally { if (withOp) endOperation(cm); } + return result; + }; + } + function docOperation(f) { + return function() { + var withOp = this.cm && !this.cm.curOp, result; + if (withOp) startOperation(this.cm); + try { result = f.apply(this, arguments); } + finally { if (withOp) endOperation(this.cm); } + return result; + }; + } + function runInOp(cm, f) { + var withOp = !cm.curOp, result; + if (withOp) startOperation(cm); + try { result = f(); } + finally { if (withOp) endOperation(cm); } + return result; + } + + function regChange(cm, from, to, lendiff) { + if (from == null) from = cm.doc.first; + if (to == null) to = cm.doc.first + cm.doc.size; + cm.curOp.changes.push({from: from, to: to, diff: lendiff}); + } + + // INPUT HANDLING + + function slowPoll(cm) { + if (cm.display.pollingFast) return; + cm.display.poll.set(cm.options.pollInterval, function() { + readInput(cm); + if (cm.state.focused) slowPoll(cm); + }); + } + + function fastPoll(cm) { + var missed = false; + cm.display.pollingFast = true; + function p() { + var changed = readInput(cm); + if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);} + else {cm.display.pollingFast = false; slowPoll(cm);} + } + cm.display.poll.set(20, p); + } + + // prevInput is a hack to work with IME. If we reset the textarea + // on every change, that breaks IME. So we look for changes + // compared to the previous content instead. (Modern browsers have + // events that indicate IME taking place, but these are not widely + // supported or compatible enough yet to rely on.) + function readInput(cm) { + var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel; + if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false; + var text = input.value; + if (text == prevInput && posEq(sel.from, sel.to)) return false; + if (ie && !ie_lt9 && cm.display.inputHasSelection === text) { + resetInput(cm, true); + return false; + } + + var withOp = !cm.curOp; + if (withOp) startOperation(cm); + sel.shift = false; + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; + var from = sel.from, to = sel.to; + if (same < prevInput.length) + from = Pos(from.line, from.ch - (prevInput.length - same)); + else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming) + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same))); + + var updateInput = cm.curOp.updateInput; + var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)), + origin: cm.state.pasteIncoming ? "paste" : "+input"}; + makeChange(cm.doc, changeEvent, "end"); + cm.curOp.updateInput = updateInput; + signalLater(cm, "inputRead", cm, changeEvent); + + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = ""; + else cm.display.prevInput = text; + if (withOp) endOperation(cm); + cm.state.pasteIncoming = false; + return true; + } + + function resetInput(cm, user) { + var minimal, selected, doc = cm.doc; + if (!posEq(doc.sel.from, doc.sel.to)) { + cm.display.prevInput = ""; + minimal = hasCopyEvent && + (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000); + var content = minimal ? "-" : selected || cm.getSelection(); + cm.display.input.value = content; + if (cm.state.focused) selectInput(cm.display.input); + if (ie && !ie_lt9) cm.display.inputHasSelection = content; + } else if (user) { + cm.display.prevInput = cm.display.input.value = ""; + if (ie && !ie_lt9) cm.display.inputHasSelection = null; + } + cm.display.inaccurateSelection = minimal; + } + + function focusInput(cm) { + if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input)) + cm.display.input.focus(); + } + + function isReadOnly(cm) { + return cm.options.readOnly || cm.doc.cantEdit; + } + + // EVENT HANDLERS + + function registerEventHandlers(cm) { + var d = cm.display; + on(d.scroller, "mousedown", operation(cm, onMouseDown)); + if (ie) + on(d.scroller, "dblclick", operation(cm, function(e) { + if (signalDOMEvent(cm, e)) return; + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; + e_preventDefault(e); + var word = findWordAt(getLine(cm.doc, pos.line).text, pos); + extendSelection(cm.doc, word.from, word.to); + })); + else + on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); + on(d.lineSpace, "selectstart", function(e) { + if (!eventInWidget(d, e)) e_preventDefault(e); + }); + // Gecko browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for Gecko. + if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); + + on(d.scroller, "scroll", function() { + if (d.scroller.clientHeight) { + setScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft, true); + signal(cm, "scroll", cm); + } + }); + on(d.scrollbarV, "scroll", function() { + if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop); + }); + on(d.scrollbarH, "scroll", function() { + if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft); + }); + + on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); + on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); + + function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); } + on(d.scrollbarH, "mousedown", reFocus); + on(d.scrollbarV, "mousedown", reFocus); + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + + var resizeTimer; + function onResize() { + if (resizeTimer == null) resizeTimer = setTimeout(function() { + resizeTimer = null; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null; + clearCaches(cm); + runInOp(cm, bind(regChange, cm)); + }, 100); + } + on(window, "resize", onResize); + // Above handler holds on to the editor and its data structures. + // Here we poll to unregister it when the editor is no longer in + // the document, so that it can be garbage-collected. + function unregister() { + for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {} + if (p) setTimeout(unregister, 5000); + else off(window, "resize", onResize); + } + setTimeout(unregister, 5000); + + on(d.input, "keyup", operation(cm, function(e) { + if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; + if (e.keyCode == 16) cm.doc.sel.shift = false; + })); + on(d.input, "input", bind(fastPoll, cm)); + on(d.input, "keydown", operation(cm, onKeyDown)); + on(d.input, "keypress", operation(cm, onKeyPress)); + on(d.input, "focus", bind(onFocus, cm)); + on(d.input, "blur", bind(onBlur, cm)); + + function drag_(e) { + if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return; + e_stop(e); + } + if (cm.options.dragDrop) { + on(d.scroller, "dragstart", function(e){onDragStart(cm, e);}); + on(d.scroller, "dragenter", drag_); + on(d.scroller, "dragover", drag_); + on(d.scroller, "drop", operation(cm, onDrop)); + } + on(d.scroller, "paste", function(e){ + if (eventInWidget(d, e)) return; + focusInput(cm); + fastPoll(cm); + }); + on(d.input, "paste", function() { + cm.state.pasteIncoming = true; + fastPoll(cm); + }); + + function prepareCopy() { + if (d.inaccurateSelection) { + d.prevInput = ""; + d.inaccurateSelection = false; + d.input.value = cm.getSelection(); + selectInput(d.input); + } + } + on(d.input, "cut", prepareCopy); + on(d.input, "copy", prepareCopy); + + // Needed to handle Tab key in KHTML + if (khtml) on(d.sizer, "mouseup", function() { + if (document.activeElement == d.input) d.input.blur(); + focusInput(cm); + }); + } + + function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true; + } + } + + function posFromMouse(cm, e, liberal) { + var display = cm.display; + if (!liberal) { + var target = e_target(e); + if (target == display.scrollbarH || target == display.scrollbarH.firstChild || + target == display.scrollbarV || target == display.scrollbarV.firstChild || + target == display.scrollbarFiller || target == display.gutterFiller) return null; + } + var x, y, space = getRect(display.lineSpace); + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX; y = e.clientY; } catch (e) { return null; } + return coordsChar(cm, x - space.left, y - space.top); + } + + var lastClick, lastDoubleClick; + function onMouseDown(e) { + if (signalDOMEvent(this, e)) return; + var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel; + sel.shift = e.shiftKey; + + if (eventInWidget(display, e)) { + if (!webkit) { + display.scroller.draggable = false; + setTimeout(function(){display.scroller.draggable = true;}, 100); + } + return; + } + if (clickInGutter(cm, e)) return; + var start = posFromMouse(cm, e); + + switch (e_button(e)) { + case 3: + if (captureMiddleClick) onContextMenu.call(cm, cm, e); + return; + case 2: + if (start) extendSelection(cm.doc, start); + setTimeout(bind(focusInput, cm), 20); + e_preventDefault(e); + return; + } + // For button 1, if it was clicked inside the editor + // (posFromMouse returning non-null), we have to adjust the + // selection. + if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;} + + if (!cm.state.focused) onFocus(cm); + + var now = +new Date, type = "single"; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { + type = "triple"; + e_preventDefault(e); + setTimeout(bind(focusInput, cm), 20); + selectLine(cm, start.line); + } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { + type = "double"; + lastDoubleClick = {time: now, pos: start}; + e_preventDefault(e); + var word = findWordAt(getLine(doc, start.line).text, start); + extendSelection(cm.doc, word.from, word.to); + } else { lastClick = {time: now, pos: start}; } + + var last = start; + if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) && + !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") { + var dragEnd = operation(cm, function(e2) { + if (webkit) display.scroller.draggable = false; + cm.state.draggingText = false; + off(document, "mouseup", dragEnd); + off(display.scroller, "drop", dragEnd); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + extendSelection(cm.doc, start); + focusInput(cm); + } + }); + // Let the drag handler handle this. + if (webkit) display.scroller.draggable = true; + cm.state.draggingText = dragEnd; + // IE's approach to draggable + if (display.scroller.dragDrop) display.scroller.dragDrop(); + on(document, "mouseup", dragEnd); + on(display.scroller, "drop", dragEnd); + return; + } + e_preventDefault(e); + if (type == "single") extendSelection(cm.doc, clipPos(doc, start)); + + var startstart = sel.from, startend = sel.to, lastPos = start; + + function doSelect(cur) { + if (posEq(lastPos, cur)) return; + lastPos = cur; + + if (type == "single") { + extendSelection(cm.doc, clipPos(doc, start), cur); + return; + } + + startstart = clipPos(doc, startstart); + startend = clipPos(doc, startend); + if (type == "double") { + var word = findWordAt(getLine(doc, cur.line).text, cur); + if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend); + else extendSelection(cm.doc, startstart, word.to); + } else if (type == "triple") { + if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0))); + else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0))); + } + } + + var editorSize = getRect(display.wrapper); + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0; + + function extend(e) { + var curCount = ++counter; + var cur = posFromMouse(cm, e, true); + if (!cur) return; + if (!posEq(cur, last)) { + if (!cm.state.focused) onFocus(cm); + last = cur; + doSelect(cur); + var visible = visibleLines(display, doc); + if (cur.line >= visible.to || cur.line < visible.from) + setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; + if (outside) setTimeout(operation(cm, function() { + if (counter != curCount) return; + display.scroller.scrollTop += outside; + extend(e); + }), 50); + } + } + + function done(e) { + counter = Infinity; + e_preventDefault(e); + focusInput(cm); + off(document, "mousemove", move); + off(document, "mouseup", up); + } + + var move = operation(cm, function(e) { + if (!ie && !e_button(e)) done(e); + else extend(e); + }); + var up = operation(cm, done); + on(document, "mousemove", move); + on(document, "mouseup", up); + } + + function clickInGutter(cm, e) { + var display = cm.display; + try { var mX = e.clientX, mY = e.clientY; } + catch(e) { return false; } + + if (mX >= Math.floor(getRect(display.gutters).right)) return false; + e_preventDefault(e); + if (!hasHandler(cm, "gutterClick")) return true; + + var lineBox = getRect(display.lineDiv); + if (mY > lineBox.bottom) return true; + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && getRect(g).right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signalLater(cm, "gutterClick", cm, line, gutter, e); + break; + } + } + return true; + } + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + + function onDrop(e) { + var cm = this; + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e)))) + return; + e_preventDefault(e); + if (ie) lastDrop = +new Date; + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; + if (!pos || isReadOnly(cm)) return; + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0; + var loadFile = function(file, i) { + var reader = new FileReader; + reader.onload = function() { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(cm.doc, pos); + makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around"); + } + }; + reader.readAsText(file); + }; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } else { + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) { + cm.state.draggingText(e); + // Ensure the editor is re-focused + setTimeout(bind(focusInput, cm), 20); + return; + } + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to; + setSelection(cm.doc, pos, pos); + if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste"); + cm.replaceSelection(text, null, "paste"); + focusInput(cm); + onFocus(cm); + } + } + catch(e){} + } + } + + function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; + + var txt = cm.getSelection(); + e.dataTransfer.setData("Text", txt); + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + if (opera) { + img.width = img.height = 1; + cm.display.wrapper.appendChild(img); + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop; + } + e.dataTransfer.setDragImage(img, 0, 0); + if (opera) img.parentNode.removeChild(img); + } + } + + function setScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) return; + cm.doc.scrollTop = val; + if (!gecko) updateDisplay(cm, [], val); + if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; + if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val; + if (gecko) updateDisplay(cm, []); + startWorker(cm, 100); + } + function setScrollLeft(cm, val, isScroller) { + if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); + cm.doc.scrollLeft = val; + alignHorizontally(cm); + if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; + if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val; + } + + // Since the delta values reported on mouse wheel events are + // unstandardized between browsers and even browser versions, and + // generally horribly unpredictable, this code starts by measuring + // the scroll effect that the first few mouse wheel events have, + // and, from that, detects the way it can convert deltas to pixel + // offsets afterwards. + // + // The reason we want to know the amount a wheel event will scroll + // is that it gives us a chance to update the display before the + // actual scrolling happens, reducing flickering. + + var wheelSamples = 0, wheelPixelsPerUnit = null; + // Fill in a browser-detected starting value on browsers where we + // know one. These don't have to be accurate -- the result of them + // being wrong would just be a slight flicker on the first wheel + // scroll (if it is large enough). + if (ie) wheelPixelsPerUnit = -.53; + else if (gecko) wheelPixelsPerUnit = 15; + else if (chrome) wheelPixelsPerUnit = -.7; + else if (safari) wheelPixelsPerUnit = -1/3; + + function onScrollWheel(cm, e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY; + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; + else if (dy == null) dy = e.wheelDelta; + + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + if (!(dx && scroll.scrollWidth > scroll.clientWidth || + dy && scroll.scrollHeight > scroll.clientHeight)) return; + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + for (var cur = e.target; cur != scroll; cur = cur.parentNode) { + if (cur.lineObj) { + cm.display.currentWheelTarget = cur; + break; + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !opera && wheelPixelsPerUnit != null) { + if (dy) + setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); + setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); + e_preventDefault(e); + display.wheelStartX = null; // Abort measurement, if in progress + return; + } + + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit; + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; + if (pixels < 0) top = Math.max(0, top + pixels - 50); + else bot = Math.min(cm.doc.height, bot + pixels + 50); + updateDisplay(cm, [], {top: top, bottom: bot}); + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; + display.wheelDX = dx; display.wheelDY = dy; + setTimeout(function() { + if (display.wheelStartX == null) return; + var movedX = scroll.scrollLeft - display.wheelStartX; + var movedY = scroll.scrollTop - display.wheelStartY; + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX); + display.wheelStartX = display.wheelStartY = null; + if (!sample) return; + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); + ++wheelSamples; + }, 200); + } else { + display.wheelDX += dx; display.wheelDY += dy; + } + } + } + + function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) return false; + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false; + var doc = cm.doc, prevShift = doc.sel.shift, done = false; + try { + if (isReadOnly(cm)) cm.state.suppressEdits = true; + if (dropShift) doc.sel.shift = false; + done = bound(cm) != Pass; + } finally { + doc.sel.shift = prevShift; + cm.state.suppressEdits = false; + } + return done; + } + + function allKeyMaps(cm) { + var maps = cm.state.keyMaps.slice(0); + if (cm.options.extraKeys) maps.push(cm.options.extraKeys); + maps.push(cm.options.keyMap); + return maps; + } + + var maybeTransition; + function handleKeyBinding(cm, e) { + // Handle auto keymap transitions + var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto; + clearTimeout(maybeTransition); + if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { + if (getKeyMap(cm.options.keyMap) == startMap) { + cm.options.keyMap = (next.call ? next.call(null, cm) : next); + keyMapChanged(cm); + } + }, 50); + + var name = keyName(e, true), handled = false; + if (!name) return false; + var keymaps = allKeyMaps(cm); + + if (e.shiftKey) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);}) + || lookupKey(name, keymaps, function(b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + return doHandleBinding(cm, b); + }); + } else { + handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); }); + } + + if (handled) { + e_preventDefault(e); + restartBlink(cm); + if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } + signalLater(cm, "keyHandled", cm, name, e); + } + return handled; + } + + function handleCharBinding(cm, e, ch) { + var handled = lookupKey("'" + ch + "'", allKeyMaps(cm), + function(b) { return doHandleBinding(cm, b, true); }); + if (handled) { + e_preventDefault(e); + restartBlink(cm); + signalLater(cm, "keyHandled", cm, "'" + ch + "'", e); + } + return handled; + } + + var lastStoppedKey = null; + function onKeyDown(e) { + var cm = this; + if (!cm.state.focused) onFocus(cm); + if (ie && e.keyCode == 27) { e.returnValue = false; } + if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; + var code = e.keyCode; + // IE does strange things with escape. + cm.doc.sel.shift = code == 16 || e.shiftKey; + // First give onKeyEvent option a chance to handle this. + var handled = handleKeyBinding(cm, e); + if (opera) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + cm.replaceSelection(""); + } + } + + function onKeyPress(e) { + var cm = this; + if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; + var keyCode = e.keyCode, charCode = e.charCode; + if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (this.options.electricChars && this.doc.mode.electricChars && + this.options.smartIndent && !isReadOnly(this) && + this.doc.mode.electricChars.indexOf(ch) > -1) + setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75); + if (handleCharBinding(cm, e, ch)) return; + if (ie && !ie_lt9) cm.display.inputHasSelection = null; + fastPoll(cm); + } + + function onFocus(cm) { + if (cm.options.readOnly == "nocursor") return; + if (!cm.state.focused) { + signal(cm, "focus", cm); + cm.state.focused = true; + if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1) + cm.display.wrapper.className += " CodeMirror-focused"; + resetInput(cm, true); + } + slowPoll(cm); + restartBlink(cm); + } + function onBlur(cm) { + if (cm.state.focused) { + signal(cm, "blur", cm); + cm.state.focused = false; + cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", ""); + } + clearInterval(cm.display.blinker); + setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150); + } + + var detectingSelectAll; + function onContextMenu(cm, e) { + if (signalDOMEvent(cm, e, "contextmenu")) return; + var display = cm.display, sel = cm.doc.sel; + if (eventInWidget(display, e)) return; + + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; + if (!pos || opera) return; // Opera is difficult. + if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) + operation(cm, setSelection)(cm.doc, pos, pos); + + var oldCSS = display.input.style.cssText; + display.inputDiv.style.position = "absolute"; + display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" + + "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);"; + focusInput(cm); + resetInput(cm, true); + // Adds "Select all" to context menu in FF + if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " "; + + function prepareSelectAllHack() { + if (display.input.selectionStart != null) { + var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value); + display.prevInput = " "; + display.input.selectionStart = 1; display.input.selectionEnd = extval.length; + } + } + function rehide() { + display.inputDiv.style.position = "relative"; + display.input.style.cssText = oldCSS; + if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos; + slowPoll(cm); + + // Try to detect the user choosing select-all + if (display.input.selectionStart != null) { + if (!ie || ie_lt9) prepareSelectAllHack(); + clearTimeout(detectingSelectAll); + var i = 0, poll = function(){ + if (display.prevInput == " " && display.input.selectionStart == 0) + operation(cm, commands.selectAll)(cm); + else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); + else resetInput(cm); + }; + detectingSelectAll = setTimeout(poll, 200); + } + } + + if (ie && !ie_lt9) prepareSelectAllHack(); + if (captureMiddleClick) { + e_stop(e); + var mouseup = function() { + off(window, "mouseup", mouseup); + setTimeout(rehide, 20); + }; + on(window, "mouseup", mouseup); + } else { + setTimeout(rehide, 50); + } + } + + // UPDATING + + var changeEnd = CodeMirror.changeEnd = function(change) { + if (!change.text) return change.to; + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); + }; + + // Make sure a position will be valid after the given change. + function clipPostChange(doc, change, pos) { + if (!posLess(change.from, pos)) return clipPos(doc, pos); + var diff = (change.text.length - 1) - (change.to.line - change.from.line); + if (pos.line > change.to.line + diff) { + var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1; + if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length); + return clipToLen(pos, getLine(doc, preLine).text.length); + } + if (pos.line == change.to.line + diff) + return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) + + getLine(doc, change.to.line).text.length - change.to.ch); + var inside = pos.line - change.from.line; + return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch)); + } + + // Hint can be null|"end"|"start"|"around"|{anchor,head} + function computeSelAfterChange(doc, change, hint) { + if (hint && typeof hint == "object") // Assumed to be {anchor, head} object + return {anchor: clipPostChange(doc, change, hint.anchor), + head: clipPostChange(doc, change, hint.head)}; + + if (hint == "start") return {anchor: change.from, head: change.from}; + + var end = changeEnd(change); + if (hint == "around") return {anchor: change.from, head: end}; + if (hint == "end") return {anchor: end, head: end}; + + // hint is null, leave the selection alone as much as possible + var adjustPos = function(pos) { + if (posLess(pos, change.from)) return pos; + if (!posLess(change.to, pos)) return end; + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; + if (pos.line == change.to.line) ch += end.ch - change.to.ch; + return Pos(line, ch); + }; + return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)}; + } + + function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function() { this.canceled = true; } + }; + if (update) obj.update = function(from, to, text, origin) { + if (from) this.from = clipPos(doc, from); + if (to) this.to = clipPos(doc, to); + if (text) this.text = text; + if (origin !== undefined) this.origin = origin; + }; + signal(doc, "beforeChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); + + if (obj.canceled) return null; + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; + } + + // Replace the range from from to to by the strings in replacement. + // change is a {from, to, text [, origin]} object + function makeChange(doc, change, selUpdate, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly); + if (doc.cm.state.suppressEdits) return; + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true); + if (!change) return; + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); + if (split) { + for (var i = split.length - 1; i >= 1; --i) + makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]}); + if (split.length) + makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate); + } else { + makeChangeNoReadonly(doc, change, selUpdate); + } + } + + function makeChangeNoReadonly(doc, change, selUpdate) { + var selAfter = computeSelAfterChange(doc, change, selUpdate); + addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); + var rebased = []; + + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); + }); + } + + function makeChangeFromHistory(doc, type) { + if (doc.cm && doc.cm.state.suppressEdits) return; + + var hist = doc.history; + var event = (type == "undo" ? hist.done : hist.undone).pop(); + if (!event) return; + + var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter, + anchorAfter: event.anchorBefore, headAfter: event.headBefore, + generation: hist.generation}; + (type == "undo" ? hist.undone : hist.done).push(anti); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); + + for (var i = event.changes.length - 1; i >= 0; --i) { + var change = event.changes[i]; + change.origin = type; + if (filter && !filterChange(doc, change, false)) { + (type == "undo" ? hist.done : hist.undone).length = 0; + return; + } + + anti.changes.push(historyChangeFromChange(doc, change)); + + var after = i ? computeSelAfterChange(doc, change, null) + : {anchor: event.anchorBefore, head: event.headBefore}; + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); + var rebased = []; + + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); + }); + } + } + + function shiftDoc(doc, distance) { + function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);} + doc.first += distance; + if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance); + doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor); + doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to); + } + + function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); + return; + } + if (change.from.line > doc.lastLine()) return; + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line); + shiftDoc(doc, shift); + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin}; + } + var last = doc.lastLine(); + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin}; + } + + change.removed = getBetween(doc, change.from, change.to); + + if (!selAfter) selAfter = computeSelAfterChange(doc, change, null); + if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter); + else updateDoc(doc, change, spans, selAfter); + } + + function makeChangeSingleDocInEditor(cm, change, spans, selAfter) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to; + + var recomputeMaxLength = false, checkWidthStart = from.line; + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line))); + doc.iter(checkWidthStart, to.line + 1, function(line) { + if (line == display.maxLine) { + recomputeMaxLength = true; + return true; + } + }); + } + + if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head)) + cm.curOp.cursorActivity = true; + + updateDoc(doc, change, spans, selAfter, estimateHeight(cm)); + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function(line) { + var len = lineLength(doc, line); + if (len > display.maxLineLength) { + display.maxLine = line; + display.maxLineLength = len; + display.maxLineChanged = true; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) cm.curOp.updateMaxLine = true; + } + + // Adjust frontier, schedule worker + doc.frontier = Math.min(doc.frontier, from.line); + startWorker(cm, 400); + + var lendiff = change.text.length - (to.line - from.line) - 1; + // Remember that these lines changed, for updating the display + regChange(cm, from.line, to.line + 1, lendiff); + + if (hasHandler(cm, "change")) { + var changeObj = {from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin}; + if (cm.curOp.textChanged) { + for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {} + cur.next = changeObj; + } else cm.curOp.textChanged = changeObj; + } + } + + function replaceRange(doc, code, from, to, origin) { + if (!to) to = from; + if (posLess(to, from)) { var tmp = to; to = from; from = tmp; } + if (typeof code == "string") code = splitLines(code); + makeChange(doc, {from: from, to: to, text: code, origin: origin}, null); + } + + // POSITION OBJECT + + function Pos(line, ch) { + if (!(this instanceof Pos)) return new Pos(line, ch); + this.line = line; this.ch = ch; + } + CodeMirror.Pos = Pos; + + function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} + function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} + function copyPos(x) {return Pos(x.line, x.ch);} + + // SELECTION + + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} + function clipPos(doc, pos) { + if (pos.line < doc.first) return Pos(doc.first, 0); + var last = doc.first + doc.size - 1; + if (pos.line > last) return Pos(last, getLine(doc, last).text.length); + return clipToLen(pos, getLine(doc, pos.line).text.length); + } + function clipToLen(pos, linelen) { + var ch = pos.ch; + if (ch == null || ch > linelen) return Pos(pos.line, linelen); + else if (ch < 0) return Pos(pos.line, 0); + else return pos; + } + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} + + // If shift is held, this will move the selection anchor. Otherwise, + // it'll set the whole selection. + function extendSelection(doc, pos, other, bias) { + if (doc.sel.shift || doc.sel.extend) { + var anchor = doc.sel.anchor; + if (other) { + var posBefore = posLess(pos, anchor); + if (posBefore != posLess(other, anchor)) { + anchor = pos; + pos = other; + } else if (posBefore != posLess(pos, other)) { + pos = other; + } + } + setSelection(doc, anchor, pos, bias); + } else { + setSelection(doc, pos, other || pos, bias); + } + if (doc.cm) doc.cm.curOp.userSelChange = true; + } + + function filterSelectionChange(doc, anchor, head) { + var obj = {anchor: anchor, head: head}; + signal(doc, "beforeSelectionChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); + obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head); + return obj; + } + + // Update the selection. Last two args are only used by + // updateDoc, since they have to be expressed in the line + // numbers before the update. + function setSelection(doc, anchor, head, bias, checkAtomic) { + if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) { + var filtered = filterSelectionChange(doc, anchor, head); + head = filtered.head; + anchor = filtered.anchor; + } + + var sel = doc.sel; + sel.goalColumn = null; + // Skip over atomic spans. + if (checkAtomic || !posEq(anchor, sel.anchor)) + anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push"); + if (checkAtomic || !posEq(head, sel.head)) + head = skipAtomic(doc, head, bias, checkAtomic != "push"); + + if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return; + + sel.anchor = anchor; sel.head = head; + var inv = posLess(head, anchor); + sel.from = inv ? head : anchor; + sel.to = inv ? anchor : head; + + if (doc.cm) + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = + doc.cm.curOp.cursorActivity = true; + + signalLater(doc, "cursorActivity", doc); + } + + function reCheckSelection(cm) { + setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push"); + } + + function skipAtomic(doc, pos, bias, mayClear) { + var flipped = false, curPos = pos; + var dir = bias || 1; + doc.cantEdit = false; + search: for (;;) { + var line = getLine(doc, curPos.line); + if (line.markedSpans) { + for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker; + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) break; + else {--i; continue;} + } + } + if (!m.atomic) continue; + var newPos = m.find()[dir < 0 ? "from" : "to"]; + if (posEq(newPos, curPos)) { + newPos.ch += dir; + if (newPos.ch < 0) { + if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1)); + else newPos = null; + } else if (newPos.ch > line.text.length) { + if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0); + else newPos = null; + } + if (!newPos) { + if (flipped) { + // Driven in a corner -- no valid cursor position found at all + // -- try again *with* clearing, if we didn't already + if (!mayClear) return skipAtomic(doc, pos, bias, true); + // Otherwise, turn off editing until further notice, and return the start of the doc + doc.cantEdit = true; + return Pos(doc.first, 0); + } + flipped = true; newPos = pos; dir = -dir; + } + } + curPos = newPos; + continue search; + } + } + } + return curPos; + } + } + + // SCROLLING + + function scrollCursorIntoView(cm) { + var coords = scrollPosIntoView(cm, cm.doc.sel.head, cm.options.cursorScrollMargin); + if (!cm.state.focused) return; + var display = cm.display, box = getRect(display.sizer), doScroll = null; + if (coords.top + box.top < 0) doScroll = true; + else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; + if (doScroll != null && !phantom) { + var hidden = display.cursor.style.display == "none"; + if (hidden) { + display.cursor.style.display = ""; + display.cursor.style.left = coords.left + "px"; + display.cursor.style.top = (coords.top - display.viewOffset) + "px"; + } + display.cursor.scrollIntoView(doScroll); + if (hidden) display.cursor.style.display = "none"; + } + } + + function scrollPosIntoView(cm, pos, margin) { + if (margin == null) margin = 0; + for (;;) { + var changed = false, coords = cursorCoords(cm, pos); + var scrollPos = calculateScrollPos(cm, coords.left, coords.top - margin, coords.left, coords.bottom + margin); + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; + if (scrollPos.scrollTop != null) { + setScrollTop(cm, scrollPos.scrollTop); + if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft); + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; + } + if (!changed) return coords; + } + } + + function scrollIntoView(cm, x1, y1, x2, y2) { + var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); + if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); + if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); + } + + function calculateScrollPos(cm, x1, y1, x2, y2) { + var display = cm.display, snapMargin = textHeight(cm.display); + if (y1 < 0) y1 = 0; + var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {}; + var docBottom = cm.doc.height + paddingVert(display); + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; + if (y1 < screentop) { + result.scrollTop = atTop ? 0 : y1; + } else if (y2 > screentop + screen) { + var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); + if (newTop != screentop) result.scrollTop = newTop; + } + + var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft; + x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth; + var gutterw = display.gutters.offsetWidth; + var atLeft = x1 < gutterw + 10; + if (x1 < screenleft + gutterw || atLeft) { + if (atLeft) x1 = 0; + result.scrollLeft = Math.max(0, x1 - 10 - gutterw); + } else if (x2 > screenw + screenleft - 3) { + result.scrollLeft = x2 + 10 - screenw; + } + return result; + } + + function updateScrollPos(cm, left, top) { + cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left, + scrollTop: top == null ? cm.doc.scrollTop : top}; + } + + function addToScrollPos(cm, left, top) { + var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop}); + var scroll = cm.display.scroller; + pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top)); + pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left)); + } + + // API UTILITIES + + function indentLine(cm, n, how, aggressive) { + var doc = cm.doc; + if (how == null) how = "add"; + if (how == "smart") { + if (!cm.doc.mode.indent) how = "prev"; + else var state = getStateBefore(cm, n); + } + + var tabSize = cm.options.tabSize; + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); + var curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (how == "smart") { + indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass) { + if (!aggressive) return; + how = "prev"; + } + } + if (how == "prev") { + if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); + else indentation = 0; + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit; + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; + } + indentation = Math.max(0, indentation); + + var indentString = "", pos = 0; + if (cm.options.indentWithTabs) + for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} + if (pos < indentation) indentString += spaceStr(indentation - pos); + + if (indentString != curSpaceString) + replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + line.stateAfter = null; + } + + function changeLine(cm, handle, op) { + var no = handle, line = handle, doc = cm.doc; + if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no)) regChange(cm, no, no + 1); + else return null; + return line; + } + + function findPosH(doc, pos, dir, unit, visually) { + var line = pos.line, ch = pos.ch, origDir = dir; + var lineObj = getLine(doc, line); + var possible = true; + function findNextLine() { + var l = line + dir; + if (l < doc.first || l >= doc.first + doc.size) return (possible = false); + line = l; + return lineObj = getLine(doc, l); + } + function moveOnce(boundToLine) { + var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); + if (next == null) { + if (!boundToLine && findNextLine()) { + if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); + else ch = dir < 0 ? lineObj.text.length : 0; + } else return (possible = false); + } else ch = next; + return true; + } + + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group"; + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) break; + var cur = lineObj.text.charAt(ch) || "\n"; + var type = isWordChar(cur) ? "w" + : !group ? null + : /\s/.test(cur) ? null + : "p"; + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce();} + break; + } + if (type) sawType = type; + if (dir > 0 && !moveOnce(!first)) break; + } + } + var result = skipAtomic(doc, Pos(line, ch), origDir, true); + if (!possible) result.hitSide = true; + return result; + } + + function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y; + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); + y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3; + } + for (;;) { + var target = coordsChar(cm, x, y); + if (!target.outside) break; + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } + y += dir * 5; + } + return target; + } + + function findWordAt(line, pos) { + var start = pos.ch, end = pos.ch; + if (line) { + if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; + var startChar = line.charAt(start); + var check = isWordChar(startChar) ? isWordChar + : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} + : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + } + return {from: Pos(pos.line, start), to: Pos(pos.line, end)}; + } + + function selectLine(cm, line) { + extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0))); + } + + // PROTOTYPE + + // The publicly visible API. Note that operation(null, f) means + // 'wrap f in an operation, performed on its `this` parameter' + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);}, + + setOption: function(option, value) { + var options = this.options, old = options[option]; + if (options[option] == value && option != "mode") return; + options[option] = value; + if (optionHandlers.hasOwnProperty(option)) + operation(this, optionHandlers[option])(this, value, old); + }, + + getOption: function(option) {return this.options[option];}, + getDoc: function() {return this.doc;}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](map); + }, + removeKeyMap: function(map) { + var maps = this.state.keyMaps; + for (var i = 0; i < maps.length; ++i) + if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) { + maps.splice(i, 1); + return true; + } + }, + + addOverlay: operation(null, function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); + if (mode.startState) throw new Error("Overlays may not be stateful."); + this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); + this.state.modeGen++; + regChange(this); + }), + removeOverlay: operation(null, function(spec) { + var overlays = this.state.overlays; + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1); + this.state.modeGen++; + regChange(this); + return; + } + } + }), + + indentLine: operation(null, function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); + }), + indentSelection: operation(null, function(how) { + var sel = this.doc.sel; + if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how); + var e = sel.to.line - (sel.to.ch ? 0 : 1); + for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how); + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + var doc = this.doc; + pos = clipPos(doc, pos); + var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode; + var line = getLine(doc, pos.line); + var stream = new StringStream(line.text, this.options.tabSize); + while (stream.pos < pos.ch && !stream.eol()) { + stream.start = stream.pos; + var style = mode.token(stream, state); + } + return {start: stream.start, + end: stream.pos, + string: stream.current(), + className: style || null, // Deprecated, use 'type' instead + type: style || null, + state: state}; + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + if (ch == 0) return styles[2]; + for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; + else if (styles[mid * 2 + 1] < ch) before = mid + 1; + else return styles[mid * 2 + 2]; + } + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) return mode; + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; + }, + + getHelper: function(pos, type) { + if (!helpers.hasOwnProperty(type)) return; + var help = helpers[type], mode = this.getModeAt(pos); + return mode[type] && help[mode[type]] || + mode.helperType && help[mode.helperType] || + help[mode.name]; + }, + + getStateAfter: function(line, precise) { + var doc = this.doc; + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); + return getStateBefore(this, line + 1, precise); + }, + + cursorCoords: function(start, mode) { + var pos, sel = this.doc.sel; + if (start == null) pos = sel.head; + else if (typeof start == "object") pos = clipPos(this.doc, start); + else pos = start ? sel.from : sel.to; + return cursorCoords(this, pos, mode || "page"); + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page"); + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page"); + return coordsChar(this, coords.left, coords.top); + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset); + }, + heightAtLine: function(line, mode) { + var end = false, last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) line = this.doc.first; + else if (line > last) { line = last; end = true; } + var lineObj = getLine(this.doc, line); + return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top + + (end ? lineObj.height : 0); + }, + + defaultTextHeight: function() { return textHeight(this.display); }, + defaultCharWidth: function() { return charWidth(this.display); }, + + setGutterMarker: operation(null, function(line, gutterID, value) { + return changeLine(this, line, function(line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}); + markers[gutterID] = value; + if (!value && isEmpty(markers)) line.gutterMarkers = null; + return true; + }); + }), + + clearGutter: operation(null, function(gutterID) { + var cm = this, doc = cm.doc, i = doc.first; + doc.iter(function(line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + line.gutterMarkers[gutterID] = null; + regChange(cm, i, i + 1); + if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; + } + ++i; + }); + }), + + addLineClass: operation(null, function(handle, where, cls) { + return changeLine(this, handle, function(line) { + var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; + if (!line[prop]) line[prop] = cls; + else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false; + else line[prop] += " " + cls; + return true; + }); + }), + + removeLineClass: operation(null, function(handle, where, cls) { + return changeLine(this, handle, function(line) { + var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; + var cur = line[prop]; + if (!cur) return false; + else if (cls == null) line[prop] = null; + else { + var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)")); + if (!found) return false; + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; + } + return true; + }); + }), + + addLineWidget: operation(null, function(handle, node, options) { + return addLineWidget(this, handle, node, options); + }), + + removeLineWidget: function(widget) { widget.clear(); }, + + lineInfo: function(line) { + if (typeof line == "number") { + if (!isLine(this.doc, line)) return null; + var n = line; + line = getLine(this.doc, line); + if (!line) return null; + } else { + var n = lineNo(line); + if (n == null) return null; + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets}; + }, + + getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display; + pos = cursorCoords(this, clipPos(this.doc, pos)); + var top = pos.bottom, left = pos.left; + node.style.position = "absolute"; + display.sizer.appendChild(node); + if (vert == "over") { + top = pos.top; + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + top = pos.top - node.offsetHeight; + else if (pos.bottom + node.offsetHeight <= vspace) + top = pos.bottom; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = top + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; + node.style.left = left + "px"; + } + if (scroll) + scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + triggerOnKeyDown: operation(null, onKeyDown), + + execCommand: function(cmd) {return commands[cmd](this);}, + + findPosH: function(from, amount, unit, visually) { + var dir = 1; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + cur = findPosH(this.doc, cur, dir, unit, visually); + if (cur.hitSide) break; + } + return cur; + }, + + moveH: operation(null, function(dir, unit) { + var sel = this.doc.sel, pos; + if (sel.shift || sel.extend || posEq(sel.from, sel.to)) + pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually); + else + pos = dir < 0 ? sel.from : sel.to; + extendSelection(this.doc, pos, pos, dir); + }), + + deleteH: operation(null, function(dir, unit) { + var sel = this.doc.sel; + if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete"); + else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete"); + this.curOp.userSelChange = true; + }), + + findPosV: function(from, amount, unit, goalColumn) { + var dir = 1, x = goalColumn; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + var coords = cursorCoords(this, cur, "div"); + if (x == null) x = coords.left; + else coords.left = x; + cur = findPosV(this, coords, dir, unit); + if (cur.hitSide) break; + } + return cur; + }, + + moveV: operation(null, function(dir, unit) { + var sel = this.doc.sel; + var pos = cursorCoords(this, sel.head, "div"); + if (sel.goalColumn != null) pos.left = sel.goalColumn; + var target = findPosV(this, pos, dir, unit); + + if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top); + extendSelection(this.doc, target, target, dir); + sel.goalColumn = pos.left; + }), + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) return; + if (this.state.overwrite = !this.state.overwrite) + this.display.cursor.className += " CodeMirror-overwrite"; + else + this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", ""); + }, + hasFocus: function() { return this.state.focused; }, + + scrollTo: operation(null, function(x, y) { + updateScrollPos(this, x, y); + }), + getScrollInfo: function() { + var scroller = this.display.scroller, co = scrollerCutOff; + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - co, width: scroller.scrollWidth - co, + clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; + }, + + scrollIntoView: operation(null, function(pos, margin) { + if (typeof pos == "number") pos = Pos(pos, 0); + if (!margin) margin = 0; + var coords = pos; + + if (!pos || pos.line != null) { + this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head; + this.curOp.scrollToPosMargin = margin; + coords = cursorCoords(this, this.curOp.scrollToPos); + } + var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin); + updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop); + }), + + setSize: operation(null, function(width, height) { + function interpret(val) { + return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; + } + if (width != null) this.display.wrapper.style.width = interpret(width); + if (height != null) this.display.wrapper.style.height = interpret(height); + if (this.options.lineWrapping) + this.display.measureLineCache.length = this.display.measureLineCachePos = 0; + this.curOp.forceUpdate = true; + }), + + operation: function(f){return runInOp(this, f);}, + + refresh: operation(null, function() { + clearCaches(this); + updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop); + regChange(this); + }), + + swapDoc: operation(null, function(doc) { + var old = this.doc; + old.cm = null; + attachDoc(this, doc); + clearCaches(this); + resetInput(this, true); + updateScrollPos(this, doc.scrollLeft, doc.scrollTop); + return old; + }), + + getInputField: function(){return this.display.input;}, + getWrapperElement: function(){return this.display.wrapper;}, + getScrollerElement: function(){return this.display.scroller;}, + getGutterElement: function(){return this.display.gutters;} + }; + eventMixin(CodeMirror); + + // OPTION DEFAULTS + + var optionHandlers = CodeMirror.optionHandlers = {}; + + // The default configuration options. + var defaults = CodeMirror.defaults = {}; + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt; + if (handle) optionHandlers[name] = + notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; + } + + var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function(cm, val) { + cm.setValue(val); + }, true); + option("mode", null, function(cm, val) { + cm.doc.modeOption = val; + loadMode(cm); + }, true); + + option("indentUnit", 2, loadMode, true); + option("indentWithTabs", false); + option("smartIndent", true); + option("tabSize", 4, function(cm) { + loadMode(cm); + clearCaches(cm); + regChange(cm); + }, true); + option("electricChars", true); + option("rtlMoveVisually", !windows); + + option("theme", "default", function(cm) { + themeChanged(cm); + guttersChanged(cm); + }, true); + option("keyMap", "default", keyMapChanged); + option("extraKeys", null); + + option("onKeyEvent", null); + option("onDragEvent", null); + + option("lineWrapping", false, wrappingChanged, true); + option("gutters", [], function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("fixedGutter", true, function(cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; + cm.refresh(); + }, true); + option("coverGutterNextToScrollbar", false, updateScrollbars, true); + option("lineNumbers", false, function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("firstLineNumber", 1, guttersChanged, true); + option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); + option("showCursorWhenSelecting", false, updateSelection, true); + + option("readOnly", false, function(cm, val) { + if (val == "nocursor") {onBlur(cm); cm.display.input.blur();} + else if (!val) resetInput(cm, true); + }); + option("dragDrop", true); + + option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); + option("cursorHeight", 1); + option("workTime", 100); + option("workDelay", 100); + option("flattenSpans", true); + option("pollInterval", 100); + option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;}); + option("historyEventDelay", 500); + option("viewportMargin", 10, function(cm){cm.refresh();}, true); + option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true); + option("moveInputWithCursor", true, function(cm, val) { + if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0; + }); + + option("tabindex", null, function(cm, val) { + cm.display.input.tabIndex = val || ""; + }); + option("autofocus", null); + + // MODE DEFINITION AND QUERYING + + // Known modes, by name and by MIME + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; + + CodeMirror.defineMode = function(name, mode) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) { + mode.dependencies = []; + for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); + } + modes[name] = mode; + }; + + CodeMirror.defineMIME = function(mime, spec) { + mimeModes[mime] = spec; + }; + + CodeMirror.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return CodeMirror.resolveMode("application/xml"); + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) continue; + if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; + modeObj[prop] = exts[prop]; + } + } + modeObj.name = spec.name; + + return modeObj; + }; + + CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + var modeExtensions = CodeMirror.modeExtensions = {}; + CodeMirror.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + copyObj(properties, exts); + }; + + // EXTENSIONS + + CodeMirror.defineExtension = function(name, func) { + CodeMirror.prototype[name] = func; + }; + CodeMirror.defineDocExtension = function(name, func) { + Doc.prototype[name] = func; + }; + CodeMirror.defineOption = option; + + var initHooks = []; + CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + + var helpers = CodeMirror.helpers = {}; + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {}; + helpers[type][name] = value; + }; + + // UTILITIES + + CodeMirror.isWordChar = isWordChar; + + // MODE STATE HANDLING + + // Utility functions for working with state. Exported because modes + // sometimes need to do this. + function copyState(mode, state) { + if (state === true) return state; + if (mode.copyState) return mode.copyState(state); + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) val = val.concat([]); + nstate[n] = val; + } + return nstate; + } + CodeMirror.copyState = copyState; + + function startState(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; + } + CodeMirror.startState = startState; + + CodeMirror.innerMode = function(mode, state) { + while (mode.innerMode) { + var info = mode.innerMode(state); + if (!info || info.mode == mode) break; + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; + }; + + // STANDARD COMMANDS + + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));}, + killLine: function(cm) { + var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); + if (!sel && cm.getLine(from.line).length == from.ch) + cm.replaceRange("", from, Pos(from.line + 1, 0), "+delete"); + else cm.replaceRange("", from, sel ? to : Pos(from.line), "+delete"); + }, + deleteLine: function(cm) { + var l = cm.getCursor().line; + cm.replaceRange("", Pos(l, 0), Pos(l), "+delete"); + }, + delLineLeft: function(cm) { + var cur = cm.getCursor(); + cm.replaceRange("", Pos(cur.line, 0), cur, "+delete"); + }, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, + goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, + goLineStart: function(cm) { + cm.extendSelection(lineStart(cm, cm.getCursor().line)); + }, + goLineStartSmart: function(cm) { + var cur = cm.getCursor(), start = lineStart(cm, cur.line); + var line = cm.getLineHandle(start.line); + var order = getOrder(line); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)); + var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch; + cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS)); + } else cm.extendSelection(start); + }, + goLineEnd: function(cm) { + cm.extendSelection(lineEnd(cm, cm.getCursor().line)); + }, + goLineRight: function(cm) { + var top = cm.charCoords(cm.getCursor(), "div").top + 5; + cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")); + }, + goLineLeft: function(cm) { + var top = cm.charCoords(cm.getCursor(), "div").top + 5; + cm.extendSelection(cm.coordsChar({left: 0, top: top}, "div")); + }, + goLineUp: function(cm) {cm.moveV(-1, "line");}, + goLineDown: function(cm) {cm.moveV(1, "line");}, + goPageUp: function(cm) {cm.moveV(-1, "page");}, + goPageDown: function(cm) {cm.moveV(1, "page");}, + goCharLeft: function(cm) {cm.moveH(-1, "char");}, + goCharRight: function(cm) {cm.moveH(1, "char");}, + goColumnLeft: function(cm) {cm.moveH(-1, "column");}, + goColumnRight: function(cm) {cm.moveH(1, "column");}, + goWordLeft: function(cm) {cm.moveH(-1, "word");}, + goGroupRight: function(cm) {cm.moveH(1, "group");}, + goGroupLeft: function(cm) {cm.moveH(-1, "group");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharBefore: function(cm) {cm.deleteH(-1, "char");}, + delCharAfter: function(cm) {cm.deleteH(1, "char");}, + delWordBefore: function(cm) {cm.deleteH(-1, "word");}, + delWordAfter: function(cm) {cm.deleteH(1, "word");}, + delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, + delGroupAfter: function(cm) {cm.deleteH(1, "group");}, + indentAuto: function(cm) {cm.indentSelection("smart");}, + indentMore: function(cm) {cm.indentSelection("add");}, + indentLess: function(cm) {cm.indentSelection("subtract");}, + insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");}, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.replaceSelection("\t", "end", "+input"); + }, + transposeChars: function(cm) { + var cur = cm.getCursor(), line = cm.getLine(cur.line); + if (cur.ch > 0 && cur.ch < line.length - 1) + cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), + Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1)); + }, + newlineAndIndent: function(cm) { + operation(cm, function() { + cm.replaceSelection("\n", "end", "+input"); + cm.indentLine(cm.getCursor().line, null, true); + })(); + }, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + // STANDARD KEYMAPS + + var keyMap = CodeMirror.keyMap = {}; + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" + }; + // Note that the save and find-related commands aren't defined by + // default. Unknown commands are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + fallthrough: "basic" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft", + fallthrough: ["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" + }; + + // KEYMAP DISPATCH + + function getKeyMap(val) { + if (typeof val == "string") return keyMap[val]; + else return val; + } + + function lookupKey(name, maps, handle) { + function lookup(map) { + map = getKeyMap(map); + var found = map[name]; + if (found === false) return "stop"; + if (found != null && handle(found)) return true; + if (map.nofallthrough) return "stop"; + + var fallthrough = map.fallthrough; + if (fallthrough == null) return false; + if (Object.prototype.toString.call(fallthrough) != "[object Array]") + return lookup(fallthrough); + for (var i = 0, e = fallthrough.length; i < e; ++i) { + var done = lookup(fallthrough[i]); + if (done) return done; + } + return false; + } + + for (var i = 0; i < maps.length; ++i) { + var done = lookup(maps[i]); + if (done) return done != "stop"; + } + } + function isModifierKey(event) { + var name = keyNames[event.keyCode]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + } + function keyName(event, noShift) { + if (opera && event.keyCode == 34 && event["char"]) return false; + var name = keyNames[event.keyCode]; + if (name == null || event.altGraphKey) return false; + if (event.altKey) name = "Alt-" + name; + if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name; + if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name; + if (!noShift && event.shiftKey) name = "Shift-" + name; + return name; + } + CodeMirror.lookupKey = lookupKey; + CodeMirror.isModifierKey = isModifierKey; + CodeMirror.keyName = keyName; + + // FROMTEXTAREA + + CodeMirror.fromTextArea = function(textarea, options) { + if (!options) options = {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabindex) + options.tabindex = textarea.tabindex; + if (!options.placeholder && textarea.placeholder) + options.placeholder = textarea.placeholder; + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = document.body; + // doc.activeElement occasionally throws on IE + try { hasFocus = document.activeElement; } catch(e) {} + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } + + function save() {textarea.value = cm.getValue();} + if (textarea.form) { + on(textarea.form, "submit", save); + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form, realSubmit = form.submit; + try { + var wrappedSubmit = form.submit = function() { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + } catch(e) {} + } + } + + textarea.style.display = "none"; + var cm = CodeMirror(function(node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + cm.save = save; + cm.getTextArea = function() { return textarea; }; + cm.toTextArea = function() { + save(); + textarea.parentNode.removeChild(cm.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + off(textarea.form, "submit", save); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + return cm; + }; + + // STRING STREAM + + // Fed to the mode parsers, provides helper functions to make + // parsers more succinct. + + // The character stream used by a mode's parser. + function StringStream(string, tabSize) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; + } + + StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == 0;}, + peek: function() {return this.string.charAt(this.pos) || undefined;}, + next: function() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + }, + eat: function(match) { + var ch = this.string.charAt(this.pos); + if (typeof match == "string") var ok = ch == match; + else var ok = ch && (match.test ? match.test(ch) : match(ch)); + if (ok) {++this.pos; return ch;} + }, + eatWhile: function(match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start; + }, + eatSpace: function() { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; + return this.pos > start; + }, + skipToEnd: function() {this.pos = this.string.length;}, + skipTo: function(ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true;} + }, + backUp: function(n) {this.pos -= n;}, + column: function() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue; + }, + indentation: function() {return countColumn(this.string, null, this.tabSize);}, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);} + }; + CodeMirror.StringStream = StringStream; + + // TEXTMARKERS + + function TextMarker(doc, type) { + this.lines = []; + this.type = type; + this.doc = doc; + } + CodeMirror.TextMarker = TextMarker; + eventMixin(TextMarker); + + TextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + var cm = this.doc.cm, withOp = cm && !cm.curOp; + if (withOp) startOperation(cm); + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) signalLater(this, "clear", found.from, found.to); + } + var min = null, max = null; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.to != null) max = lineNo(line); + line.markedSpans = removeMarkedSpan(line.markedSpans, span); + if (span.from != null) + min = lineNo(line); + else if (this.collapsed && !lineIsHidden(this.doc, line) && cm) + updateLineHeight(line, textHeight(cm.display)); + } + if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { + var visual = visualLine(cm.doc, this.lines[i]), len = lineLength(cm.doc, visual); + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual; + cm.display.maxLineLength = len; + cm.display.maxLineChanged = true; + } + } + + if (min != null && cm) regChange(cm, min, max + 1); + this.lines.length = 0; + this.explicitlyCleared = true; + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false; + if (cm) reCheckSelection(cm); + } + if (withOp) endOperation(cm); + }; + + TextMarker.prototype.find = function() { + var from, to; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null || span.to != null) { + var found = lineNo(line); + if (span.from != null) from = Pos(found, span.from); + if (span.to != null) to = Pos(found, span.to); + } + } + if (this.type == "bookmark") return from; + return from && {from: from, to: to}; + }; + + TextMarker.prototype.changed = function() { + var pos = this.find(), cm = this.doc.cm; + if (!pos || !cm) return; + var line = getLine(this.doc, pos.from.line); + clearCachedMeasurement(cm, line); + if (pos.from.line >= cm.display.showingFrom && pos.from.line < cm.display.showingTo) { + for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) { + if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight); + break; + } + runInOp(cm, function() { + cm.curOp.selectionChanged = cm.curOp.forceUpdate = cm.curOp.updateMaxLine = true; + }); + } + }; + + TextMarker.prototype.attachLine = function(line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); + } + this.lines.push(line); + }; + TextMarker.prototype.detachLine = function(line) { + this.lines.splice(indexOf(this.lines, line), 1); + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); + } + }; + + function markText(doc, from, to, options, type) { + if (options && options.shared) return markTextShared(doc, from, to, options, type); + if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); + + var marker = new TextMarker(doc, type); + if (type == "range" && !posLess(from, to)) return marker; + if (options) copyObj(options, marker); + if (marker.replacedWith) { + marker.collapsed = true; + marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true; + } + if (marker.collapsed) sawCollapsedSpans = true; + + if (marker.addToHistory) + addToHistory(doc, {from: from, to: to, origin: "markText"}, + {head: doc.sel.head, anchor: doc.sel.anchor}, NaN); + + var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine; + doc.iter(curLine, to.line + 1, function(line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine) + updateMaxLine = true; + var span = {from: null, to: null, marker: marker}; + size += line.text.length; + if (curLine == from.line) {span.from = from.ch; size -= from.ch;} + if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;} + if (marker.collapsed) { + if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch); + if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch); + else updateLineHeight(line, 0); + } + addMarkedSpan(line, span); + ++curLine; + }); + if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { + if (lineIsHidden(doc, line)) updateLineHeight(line, 0); + }); + + if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); + + if (marker.readOnly) { + sawReadOnlySpans = true; + if (doc.history.done.length || doc.history.undone.length) + doc.clearHistory(); + } + if (marker.collapsed) { + if (collapsedAtStart != collapsedAtEnd) + throw new Error("Inserting collapsed marker overlapping an existing one"); + marker.size = size; + marker.atomic = true; + } + if (cm) { + if (updateMaxLine) cm.curOp.updateMaxLine = true; + if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.collapsed) + regChange(cm, from.line, to.line + 1); + if (marker.atomic) reCheckSelection(cm); + } + return marker; + } + + // SHARED TEXTMARKERS + + function SharedTextMarker(markers, primary) { + this.markers = markers; + this.primary = primary; + for (var i = 0, me = this; i < markers.length; ++i) { + markers[i].parent = this; + on(markers[i], "clear", function(){me.clear();}); + } + } + CodeMirror.SharedTextMarker = SharedTextMarker; + eventMixin(SharedTextMarker); + + SharedTextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + this.explicitlyCleared = true; + for (var i = 0; i < this.markers.length; ++i) + this.markers[i].clear(); + signalLater(this, "clear"); + }; + SharedTextMarker.prototype.find = function() { + return this.primary.find(); + }; + + function markTextShared(doc, from, to, options, type) { + options = copyObj(options); + options.shared = false; + var markers = [markText(doc, from, to, options, type)], primary = markers[0]; + var widget = options.replacedWith; + linkedDocs(doc, function(doc) { + if (widget) options.replacedWith = widget.cloneNode(true); + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); + for (var i = 0; i < doc.linked.length; ++i) + if (doc.linked[i].isParent) return; + primary = lst(markers); + }); + return new SharedTextMarker(markers, primary); + } + + // TEXTMARKER SPANS + + function getMarkedSpanFor(spans, marker) { + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) return span; + } + } + function removeMarkedSpan(spans, span) { + for (var r, i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]); + return r; + } + function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; + span.marker.attachLine(line); + } + + function markedSpansBefore(old, startCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); + (nw || (nw = [])).push({from: span.from, + to: endsAfter ? null : span.to, + marker: marker}); + } + } + return nw; + } + + function markedSpansAfter(old, endCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); + (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh, + to: span.to == null ? null : span.to - endCh, + marker: marker}); + } + } + return nw; + } + + function stretchSpansOverChange(doc, change) { + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; + if (!oldFirst && !oldLast) return null; + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = posEq(change.from, change.to); + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert); + var last = markedSpansAfter(oldLast, endCh, isInsert); + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) span.to = startCh; + else if (sameLine) span.to = found.to == null ? null : found.to + offset; + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i = 0; i < last.length; ++i) { + var span = last[i]; + if (span.to != null) span.to += offset; + if (span.from == null) { + var found = getMarkedSpanFor(first, span.marker); + if (!found) { + span.from = offset; + if (sameLine) (first || (first = [])).push(span); + } + } else { + span.from += offset; + if (sameLine) (first || (first = [])).push(span); + } + } + } + if (sameLine && first) { + // Make sure we didn't create any zero-length spans + for (var i = 0; i < first.length; ++i) + if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != "bookmark") + first.splice(i--, 1); + if (!first.length) first = null; + } + + var newMarkers = [first]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers; + if (gap > 0 && first) + for (var i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); + for (var i = 0; i < gap; ++i) + newMarkers.push(gapMarkers); + newMarkers.push(last); + } + return newMarkers; + } + + function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change); + var stretched = stretchSpansOverChange(doc, change); + if (!old) return stretched; + if (!stretched) return old; + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i]; + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j]; + for (var k = 0; k < oldCur.length; ++k) + if (oldCur[k].marker == span.marker) continue spans; + oldCur.push(span); + } + } else if (stretchCur) { + old[i] = stretchCur; + } + } + return old; + } + + function removeReadOnlyRanges(doc, from, to) { + var markers = null; + doc.iter(from.line, to.line + 1, function(line) { + if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker; + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + (markers || (markers = [])).push(mark); + } + }); + if (!markers) return null; + var parts = [{from: from, to: to}]; + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(); + for (var j = 0; j < parts.length; ++j) { + var p = parts[j]; + if (posLess(p.to, m.from) || posLess(m.to, p.from)) continue; + var newParts = [j, 1]; + if (posLess(p.from, m.from) || !mk.inclusiveLeft && posEq(p.from, m.from)) + newParts.push({from: p.from, to: m.from}); + if (posLess(m.to, p.to) || !mk.inclusiveRight && posEq(p.to, m.to)) + newParts.push({from: m.to, to: p.to}); + parts.splice.apply(parts, newParts); + j += newParts.length - 1; + } + } + return parts; + } + + function collapsedSpanAt(line, ch) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (!sp.marker.collapsed) continue; + if ((sp.from == null || sp.from < ch) && + (sp.to == null || sp.to > ch) && + (!found || found.width < sp.marker.width)) + found = sp.marker; + } + return found; + } + function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); } + function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); } + + function visualLine(doc, line) { + var merged; + while (merged = collapsedSpanAtStart(line)) + line = getLine(doc, merged.find().from.line); + return line; + } + + function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (!sp.marker.collapsed) continue; + if (sp.from == null) return true; + if (sp.marker.replacedWith) continue; + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + return true; + } + } + function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find().to, endLine = getLine(doc, end.line); + return lineIsHiddenInner(doc, endLine, getMarkedSpanFor(endLine.markedSpans, span.marker)); + } + if (span.marker.inclusiveRight && span.to == line.text.length) + return true; + for (var sp, i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i]; + if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) return true; + } + } + + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.detachLine(line); + line.markedSpans = null; + } + + function attachMarkedSpans(line, spans) { + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.attachLine(line); + line.markedSpans = spans; + } + + // LINE WIDGETS + + var LineWidget = CodeMirror.LineWidget = function(cm, node, options) { + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) + this[opt] = options[opt]; + this.cm = cm; + this.node = node; + }; + eventMixin(LineWidget); + function widgetOperation(f) { + return function() { + var withOp = !this.cm.curOp; + if (withOp) startOperation(this.cm); + try {var result = f.apply(this, arguments);} + finally {if (withOp) endOperation(this.cm);} + return result; + }; + } + LineWidget.prototype.clear = widgetOperation(function() { + var ws = this.line.widgets, no = lineNo(this.line); + if (no == null || !ws) return; + for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); + if (!ws.length) this.line.widgets = null; + var aboveVisible = heightAtLine(this.cm, this.line) < this.cm.doc.scrollTop; + updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this))); + if (aboveVisible) addToScrollPos(this.cm, 0, -this.height); + regChange(this.cm, no, no + 1); + }); + LineWidget.prototype.changed = widgetOperation(function() { + var oldH = this.height; + this.height = null; + var diff = widgetHeight(this) - oldH; + if (!diff) return; + updateLineHeight(this.line, this.line.height + diff); + var no = lineNo(this.line); + regChange(this.cm, no, no + 1); + }); + + function widgetHeight(widget) { + if (widget.height != null) return widget.height; + if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1) + removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative")); + return widget.height = widget.node.offsetHeight; + } + + function addLineWidget(cm, handle, node, options) { + var widget = new LineWidget(cm, node, options); + if (widget.noHScroll) cm.display.alignWidgets = true; + changeLine(cm, handle, function(line) { + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) widgets.push(widget); + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); + widget.line = line; + if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) { + var aboveVisible = heightAtLine(cm, line) < cm.doc.scrollTop; + updateLineHeight(line, line.height + widgetHeight(widget)); + if (aboveVisible) addToScrollPos(cm, 0, widget.height); + } + return true; + }); + return widget; + } + + // LINE DATA STRUCTURE + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + eventMixin(Line); + + function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text; + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + if (line.order != null) line.order = null; + detachMarkedSpans(line); + attachMarkedSpans(line, markedSpans); + var estHeight = estimateHeight ? estimateHeight(line) : 1; + if (estHeight != line.height) updateLineHeight(line, estHeight); + } + + function cleanUpLine(line) { + line.parent = null; + detachMarkedSpans(line); + } + + // Run the given mode's parser over a line, update the styles + // array, which contains alternating fragments of text and CSS + // classes. + function runMode(cm, text, mode, state, f) { + var flattenSpans = mode.flattenSpans; + if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize), style; + if (text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false; + // Webkit seems to refuse to render text nodes longer than 57444 characters + stream.pos = Math.min(text.length, stream.start + 50000); + style = null; + } else { + style = mode.token(stream, state); + } + if (!flattenSpans || curStyle != style) { + if (curStart < stream.start) f(stream.start, curStyle); + curStart = stream.start; curStyle = style; + } + stream.start = stream.pos; + } + if (curStart < stream.pos) f(stream.pos, curStyle); + } + + function highlightLine(cm, line, state) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen]; + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, state, function(end, style) {st.push(end, style);}); + + // Run overlays, adjust style array. + for (var o = 0; o < cm.state.overlays.length; ++o) { + var overlay = cm.state.overlays[o], i = 1, at = 0; + runMode(cm, line.text, overlay.mode, true, function(end, style) { + var start = i; + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i]; + if (i_end > end) + st.splice(i, 1, end, st[i+1], i_end); + i += 2; + at = Math.min(end, i_end); + } + if (!style) return; + if (overlay.opaque) { + st.splice(start, i - start, end, style); + i = start + 2; + } else { + for (; start < i; start += 2) { + var cur = st[start+1]; + st[start+1] = cur ? cur + " " + style : style; + } + } + }); + } + + return st; + } + + function getLineStyles(cm, line) { + if (!line.styles || line.styles[0] != cm.state.modeGen) + line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); + return line.styles; + } + + // Lightweight form of highlight -- proceed over this line and + // update state, but don't save a style array. + function processLine(cm, line, state) { + var mode = cm.doc.mode; + var stream = new StringStream(line.text, cm.options.tabSize); + if (line.text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { + mode.token(stream, state); + stream.start = stream.pos; + } + } + + var styleToClassCache = {}; + function styleToClass(style) { + if (!style) return null; + return styleToClassCache[style] || + (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-")); + } + + function lineContent(cm, realLine, measure, copyWidgets) { + var merged, line = realLine, empty = true; + while (merged = collapsedSpanAtStart(line)) + line = getLine(cm.doc, merged.find().from.line); + + var builder = {pre: elt("pre"), col: 0, pos: 0, + measure: null, measuredSomething: false, cm: cm, + copyWidgets: copyWidgets}; + if (line.textClass) builder.pre.className = line.textClass; + + do { + if (line.text) empty = false; + builder.measure = line == realLine && measure; + builder.pos = 0; + builder.addToken = builder.measure ? buildTokenMeasure : buildToken; + if ((ie || webkit) && cm.getOption("lineWrapping")) + builder.addToken = buildTokenSplitSpaces(builder.addToken); + var next = insertLineContent(line, builder, getLineStyles(cm, line)); + if (measure && line == realLine && !builder.measuredSomething) { + measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure)); + builder.measuredSomething = true; + } + if (next) line = getLine(cm.doc, next.to.line); + } while (next); + + if (measure && !builder.measuredSomething && !measure[0]) + measure[0] = builder.pre.appendChild(empty ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure)); + if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine)) + builder.pre.appendChild(document.createTextNode("\u00a0")); + + var order; + // Work around problem with the reported dimensions of single-char + // direction spans on IE (issue #1129). See also the comment in + // cursorCoords. + if (measure && ie && (order = getOrder(line))) { + var l = order.length - 1; + if (order[l].from == order[l].to) --l; + var last = order[l], prev = order[l - 1]; + if (last.from + 1 == last.to && prev && last.level < prev.level) { + var span = measure[builder.pos - 1]; + if (span) span.parentNode.insertBefore(span.measureRight = zeroWidthElement(cm.display.measure), + span.nextSibling); + } + } + + signal(cm, "renderLine", cm, realLine, builder.pre); + return builder.pre; + } + + var tokenSpecialChars = /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g; + function buildToken(builder, text, style, startStyle, endStyle, title) { + if (!text) return; + if (!tokenSpecialChars.test(text)) { + builder.col += text.length; + var content = document.createTextNode(text); + } else { + var content = document.createDocumentFragment(), pos = 0; + while (true) { + tokenSpecialChars.lastIndex = pos; + var m = tokenSpecialChars.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); + builder.col += skipped; + } + if (!m) break; + pos += skipped + 1; + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; + content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + builder.col += tabWidth; + } else { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + m[0].charCodeAt(0).toString(16); + content.appendChild(token); + builder.col += 1; + } + } + } + if (style || startStyle || endStyle || builder.measure) { + var fullStyle = style || ""; + if (startStyle) fullStyle += startStyle; + if (endStyle) fullStyle += endStyle; + var token = elt("span", [content], fullStyle); + if (title) token.title = title; + return builder.pre.appendChild(token); + } + builder.pre.appendChild(content); + } + + function buildTokenMeasure(builder, text, style, startStyle, endStyle) { + var wrapping = builder.cm.options.lineWrapping; + for (var i = 0; i < text.length; ++i) { + var ch = text.charAt(i), start = i == 0; + if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) { + ch = text.slice(i, i + 2); + ++i; + } else if (i && wrapping && spanAffectsWrapping(text, i)) { + builder.pre.appendChild(elt("wbr")); + } + var old = builder.measure[builder.pos]; + var span = builder.measure[builder.pos] = + buildToken(builder, ch, style, + start && startStyle, i == text.length - 1 && endStyle); + if (old) span.leftSide = old.leftSide || old; + // In IE single-space nodes wrap differently than spaces + // embedded in larger text nodes, except when set to + // white-space: normal (issue #1268). + if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) && + i < text.length - 1 && !/\s/.test(text.charAt(i + 1))) + span.style.whiteSpace = "normal"; + builder.pos += ch.length; + } + if (text.length) builder.measuredSomething = true; + } + + function buildTokenSplitSpaces(inner) { + function split(old) { + var out = " "; + for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; + out += " "; + return out; + } + return function(builder, text, style, startStyle, endStyle, title) { + return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle, title); + }; + } + + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.replacedWith; + if (widget) { + if (builder.copyWidgets) widget = widget.cloneNode(true); + builder.pre.appendChild(widget); + if (builder.measure) { + if (size) { + builder.measure[builder.pos] = widget; + } else { + var elt = builder.measure[builder.pos] = zeroWidthElement(builder.cm.display.measure); + if (marker.type != "bookmark" || marker.insertLeft) + builder.pre.insertBefore(elt, widget); + else + builder.pre.appendChild(elt); + } + builder.measuredSomething = true; + } + } + builder.pos += size; + } + + // Outputs a number of spans to make up a line, taking highlighting + // and marked text into account. + function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0; + if (!spans) { + for (var i = 1; i < styles.length; i+=2) + builder.addToken(builder, allText.slice(at, at = styles[i]), styleToClass(styles[i+1])); + return; + } + + var len = allText.length, pos = 0, i = 1, text = "", style; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = title = ""; + collapsed = null; nextChange = Infinity; + var foundBookmark = null; + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker; + if (sp.from <= pos && (sp.to == null || sp.to > pos)) { + if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } + if (m.className) spanStyle += " " + m.className; + if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; + if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; + if (m.title && !title) title = m.title; + if (m.collapsed && (!collapsed || collapsed.marker.size < m.size)) + collapsed = sp; + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from; + } + if (m.type == "bookmark" && sp.from == pos && m.replacedWith) foundBookmark = m; + } + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos, + collapsed.marker, collapsed.from == null); + if (collapsed.to == null) return collapsed.marker.find(); + } + if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark); + } + if (pos >= len) break; + + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text; + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title); + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + spanStartStyle = ""; + } + text = allText.slice(at, at = styles[i++]); + style = styleToClass(styles[i++]); + } + } + } + + // DOCUMENT DATA STRUCTURE + + function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null;} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight); + signalLater(line, "change", line, change); + } + + var from = change.from, to = change.to, text = change.text; + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; + + // First adjust the line structure + if (from.ch == 0 && to.ch == 0 && lastText == "") { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + for (var i = 0, e = text.length - 1, added = []; i < e; ++i) + added.push(new Line(text[i], spansFor(i), estimateHeight)); + update(lastLine, lastLine.text, lastSpans); + if (nlines) doc.remove(from.line, nlines); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); + } else { + for (var added = [], i = 1, e = text.length - 1; i < e; ++i) + added.push(new Line(text[i], spansFor(i), estimateHeight)); + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + doc.insert(from.line + 1, added); + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); + doc.remove(from.line + 1, nlines); + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); + for (var i = 1, e = text.length - 1, added = []; i < e; ++i) + added.push(new Line(text[i], spansFor(i), estimateHeight)); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1); + doc.insert(from.line + 1, added); + } + + signalLater(doc, "change", doc, change); + setSelection(doc, selAfter.anchor, selAfter.head, null, true); + } + + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + for (var i = 0, e = lines.length, height = 0; i < e; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length; }, + removeInner: function(at, n) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + cleanUpLine(line); + signalLater(line, "delete"); + } + this.lines.splice(at, n); + }, + collapse: function(lines) { + lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); + }, + insertInner: function(at, lines, height) { + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; + }, + iterN: function(at, n, op) { + for (var e = at + n; at < e; ++at) + if (op(this.lines[at])) return true; + } + }; + + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0, e = children.length; i < e; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + + BranchChunk.prototype = { + chunkSize: function() { return this.size; }, + removeInner: function(at, n) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.removeInner(at, rm); + this.height -= oldHeight - child.height; + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) break; + at = 0; + } else at -= sz; + } + if (this.size - n < 25) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + collapse: function(lines) { + for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines); + }, + insertInner: function(at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0, e = this.children.length; i < e; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertInner(at, lines, height); + if (child.lines && child.lines.length > 50) { + while (child.lines.length > 50) { + var spilled = child.lines.splice(child.lines.length - 25, 25); + var newleaf = new LeafChunk(spilled); + child.height -= newleaf.height; + this.children.splice(i + 1, 0, newleaf); + newleaf.parent = this; + } + this.maybeSpill(); + } + break; + } + at -= sz; + } + }, + maybeSpill: function() { + if (this.children.length <= 10) return; + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10); + me.parent.maybeSpill(); + }, + iterN: function(at, n, op) { + for (var i = 0, e = this.children.length; i < e; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) return true; + if ((n -= used) == 0) break; + at = 0; + } else at -= sz; + } + } + }; + + var nextDocId = 0; + var Doc = CodeMirror.Doc = function(text, mode, firstLine) { + if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); + if (firstLine == null) firstLine = 0; + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); + this.first = firstLine; + this.scrollTop = this.scrollLeft = 0; + this.cantEdit = false; + this.history = makeHistory(); + this.cleanGeneration = 1; + this.frontier = firstLine; + var start = Pos(firstLine, 0); + this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null}; + this.id = ++nextDocId; + this.modeOption = mode; + + if (typeof text == "string") text = splitLines(text); + updateDoc(this, {from: start, to: start, text: text}, null, {head: start, anchor: start}); + }; + + Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + iter: function(from, to, op) { + if (op) this.iterN(from - this.first, to - from, op); + else this.iterN(this.first, this.first + this.size, from); + }, + + insert: function(at, lines) { + var height = 0; + for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; + this.insertInner(at - this.first, lines, height); + }, + remove: function(at, n) { this.removeInner(at - this.first, n); }, + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + setValue: function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1; + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: splitLines(code), origin: "setValue"}, + {head: top, anchor: top}, true); + }, + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from); + to = to ? clipPos(this, to) : from; + replaceRange(this, code, from, to, origin); + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, + setLine: function(line, text) { + if (isLine(this, line)) + replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line))); + }, + removeLine: function(line) { + if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line))); + else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0))); + }, + + getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, + getLineNumber: function(line) {return lineNo(line);}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") line = getLine(this, line); + return visualLine(this, line); + }, + + lineCount: function() {return this.size;}, + firstLine: function() {return this.first;}, + lastLine: function() {return this.first + this.size - 1;}, + + clipPos: function(pos) {return clipPos(this, pos);}, + + getCursor: function(start) { + var sel = this.sel, pos; + if (start == null || start == "head") pos = sel.head; + else if (start == "anchor") pos = sel.anchor; + else if (start == "end" || start === false) pos = sel.to; + else pos = sel.from; + return copyPos(pos); + }, + somethingSelected: function() {return !posEq(this.sel.head, this.sel.anchor);}, + + setCursor: docOperation(function(line, ch, extend) { + var pos = clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line); + if (extend) extendSelection(this, pos); + else setSelection(this, pos, pos); + }), + setSelection: docOperation(function(anchor, head) { + setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor)); + }), + extendSelection: docOperation(function(from, to) { + extendSelection(this, clipPos(this, from), to && clipPos(this, to)); + }), + + getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);}, + replaceSelection: function(code, collapse, origin) { + makeChange(this, {from: this.sel.from, to: this.sel.to, text: splitLines(code), origin: origin}, collapse || "around"); + }, + undo: docOperation(function() {makeChangeFromHistory(this, "undo");}), + redo: docOperation(function() {makeChangeFromHistory(this, "redo");}), + + setExtending: function(val) {this.sel.extend = val;}, + + historySize: function() { + var hist = this.history; + return {undo: hist.done.length, redo: hist.undone.length}; + }, + clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(); + }, + changeGeneration: function() { + this.history.lastOp = this.history.lastOrigin = null; + return this.history.generation; + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration); + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)}; + }, + setHistory: function(histData) { + var hist = this.history = makeHistory(this.history.maxGeneration); + hist.done = histData.done.slice(0); + hist.undone = histData.undone.slice(0); + }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft}; + pos = clipPos(this, pos); + return markText(this, pos, pos, realOpts, "bookmark"); + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos); + var markers = [], spans = getLine(this, pos.line).markedSpans; + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker.parent || span.marker); + } + return markers; + }, + getAllMarks: function() { + var markers = []; + this.iter(function(line) { + var sps = line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) + if (sps[i].from != null) markers.push(sps[i].marker); + }); + return markers; + }, + + posFromIndex: function(off) { + var ch, lineNo = this.first; + this.iter(function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos(this, Pos(lineNo, ch)); + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords); + var index = coords.ch; + if (coords.line < this.first || coords.ch < 0) return 0; + this.iter(this.first, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first); + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; + doc.sel = {from: this.sel.from, to: this.sel.to, head: this.sel.head, anchor: this.sel.anchor, + shift: this.sel.shift, extend: false, goalColumn: this.sel.goalColumn}; + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth; + doc.setHistory(this.getHistory()); + } + return doc; + }, + + linkedDoc: function(options) { + if (!options) options = {}; + var from = this.first, to = this.first + this.size; + if (options.from != null && options.from > from) from = options.from; + if (options.to != null && options.to < to) to = options.to; + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from); + if (options.sharedHist) copy.history = this.history; + (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; + return copy; + }, + unlinkDoc: function(other) { + if (other instanceof CodeMirror) other = other.doc; + if (this.linked) for (var i = 0; i < this.linked.length; ++i) { + var link = this.linked[i]; + if (link.doc != other) continue; + this.linked.splice(i, 1); + other.unlinkDoc(this); + break; + } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id]; + linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); + other.history = makeHistory(); + other.history.done = copyHistoryArray(this.history.done, splitIds); + other.history.undone = copyHistoryArray(this.history.undone, splitIds); + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f);}, + + getMode: function() {return this.mode;}, + getEditor: function() {return this.cm;} + }); + + Doc.prototype.eachLine = Doc.prototype.iter; + + // The Doc methods that should be available on CodeMirror instances + var dontDelegate = "iter insert remove copy getEditor".split(" "); + for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments);}; + })(Doc.prototype[prop]); + + eventMixin(Doc); + + function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i]; + if (rel.doc == skip) continue; + var shared = sharedHist && rel.sharedHist; + if (sharedHistOnly && !shared) continue; + f(rel.doc, shared); + propagate(rel.doc, doc, shared); + } + } + propagate(doc, null, true); + } + + function attachDoc(cm, doc) { + if (doc.cm) throw new Error("This document is already in use."); + cm.doc = doc; + doc.cm = cm; + estimateLineHeights(cm); + loadMode(cm); + if (!cm.options.lineWrapping) computeMaxLength(cm); + cm.options.mode = doc.modeOption; + regChange(cm); + } + + // LINE UTILITIES + + function getLine(chunk, n) { + n -= chunk.first; + while (!chunk.lines) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break; } + n -= sz; + } + } + return chunk.lines[n]; + } + + function getBetween(doc, start, end) { + var out = [], n = start.line; + doc.iter(start.line, end.line + 1, function(line) { + var text = line.text; + if (n == end.line) text = text.slice(0, end.ch); + if (n == start.line) text = text.slice(start.ch); + out.push(text); + ++n; + }); + return out; + } + function getLines(doc, from, to) { + var out = []; + doc.iter(from, to, function(line) { out.push(line.text); }); + return out; + } + + function updateLineHeight(line, height) { + var diff = height - line.height; + for (var n = line; n; n = n.parent) n.height += diff; + } + + function lineNo(line) { + if (line.parent == null) return null; + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no + cur.first; + } + + function lineAtHeight(chunk, h) { + var n = chunk.first; + outer: do { + for (var i = 0, e = chunk.children.length; i < e; ++i) { + var child = chunk.children[i], ch = child.height; + if (h < ch) { chunk = child; continue outer; } + h -= ch; + n += child.chunkSize(); + } + return n; + } while (!chunk.lines); + for (var i = 0, e = chunk.lines.length; i < e; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) break; + h -= lh; + } + return n + i; + } + + function heightAtLine(cm, lineObj) { + lineObj = visualLine(cm.doc, lineObj); + + var h = 0, chunk = lineObj.parent; + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i]; + if (line == lineObj) break; + else h += line.height; + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i = 0; i < p.children.length; ++i) { + var cur = p.children[i]; + if (cur == chunk) break; + else h += cur.height; + } + } + return h; + } + + function getOrder(line) { + var order = line.order; + if (order == null) order = line.order = bidiOrdering(line.text); + return order; + } + + // HISTORY + + function makeHistory(startGen) { + return { + // Arrays of history events. Doing something adds an event to + // done and clears undo. Undoing moves events from done to + // undone, redoing moves them in the other direction. + done: [], undone: [], undoDepth: Infinity, + // Used to track when changes can be merged into a single undo + // event + lastTime: 0, lastOp: null, lastOrigin: null, + // Used by the isClean() method + generation: startGen || 1, maxGeneration: startGen || 1 + }; + } + + function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0; + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { + if (line.markedSpans) + (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; + ++n; + }); + } + + function historyChangeFromChange(doc, change) { + var from = { line: change.from.line, ch: change.from.ch }; + var histChange = {from: from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); + linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); + return histChange; + } + + function addToHistory(doc, change, selAfter, opId) { + var hist = doc.history; + hist.undone.length = 0; + var time = +new Date, cur = lst(hist.done); + + if (cur && + (hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*"))) { + // Merge this change into the last event + var last = lst(cur.changes); + if (posEq(change.from, change.to) && posEq(change.from, last.to)) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change); + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)); + } + cur.anchorAfter = selAfter.anchor; cur.headAfter = selAfter.head; + } else { + // Can not be merged, start a new event. + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation, + anchorBefore: doc.sel.anchor, headBefore: doc.sel.head, + anchorAfter: selAfter.anchor, headAfter: selAfter.head}; + hist.done.push(cur); + hist.generation = ++hist.maxGeneration; + while (hist.done.length > hist.undoDepth) + hist.done.shift(); + } + hist.lastTime = time; + hist.lastOp = opId; + hist.lastOrigin = change.origin; + } + + function removeClearedSpans(spans) { + if (!spans) return null; + for (var i = 0, out; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } + else if (out) out.push(spans[i]); + } + return !out ? spans : out.length ? out : null; + } + + function getOldSpans(doc, change) { + var found = change["spans_" + doc.id]; + if (!found) return null; + for (var i = 0, nw = []; i < change.text.length; ++i) + nw.push(removeClearedSpans(found[i])); + return nw; + } + + // Used both to provide a JSON-safe object in .getHistory, and, when + // detaching a document, to split the history in two + function copyHistoryArray(events, newGroup) { + for (var i = 0, copy = []; i < events.length; ++i) { + var event = events[i], changes = event.changes, newChanges = []; + copy.push({changes: newChanges, anchorBefore: event.anchorBefore, headBefore: event.headBefore, + anchorAfter: event.anchorAfter, headAfter: event.headAfter}); + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m; + newChanges.push({from: change.from, to: change.to, text: change.text}); + if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop]; + delete change[prop]; + } + } + } + } + return copy; + } + + // Rebasing/resetting history to deal with externally-sourced changes + + function rebaseHistSel(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff; + } else if (from < pos.line) { + pos.line = from; + pos.ch = 0; + } + } + + // Tries to rebase an array of history events given a change in the + // document. If the change touches the same lines as the event, the + // event, and everything 'behind' it, is discarded. If the change is + // before the event, the event's positions are updated. Uses a + // copy-on-write scheme for the positions, to avoid having to + // reallocate them all on every rebase, but also avoid problems with + // shared position objects being unsafely updated. + function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true; + for (var j = 0; j < sub.changes.length; ++j) { + var cur = sub.changes[j]; + if (!sub.copied) { cur.from = copyPos(cur.from); cur.to = copyPos(cur.to); } + if (to < cur.from.line) { + cur.from.line += diff; + cur.to.line += diff; + } else if (from <= cur.to.line) { + ok = false; + break; + } + } + if (!sub.copied) { + sub.anchorBefore = copyPos(sub.anchorBefore); sub.headBefore = copyPos(sub.headBefore); + sub.anchorAfter = copyPos(sub.anchorAfter); sub.readAfter = copyPos(sub.headAfter); + sub.copied = true; + } + if (!ok) { + array.splice(0, i + 1); + i = 0; + } else { + rebaseHistSel(sub.anchorBefore); rebaseHistSel(sub.headBefore); + rebaseHistSel(sub.anchorAfter); rebaseHistSel(sub.headAfter); + } + } + } + + function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; + rebaseHistArray(hist.done, from, to, diff); + rebaseHistArray(hist.undone, from, to, diff); + } + + // EVENT OPERATORS + + function stopMethod() {e_stop(this);} + // Ensure an event has a stop method. + function addStop(event) { + if (!event.stop) event.stop = stopMethod; + return event; + } + + function e_preventDefault(e) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + } + function e_stopPropagation(e) { + if (e.stopPropagation) e.stopPropagation(); + else e.cancelBubble = true; + } + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; + } + function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} + CodeMirror.e_stop = e_stop; + CodeMirror.e_preventDefault = e_preventDefault; + CodeMirror.e_stopPropagation = e_stopPropagation; + + function e_target(e) {return e.target || e.srcElement;} + function e_button(e) { + var b = e.which; + if (b == null) { + if (e.button & 1) b = 1; + else if (e.button & 2) b = 3; + else if (e.button & 4) b = 2; + } + if (mac && e.ctrlKey && b == 1) b = 3; + return b; + } + + // EVENT HANDLING + + function on(emitter, type, f) { + if (emitter.addEventListener) + emitter.addEventListener(type, f, false); + else if (emitter.attachEvent) + emitter.attachEvent("on" + type, f); + else { + var map = emitter._handlers || (emitter._handlers = {}); + var arr = map[type] || (map[type] = []); + arr.push(f); + } + } + + function off(emitter, type, f) { + if (emitter.removeEventListener) + emitter.removeEventListener(type, f, false); + else if (emitter.detachEvent) + emitter.detachEvent("on" + type, f); + else { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + for (var i = 0; i < arr.length; ++i) + if (arr[i] == f) { arr.splice(i, 1); break; } + } + } + + function signal(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2); + for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); + } + + var delayedCallbacks, delayedCallbackDepth = 0; + function signalLater(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2); + if (!delayedCallbacks) { + ++delayedCallbackDepth; + delayedCallbacks = []; + setTimeout(fireDelayed, 0); + } + function bnd(f) {return function(){f.apply(null, args);};}; + for (var i = 0; i < arr.length; ++i) + delayedCallbacks.push(bnd(arr[i])); + } + + function signalDOMEvent(cm, e, override) { + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore; + } + + function fireDelayed() { + --delayedCallbackDepth; + var delayed = delayedCallbacks; + delayedCallbacks = null; + for (var i = 0; i < delayed.length; ++i) delayed[i](); + } + + function hasHandler(emitter, type) { + var arr = emitter._handlers && emitter._handlers[type]; + return arr && arr.length > 0; + } + + CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal; + + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + + // MISC UTILITIES + + // Number of pixels added to scroller and sizer to hide scrollbar + var scrollerCutOff = 30; + + // Returned or thrown by various protocols to signal 'I'm not + // handling this'. + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; + + function Delayed() {this.id = null;} + Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + function countColumn(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) { + if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); + else ++n; + } + return n; + } + CodeMirror.countColumn = countColumn; + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(lst(spaceStrs) + " "); + return spaceStrs[n]; + } + + function lst(arr) { return arr[arr.length-1]; } + + function selectInput(node) { + if (ios) { // Mobile Safari apparently has a bug where select() is broken. + node.selectionStart = 0; + node.selectionEnd = node.value.length; + } else { + // Suppress mysterious IE10 errors + try { node.select(); } + catch(_e) {} + } + } + + function indexOf(collection, elt) { + if (collection.indexOf) return collection.indexOf(elt); + for (var i = 0, e = collection.length; i < e; ++i) + if (collection[i] == elt) return i; + return -1; + } + + function createObj(base, props) { + function Obj() {} + Obj.prototype = base; + var inst = new Obj(); + if (props) copyObj(props, inst); + return inst; + } + + function copyObj(obj, target) { + if (!target) target = {}; + for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; + return target; + } + + function emptyArray(size) { + for (var a = [], i = 0; i < size; ++i) a.push(undefined); + return a; + } + + function bind(f) { + var args = Array.prototype.slice.call(arguments, 1); + return function(){return f.apply(null, args);}; + } + + var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + function isWordChar(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); + } + + function isEmpty(obj) { + for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; + return true; + } + + var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff]/; + + // DOM UTILITIES + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") setTextContent(e, content); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + + function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + e.removeChild(e.firstChild); + return e; + } + + function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e); + } + + function setTextContent(e, str) { + if (ie_lt9) { + e.innerHTML = ""; + e.appendChild(document.createTextNode(str)); + } else e.textContent = str; + } + + function getRect(node) { + return node.getBoundingClientRect(); + } + CodeMirror.replaceGetRect = function(f) { getRect = f; }; + + // FEATURE DETECTION + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie_lt9) return false; + var div = elt('div'); + return "draggable" in div || "dragDrop" in div; + }(); + + // For a reason I have yet to figure out, some browsers disallow + // word wrapping between certain characters *only* if a new inline + // element is started between them. This makes it hard to reliably + // measure the position of things, since that requires inserting an + // extra span. This terribly fragile set of tests matches the + // character combinations that suffer from this phenomenon on the + // various browsers. + function spanAffectsWrapping() { return false; } + if (gecko) // Only for "$'" + spanAffectsWrapping = function(str, i) { + return str.charCodeAt(i - 1) == 36 && str.charCodeAt(i) == 39; + }; + else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent)) + spanAffectsWrapping = function(str, i) { + return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1)); + }; + else if (webkit && !/Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent)) + spanAffectsWrapping = function(str, i) { + if (i > 1 && str.charCodeAt(i - 1) == 45) { + if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true; + if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false; + } + return /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1)); + }; + + var knownScrollbarWidth; + function scrollbarWidth(measure) { + if (knownScrollbarWidth != null) return knownScrollbarWidth; + var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll"); + removeChildrenAndAdd(measure, test); + if (test.offsetWidth) + knownScrollbarWidth = test.offsetHeight - test.clientHeight; + return knownScrollbarWidth || 0; + } + + var zwspSupported; + function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b"); + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); + if (measure.firstChild.offsetHeight != 0) + zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8; + } + if (zwspSupported) return elt("span", "\u200b"); + else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) nl = string.length; + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } + } + return result; + } : function(string){return string.split(/\r\n?|\n/);}; + CodeMirror.splitLines = splitLines; + + var hasSelection = window.getSelection ? function(te) { + try { return te.selectionStart != te.selectionEnd; } + catch(e) { return false; } + } : function(te) { + try {var range = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range || range.parentElement() != te) return false; + return range.compareEndPoints("StartToEnd", range) != 0; + }; + + var hasCopyEvent = (function() { + var e = elt("div"); + if ("oncopy" in e) return true; + e.setAttribute("oncopy", "return;"); + return typeof e.oncopy == 'function'; + })(); + + // KEY NAMING + + var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", + 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", + 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; + CodeMirror.keyNames = keyNames; + (function() { + // Number keys + for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); + // Alphabetic keys + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); + // Function keys + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; + })(); + + // BIDI HELPERS + + function iterateBidiSections(order, from, to, f) { + if (!order) return f(from, to, "ltr"); + var found = false; + for (var i = 0; i < order.length; ++i) { + var part = order[i]; + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); + found = true; + } + } + if (!found) f(from, to, "ltr"); + } + + function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } + function bidiRight(part) { return part.level % 2 ? part.from : part.to; } + + function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } + function lineRight(line) { + var order = getOrder(line); + if (!order) return line.text.length; + return bidiRight(lst(order)); + } + + function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLine(cm.doc, line); + if (visual != line) lineN = lineNo(visual); + var order = getOrder(visual); + var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); + return Pos(lineN, ch); + } + function lineEnd(cm, lineN) { + var merged, line; + while (merged = collapsedSpanAtEnd(line = getLine(cm.doc, lineN))) + lineN = merged.find().to.line; + var order = getOrder(line); + var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); + return Pos(lineN, ch); + } + + function compareBidiLevel(order, a, b) { + var linedir = order[0].level; + if (a == linedir) return true; + if (b == linedir) return false; + return a < b; + } + var bidiOther; + function getBidiPartAt(order, pos) { + for (var i = 0, found; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < pos && cur.to > pos) { bidiOther = null; return i; } + if (cur.from == pos || cur.to == pos) { + if (found == null) { + found = i; + } else if (compareBidiLevel(order, cur.level, order[found].level)) { + bidiOther = found; + return i; + } else { + bidiOther = i; + return found; + } + } + } + bidiOther = null; + return found; + } + + function moveInLine(line, pos, dir, byUnit) { + if (!byUnit) return pos + dir; + do pos += dir; + while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); + return pos; + } + + // This is somewhat involved. It is needed in order to move + // 'visually' through bi-directional text -- i.e., pressing left + // should make the cursor go left, even when in RTL text. The + // tricky part is the 'jumps', where RTL and LTR text touch each + // other. This often requires the cursor offset to move more than + // one unit, in order to visually move one unit. + function moveVisually(line, start, dir, byUnit) { + var bidi = getOrder(line); + if (!bidi) return moveLogically(line, start, dir, byUnit); + var pos = getBidiPartAt(bidi, start), part = bidi[pos]; + var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); + + for (;;) { + if (target > part.from && target < part.to) return target; + if (target == part.from || target == part.to) { + if (getBidiPartAt(bidi, target) == pos) return target; + part = bidi[pos += dir]; + return (dir > 0) == part.level % 2 ? part.to : part.from; + } else { + part = bidi[pos += dir]; + if (!part) return null; + if ((dir > 0) == part.level % 2) + target = moveInLine(line, part.to, -1, byUnit); + else + target = moveInLine(line, part.from, 1, byUnit); + } + } + } + + function moveLogically(line, start, dir, byUnit) { + var target = start + dir; + if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir; + return target < 0 || target > line.text.length ? null : target; + } + + // Bidirectional ordering algorithm + // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm + // that this (partially) implements. + + // One-char codes used for character types: + // L (L): Left-to-Right + // R (R): Right-to-Left + // r (AL): Right-to-Left Arabic + // 1 (EN): European Number + // + (ES): European Number Separator + // % (ET): European Number Terminator + // n (AN): Arabic Number + // , (CS): Common Number Separator + // m (NSM): Non-Spacing Mark + // b (BN): Boundary Neutral + // s (B): Paragraph Separator + // t (S): Segment Separator + // w (WS): Whitespace + // N (ON): Other Neutrals + + // Returns null if characters are ordered as they appear + // (left-to-right), or an array of sections ({from, to, level} + // objects) in the order in which they occur visually. + var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL"; + // Character types for codepoints 0x600 to 0x6ff + var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr"; + function charType(code) { + if (code <= 0xff) return lowTypes.charAt(code); + else if (0x590 <= code && code <= 0x5f4) return "R"; + else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600); + else if (0x700 <= code && code <= 0x8ac) return "r"; + else return "L"; + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; + // Browsers seem to always treat the boundaries of block elements as being L. + var outerType = "L"; + + return function(str) { + if (!bidiRE.test(str)) return false; + var len = str.length, types = []; + for (var i = 0, type; i < len; ++i) + types.push(type = charType(str.charCodeAt(i))); + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i = 0, prev = outerType; i < len; ++i) { + var type = types[i]; + if (type == "m") types[i] = prev; + else prev = type; + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (type == "1" && cur == "r") types[i] = "n"; + else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i = 1, prev = types[0]; i < len - 1; ++i) { + var type = types[i]; + if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; + else if (type == "," && prev == types[i+1] && + (prev == "1" || prev == "n")) types[i] = prev; + prev = type; + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i = 0; i < len; ++i) { + var type = types[i]; + if (type == ",") types[i] = "N"; + else if (type == "%") { + for (var end = i + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (cur == "L" && type == "1") types[i] = "L"; + else if (isStrong.test(type)) cur = type; + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i = 0; i < len; ++i) { + if (isNeutral.test(types[i])) { + for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} + var before = (i ? types[i-1] : outerType) == "L"; + var after = (end < len - 1 ? types[end] : outerType) == "L"; + var replace = before || after ? "L" : "R"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m; + for (var i = 0; i < len;) { + if (countsAsLeft.test(types[i])) { + var start = i; + for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} + order.push({from: start, to: i, level: 0}); + } else { + var pos = i, at = order.length; + for (++i; i < len && types[i] != "L"; ++i) {} + for (var j = pos; j < i;) { + if (countsAsNum.test(types[j])) { + if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1}); + var nstart = j; + for (++j; j < i && countsAsNum.test(types[j]); ++j) {} + order.splice(at, 0, {from: nstart, to: j, level: 2}); + pos = j; + } else ++j; + } + if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1}); + } + } + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length; + order.unshift({from: 0, to: m[0].length, level: 0}); + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length; + order.push({from: len - m[0].length, to: len, level: 0}); + } + if (order[0].level != lst(order).level) + order.push({from: len, to: len, level: order[0].level}); + + return order; + }; + })(); + + // THE END + + CodeMirror.version = "3.15.0"; + + return CodeMirror; +})(); diff --git a/sources/js/codemirror/mode/sql/sql.js b/sources/js/codemirror/mode/sql/sql.js new file mode 100644 index 0000000..9016cc7 --- /dev/null +++ b/sources/js/codemirror/mode/sql/sql.js @@ -0,0 +1,349 @@ +CodeMirror.defineMode("sql", function(config, parserConfig) { + "use strict"; + + var client = parserConfig.client || {}, + atoms = parserConfig.atoms || {"false": true, "true": true, "null": true}, + builtin = parserConfig.builtin || {}, + keywords = parserConfig.keywords || {}, + operatorChars = parserConfig.operatorChars || /^[*+\-%<>!=&|~^]/, + support = parserConfig.support || {}, + hooks = parserConfig.hooks || {}, + dateSQL = parserConfig.dateSQL || {"date" : true, "time" : true, "timestamp" : true}; + + function tokenBase(stream, state) { + var ch = stream.next(); + + // call hooks from the mime type + if (hooks[ch]) { + var result = hooks[ch](stream, state); + if (result !== false) return result; + } + + if (support.hexNumber == true && + ((ch == "0" && stream.match(/^[xX][0-9a-fA-F]+/)) + || (ch == "x" || ch == "X") && stream.match(/^'[0-9a-fA-F]+'/))) { + // hex + // ref: http://dev.mysql.com/doc/refman/5.5/en/hexadecimal-literals.html + return "number"; + } else if (support.binaryNumber == true && + (((ch == "b" || ch == "B") && stream.match(/^'[01]+'/)) + || (ch == "0" && stream.match(/^b[01]+/)))) { + // bitstring + // ref: http://dev.mysql.com/doc/refman/5.5/en/bit-field-literals.html + return "number"; + } else if (ch.charCodeAt(0) > 47 && ch.charCodeAt(0) < 58) { + // numbers + // ref: http://dev.mysql.com/doc/refman/5.5/en/number-literals.html + stream.match(/^[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/); + support.decimallessFloat == true && stream.eat('.'); + return "number"; + } else if (ch == "?" && (stream.eatSpace() || stream.eol() || stream.eat(";"))) { + // placeholders + return "variable-3"; + } else if (ch == "'" || (ch == '"' && support.doubleQuote)) { + // strings + // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html + state.tokenize = tokenLiteral(ch); + return state.tokenize(stream, state); + } else if ((((support.nCharCast == true && (ch == "n" || ch == "N")) + || (support.charsetCast == true && ch == "_" && stream.match(/[a-z][a-z0-9]*/i))) + && (stream.peek() == "'" || stream.peek() == '"'))) { + // charset casting: _utf8'str', N'str', n'str' + // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html + return "keyword"; + } else if (/^[\(\),\;\[\]]/.test(ch)) { + // no highlightning + return null; + } else if (support.commentSlashSlash && ch == "/" && stream.eat("/")) { + // 1-line comment + stream.skipToEnd(); + return "comment"; + } else if ((support.commentHash && ch == "#") + || (ch == "-" && stream.eat("-") && (!support.commentSpaceRequired || stream.eat(" ")))) { + // 1-line comments + // ref: https://kb.askmonty.org/en/comment-syntax/ + stream.skipToEnd(); + return "comment"; + } else if (ch == "/" && stream.eat("*")) { + // multi-line comments + // ref: https://kb.askmonty.org/en/comment-syntax/ + state.tokenize = tokenComment; + return state.tokenize(stream, state); + } else if (ch == ".") { + // .1 for 0.1 + if (support.zerolessFloat == true && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) { + return "number"; + } + // .table_name (ODBC) + // // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html + if (support.ODBCdotTable == true && stream.match(/^[a-zA-Z_]+/)) { + return "variable-2"; + } + } else if (operatorChars.test(ch)) { + // operators + stream.eatWhile(operatorChars); + return null; + } else if (ch == '{' && + (stream.match(/^( )*(d|D|t|T|ts|TS)( )*'[^']*'( )*}/) || stream.match(/^( )*(d|D|t|T|ts|TS)( )*"[^"]*"( )*}/))) { + // dates (weird ODBC syntax) + // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html + return "number"; + } else { + stream.eatWhile(/^[_\w\d]/); + var word = stream.current().toLowerCase(); + // dates (standard SQL syntax) + // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html + if (dateSQL.hasOwnProperty(word) && (stream.match(/^( )+'[^']*'/) || stream.match(/^( )+"[^"]*"/))) + return "number"; + if (atoms.hasOwnProperty(word)) return "atom"; + if (builtin.hasOwnProperty(word)) return "builtin"; + if (keywords.hasOwnProperty(word)) return "keyword"; + if (client.hasOwnProperty(word)) return "string-2"; + return null; + } + } + + // 'string', with char specified in quote escaped by '\' + function tokenLiteral(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && ch == "\\"; + } + return "string"; + }; + } + function tokenComment(stream, state) { + while (true) { + if (stream.skipTo("*")) { + stream.next(); + if (stream.eat("/")) { + state.tokenize = tokenBase; + break; + } + } else { + stream.skipToEnd(); + break; + } + } + return "comment"; + } + + function pushContext(stream, state, type) { + state.context = { + prev: state.context, + indent: stream.indentation(), + col: stream.column(), + type: type + }; + } + + function popContext(state) { + state.indent = state.context.indent; + state.context = state.context.prev; + } + + return { + startState: function() { + return {tokenize: tokenBase, context: null}; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (state.context && state.context.align == null) + state.context.align = false; + } + if (stream.eatSpace()) return null; + + var style = state.tokenize(stream, state); + if (style == "comment") return style; + + if (state.context && state.context.align == null) + state.context.align = true; + + var tok = stream.current(); + if (tok == "(") + pushContext(stream, state, ")"); + else if (tok == "[") + pushContext(stream, state, "]"); + else if (state.context && state.context.type == tok) + popContext(state); + return style; + }, + + indent: function(state, textAfter) { + var cx = state.context; + if (!cx) return CodeMirror.Pass; + if (cx.align) return cx.col + (textAfter.charAt(0) == cx.type ? 0 : 1); + else return cx.indent + config.indentUnit; + } + }; +}); + +(function() { + "use strict"; + + // `identifier` + function hookIdentifier(stream) { + // MySQL/MariaDB identifiers + // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html + var ch; + while ((ch = stream.next()) != null) { + if (ch == "`" && !stream.eat("`")) return "variable-2"; + } + return null; + } + + // variable token + function hookVar(stream) { + // variables + // @@prefix.varName @varName + // varName can be quoted with ` or ' or " + // ref: http://dev.mysql.com/doc/refman/5.5/en/user-variables.html + if (stream.eat("@")) { + stream.match(/^session\./); + stream.match(/^local\./); + stream.match(/^global\./); + } + + if (stream.eat("'")) { + stream.match(/^.*'/); + return "variable-2"; + } else if (stream.eat('"')) { + stream.match(/^.*"/); + return "variable-2"; + } else if (stream.eat("`")) { + stream.match(/^.*`/); + return "variable-2"; + } else if (stream.match(/^[0-9a-zA-Z$\.\_]+/)) { + return "variable-2"; + } + return null; + }; + + // short client keyword token + function hookClient(stream) { + // \N means NULL + // ref: http://dev.mysql.com/doc/refman/5.5/en/null-values.html + if (stream.eat("N")) { + return "atom"; + } + // \g, etc + // ref: http://dev.mysql.com/doc/refman/5.5/en/mysql-commands.html + return stream.match(/^[a-zA-Z.#!?]/) ? "variable-2" : null; + } + + // these keywords are used by all SQL dialects (however, a mode can still overwrite it) + var sqlKeywords = "alter and as asc between by count create delete desc distinct drop from having in insert into is join like not on or order select set table union update values where "; + + // turn a space-separated list into an array + function set(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + // A generic SQL Mode. It's not a standard, it just try to support what is generally supported + CodeMirror.defineMIME("text/x-sql", { + name: "sql", + keywords: set(sqlKeywords + "begin"), + builtin: set("bool boolean bit blob enum long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision real date datetime year unsigned signed decimal numeric"), + atoms: set("false true null unknown"), + operatorChars: /^[*+\-%<>!=]/, + dateSQL: set("date time timestamp"), + support: set("ODBCdotTable doubleQuote binaryNumber hexNumber") + }); + + CodeMirror.defineMIME("text/x-mysql", { + name: "sql", + client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"), + keywords: set(sqlKeywords + "accessible action add after algorithm all analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general global grant grants group groupby_concat handler hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"), + builtin: set("bool boolean bit blob decimal double enum float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"), + atoms: set("false true null unknown"), + operatorChars: /^[*+\-%<>!=&|^]/, + dateSQL: set("date time timestamp"), + support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"), + hooks: { + "@": hookVar, + "`": hookIdentifier, + "\\": hookClient + } + }); + + CodeMirror.defineMIME("text/x-mariadb", { + name: "sql", + client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"), + keywords: set(sqlKeywords + "accessible action add after algorithm all always analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general generated global grant grants group groupby_concat handler hard hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password persistent phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soft soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views virtual warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"), + builtin: set("bool boolean bit blob decimal double enum float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"), + atoms: set("false true null unknown"), + operatorChars: /^[*+\-%<>!=&|^]/, + dateSQL: set("date time timestamp"), + support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"), + hooks: { + "@": hookVar, + "`": hookIdentifier, + "\\": hookClient + } + }); + + // the query language used by Apache Cassandra is called CQL, but this mime type + // is called Cassandra to avoid confusion with Contextual Query Language + CodeMirror.defineMIME("text/x-cassandra", { + name: "sql", + client: { }, + keywords: set("use select from using consistency where limit first reversed first and in insert into values using consistency ttl update set delete truncate begin batch apply create keyspace with columnfamily primary key index on drop alter type add any one quorum all local_quorum each_quorum"), + builtin: set("ascii bigint blob boolean counter decimal double float int text timestamp uuid varchar varint"), + atoms: set("false true"), + operatorChars: /^[<>=]/, + dateSQL: { }, + support: set("commentSlashSlash decimallessFloat"), + hooks: { } + }); + + // this is based on Peter Raganitsch's 'plsql' mode + CodeMirror.defineMIME("text/x-plsql", { + name: "sql", + client: set("appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap"), + keywords: set("abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work"), + functions: set("abs acos add_months ascii asin atan atan2 average bfilename ceil chartorowid chr concat convert cos cosh count decode deref dual dump dup_val_on_index empty error exp false floor found glb greatest hextoraw initcap instr instrb isopen last_day least lenght lenghtb ln lower lpad ltrim lub make_ref max min mod months_between new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null nvl others power rawtohex reftohex round rowcount rowidtochar rpad rtrim sign sin sinh soundex sqlcode sqlerrm sqrt stddev substr substrb sum sysdate tan tanh to_char to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid upper user userenv variance vsize"), + builtin: set("bfile blob character clob dec float int integer mlslabel natural naturaln nchar nclob number numeric nvarchar2 real rowtype signtype smallint string varchar varchar2"), + operatorChars: /^[*+\-%<>!=~]/, + dateSQL: set("date time timestamp"), + support: set("doubleQuote nCharCast zerolessFloat binaryNumber hexNumber") + }); +}()); + +/* + How Properties of Mime Types are used by SQL Mode + ================================================= + + keywords: + A list of keywords you want to be highlighted. + functions: + A list of function names you want to be highlighted. + builtin: + A list of builtin types you want to be highlighted (if you want types to be of class "builtin" instead of "keyword"). + operatorChars: + All characters that must be handled as operators. + client: + Commands parsed and executed by the client (not the server). + support: + A list of supported syntaxes which are not common, but are supported by more than 1 DBMS. + * ODBCdotTable: .tableName + * zerolessFloat: .1 + * doubleQuote + * nCharCast: N'string' + * charsetCast: _utf8'string' + * commentHash: use # char for comments + * commentSlashSlash: use // for comments + * commentSpaceRequired: require a space after -- for comments + atoms: + Keywords that must be highlighted as atoms,. Some DBMS's support more atoms than others: + UNKNOWN, INFINITY, UNDERFLOW, NaN... + dateSQL: + Used for date/time SQL standard syntax, because not all DBMS's support same temporal types. +*/ diff --git a/sources/js/common.js b/sources/js/common.js new file mode 100644 index 0000000..468036d --- /dev/null +++ b/sources/js/common.js @@ -0,0 +1,300 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for communicating with the querywindow + */ +$(function () { + /** + * Event handler for click on the open query window link + * in the top menu of the navigation panel + */ + $('#pma_open_querywindow').click(function (event) { + event.preventDefault(); + PMA_querywindow.focus(); + }); +}); + +/** + * Holds common parameters such as server, db, table, etc + * + * The content for this is normally loaded from Header.class.php or + * Response.class.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 hash obj 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]) { + reload = true; + if (i == 'db' || i == 'table') { + updateNavigation = true; + } + } + params[i] = obj[i]; + } + if (updateNavigation) { + PMA_showCurrentNavigation(); + } + if (reload) { + PMA_querywindow.refresh(); + } + }, + /** + * Retrieves a value given its key + * Returns empty string for undefined values + * + * @param string name The key + * + * @return string + */ + get: function (name) { + return params[name] || ''; + }, + /** + * Saves a single key value pair + * + * @param string name The key + * @param string value The value + * + * @return self For chainability + */ + set: function (name, value) { + var updateNavigation = false; + if (params[name] !== undefined && params[name] !== value) { + PMA_querywindow.refresh(); + if (name == 'db' || name == 'table') { + updateNavigation = true; + } + } + params[name] = value; + if (updateNavigation) { + PMA_showCurrentNavigation(); + } + return this; + }, + /** + * Returns the url query string using the saved parameters + * + * @return string + */ + getUrlQuery: function () { + return $.sprintf( + '?%s&server=%s&db=%s&table=%s', + this.get('common_query'), + 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.class.php or + * Response.class.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 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 string new_db The name of the new database + * + * @return void + */ + openDb: function (new_db) { + PMA_commonParams + .set('db', new_db) + .set('table', ''); + PMA_querywindow.refresh(); + 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 a').attr('href'); + url = url.substring(0, url.indexOf('?')); + } + url += PMA_commonParams.getUrlQuery(); + $('', {href: url}) + .appendTo('body') + .click() + .remove(); + AJAX._callback = callback; + } +}; + +/** + * Common functions used for communicating with the querywindow + */ +var PMA_querywindow = (function ($, window) { + /** + * @var Object querywindow Reference to the window + * object of the querywindow + * @access private + */ + var querywindow = {}; + /** + * @var string queryToLoad Stores the SQL query that is to be displayed + * in the querywindow when it is ready + * @access private + */ + var queryToLoad = ''; + // The returned object is the public part of the module + return { + /** + * Opens the query window + * + * @param mixed url Undefined to open the default page + * String to go to a different + * + * @return void + */ + open: function (url, sql_query) { + if (! url) { + url = 'querywindow.php' + PMA_commonParams.getUrlQuery(); + } + if (sql_query) { + url += '&sql_query=' + encodeURIComponent(sql_query); + } + + if (! querywindow.closed && querywindow.location) { + var href = querywindow.location.href; + if (href != url && + href != PMA_commonParams.get('pma_absolute_uri') + url + ) { + if (PMA_commonParams.get('safari_browser')) { + querywindow.location.href = targeturl; + } else { + querywindow.location.replace(targeturl); + } + querywindow.focus(); + } + } else { + querywindow = window.open( + url + '&init=1', + '', + 'toolbar=0,location=0,directories=0,status=1,' + + 'menubar=0,scrollbars=yes,resizable=yes,' + + 'width=' + PMA_commonParams.get('querywindow_width') + ',' + + 'height=' + PMA_commonParams.get('querywindow_height') + ); + } + if (! querywindow.opener) { + querywindow.opener = window.window; + } + if (window.focus) { + querywindow.focus(); + } + }, + /** + * Opens, if necessary, focuses the query window + * and displays an SQL query. + * + * @param string sql_query The SQL query to display in + * the query window + * + * @return void + */ + focus: function (sql_query) { + if (! querywindow || querywindow.closed || ! querywindow.location) { + // we need first to open the window and cannot pass the query with it + // as we dont know if the query exceeds max url length + queryToLoad = sql_query; + this.open(false, sql_query); + } else { + //var querywindow = querywindow; + var hiddenqueryform = querywindow + .document + .getElementById('hiddenqueryform'); + if (hiddenqueryform.querydisplay_tab != 'sql') { + hiddenqueryform.querydisplay_tab.value = "sql"; + hiddenqueryform.sql_query.value = sql_query; + $(hiddenqueryform).addClass('disableAjax'); + hiddenqueryform.submit(); + querywindow.focus(); + } else { + querywindow.focus(); + } + } + }, + /** + * Refreshes the query window given a url + * + * @param string url Where to go to + * + * @return void + */ + refresh: function (url) { + if (! querywindow.closed && querywindow.location) { + var $form = $(querywindow.document).find('#sqlqueryform'); + if ($form.find('#checkbox_lock:checked').length === 0) { + PMA_querywindow.open(url); + } + } + }, + /** + * Reloads the query window given the details + * of a db, a table and an sql_query + * + * @param string db The name of the database + * @param string table The name of the table + * @param string sql_query The SQL query to be displayed + * + * @return void + */ + reload: function (db, table, sql_query) { + if (! querywindow.closed && querywindow.location) { + var $form = $(querywindow.document).find('#sqlqueryform'); + if ($form.find('#checkbox_lock:checked').length === 0) { + var $hiddenform = $(querywindow.document) + .find('#hiddenqueryform'); + $hiddenform.find('input[name=db]').val(db); + $hiddenform.find('input[name=table]').val(table); + if (sql_query) { + $hiddenform.find('input[name=sql_query]').val(sql_query); + } + $hiddenform.addClass('disableAjax').submit(); + } + } + } + }; +})(jQuery, window); diff --git a/sources/js/config.js b/sources/js/config.js new file mode 100644 index 0000000..9113c85 --- /dev/null +++ b/sources/js/config.js @@ -0,0 +1,798 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functions used in configuration forms and on user preferences pages + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('config.js', function () { + $('input[id], select[id], textarea[id]').unbind('change').unbind('keyup'); + $('input[type=button][name=submit_reset]').unbind('click'); + $('div.tabs_contents').undelegate(); + $('#import_local_storage, #export_local_storage').unbind('click'); + $('form.prefs-form').unbind('change').unbind('submit'); + $('div.click-hide-message').die('click'); + $('#prefs_autoload').find('a').unbind('click'); +}); + +AJAX.registerOnload('config.js', function () { + $('#topmenu2').find('li.active a').attr('rel', 'samepage'); + $('#topmenu2').find('li:not(.active) a').attr('rel', 'newpage'); +}); + +// default values for fields +var defaultValues = {}; + +/** + * Returns field type + * + * @param {Element} field + */ +function getFieldType(field) +{ + 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 ''; +} + +/** + * 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) +{ + field = $(field); + switch (field_type) { + case 'text': + case 'number': + //TODO: replace to .val() + field.attr('value', (value !== undefined ? value : field.attr('defaultValue'))); + break; + case 'checkbox': + //TODO: replace to .prop() + field.attr('checked', (value !== undefined ? value : field.attr('defaultChecked'))); + break; + case 'select': + var options = field.prop('options'); + var i, imax = options.length; + if (value === undefined) { + for (i = 0; i < imax; i++) { + options[i].selected = options[i].defaultSelected; + } + } else { + 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) +{ + 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, imax = options.length, 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, 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) +{ + 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 : $.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] = []; + } + validate[id].push([type, params, onKeyUp]); +} + +/** + * Returns valdiation functions associated with form field + * + * @param {String} field_id form field id + * @param {boolean} onKeyUpOnly see validateField + * @type Array + * @return array of [function, paramseters to be passed to function] + */ +function getFieldValidators(field_id, onKeyUpOnly) +{ + // look for field bound validator + var name = 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 + * display_input() in FormDisplay.tpl.php! + * + * @param {Object} error_list list of errors in the form {field id: error array} + */ +function displayErrors(error_list) +{ + 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, function (item) { + return item !== ''; + }); + + // 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 fieldset and puts errors in 'errors' object + * + * @param {Element} fieldset + * @param {boolean} isKeyUp + * @param {Object} errors + */ +function validate_fieldset(fieldset, isKeyUp, errors) +{ + 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, result; + 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) +{ + field = $(field); + var errors = {}; + validate_field(field, isKeyUp, errors); + validate_fieldset(field.closest('fieldset'), isKeyUp, errors); + displayErrors(errors); +} + +/** + * Marks field depending on its value (system default or custom) + * + * @param {Element} field + */ +function markField(field) +{ + 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'); +} + +/** + * 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'](); +} + +AJAX.registerOnload('config.js', function () { + // register validators and mark custom values + var elements = $('input[id], select[id], textarea[id]'); + $('input[id], select[id], textarea[id]').each(function () { + markField(this); + var el = $(this); + el.bind('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').each(function () { + validate_fieldset(this, false, errors); + }); + + displayErrors(errors); + } else if (check_page_refresh) { + check_page_refresh.val('1'); + } +}); + +// +// END: Form validation and field operations +// ------------------------------------------------------------------ + +// ------------------------------------------------------------------ +// Tabbed forms +// + +/** + * Sets active tab + * + * @param {String} tab_id + */ +function setTab(tab_id) +{ + $('ul.tabs li').removeClass('active').find('a[href=#' + tab_id + ']').parent().addClass('active'); + $('div.tabs_contents fieldset').hide().filter('#' + tab_id).show(); + location.hash = 'tab_' + tab_id; + $('form.config-form input[name=tab_hash]').val(location.hash); +} + +AJAX.registerOnload('config.js', function () { + var tabs = $('ul.tabs'); + if (!tabs.length) { + return; + } + // add tabs events and activate one tab (the first one or indicated by location hash) + tabs.find('a') + .click(function (e) { + e.preventDefault(); + setTab($(this).attr('href').substr(1)); + }) + .filter(':first') + .parent() + .addClass('active'); + $('div.tabs_contents fieldset').hide().filter(':first').show(); + + // 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 (location.hash.match(/^#tab_.+/) && $('#' + location.hash.substr(5)).length) { + setTab(location.hash.substr(5)); + } + } + }; + tab_check_fnc(); + setInterval(tab_check_fnc, 200); +}); + +// +// END: Tabbed forms +// ------------------------------------------------------------------ + +// ------------------------------------------------------------------ +// Form reset buttons +// + +AJAX.registerOnload('config.js', function () { + $('input[type=button][name=submit_reset]').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])); + } + }); +}); + +// +// 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]); +} + +AJAX.registerOnload('config.js', function () { + $('div.tabs_contents') + .delegate('.restore-default, .set-value', 'mouseenter', function () { + $(this).css('opacity', 1); + }) + .delegate('.restore-default, .set-value', 'mouseleave', function () { + $(this).css('opacity', 0.25); + }) + .delegate('.restore-default, .set-value', 'click', 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}); +}); + +// +// 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 = window.localStorage || false; + 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']); + } + }); + + $('div.click-hide-message').live('click', 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: form.find('input[name=server]').val(), + token: form.find('input[name=token]').val(), + submit_get_json: true + }, + success: function (data) { + if (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 div.localStorage-exists').html(msg); +} + +/** + * Prepares message which informs that localStorage preferences are available and can be imported + */ +function offerPrefsAutoimport() +{ + var has_config = (window.localStorage || false) && (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', { + token: cnt.find('input[name=token]').val(), + prefs_autoload: 'hide' + }); + return; + } + cnt.find('input[name=json]').val(window.localStorage['config']); + cnt.find('form').submit(); + }); + cnt.show(); +} + +// +// END: User preferences import/export +// ------------------------------------------------------------------ diff --git a/sources/js/cross_framing_protection.js b/sources/js/cross_framing_protection.js new file mode 100644 index 0000000..5952ab7 --- /dev/null +++ b/sources/js/cross_framing_protection.js @@ -0,0 +1,9 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Conditionally included if framing is not allowed + */ +if (self == top) { + document.documentElement.style.display = 'block'; +} else { + top.location = self.location; +} diff --git a/sources/js/db_operations.js b/sources/js/db_operations.js new file mode 100644 index 0000000..763040b --- /dev/null +++ b/sources/js/db_operations.js @@ -0,0 +1,114 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview function used in server privilege pages + * @name Database Operations + * + * @requires jQuery + * @requires jQueryUI + * @requires js/functions.js + * + */ + +/** + * Ajax event handlers here for db_operations.php + * + * Actions Ajaxified here: + * Rename Database + * Copy Database + * Change charset + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('db_operations.js', function () { + $("#rename_db_form.ajax").die('submit'); + $("#copy_db_form.ajax").die('submit'); + $("#change_db_charset_form.ajax").die('submit'); +}); + +AJAX.registerOnload('db_operations.js', function () { + + /** + * Ajax event handlers for 'Rename Database' + */ + $("#rename_db_form.ajax").live('submit', function (event) { + event.preventDefault(); + + var $form = $(this); + + var question = escapeHtml('CREATE DATABASE ' + $('#new_db_name').val() + ' / DROP DATABASE ' + PMA_commonParams.get('db')); + + PMA_prepareForAjaxRequest($form); + + $form.PMA_confirm(question, $form.attr('action'), function (url) { + PMA_ajaxShowMessage(PMA_messages.strRenamingDatabases, false); + $.get(url, $("#rename_db_form").serialize() + '&is_js_confirmed=1', function (data) { + if (data.success === true) { + PMA_ajaxShowMessage(data.message); + PMA_commonParams.set('db', data.newname); + + PMA_reloadNavigation(function () { + $('#pma_navigation_tree') + .find("a:not('.expander')") + .each(function (index) { + var $thisAnchor = $(this); + if ($thisAnchor.text() == data.newname) { + // simulate a click on the new db name + // in navigation + $thisAnchor.trigger('click'); + } + }); + }); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.get() + }); + }); // end Rename Database + + /** + * Ajax Event Handler for 'Copy Database' + */ + $("#copy_db_form.ajax").live('submit', function (event) { + event.preventDefault(); + PMA_ajaxShowMessage(PMA_messages.strCopyingDatabase, false); + var $form = $(this); + PMA_prepareForAjaxRequest($form); + $.get($form.attr('action'), $form.serialize(), function (data) { + // use messages that stay on screen + $('div.success, div.error').fadeOut(); + if (data.success === true) { + if ($("#checkbox_switch").is(":checked")) { + PMA_commonParams.set('db', data.newname); + PMA_commonActions.refreshMain(false, function () { + PMA_ajaxShowMessage(data.message); + }); + } else { + PMA_commonParams.set('db', data.db); + PMA_ajaxShowMessage(data.message); + } + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.get + }); // end copy database + + /** + * Ajax Event handler for 'Change Charset' of the database + */ + $("#change_db_charset_form.ajax").live('submit', function (event) { + event.preventDefault(); + var $form = $(this); + PMA_prepareForAjaxRequest($form); + PMA_ajaxShowMessage(PMA_messages.strChangingCharset); + $.get($form.attr('action'), $form.serialize() + "&submitcollation=1", function (data) { + if (data.success === true) { + PMA_ajaxShowMessage(data.message); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.get() + }); // end change charset +}); diff --git a/sources/js/db_search.js b/sources/js/db_search.js new file mode 100644 index 0000000..6518aa0 --- /dev/null +++ b/sources/js/db_search.js @@ -0,0 +1,228 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * JavaScript functions used on Database Search page + * + * @requires jQuery + * @requires js/functions.js + * + * @package PhpMyAdmin + */ + +/** + * AJAX script for the Database Search page. + * + * Actions ajaxified here: + * Retrieve result of SQL query + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('db_search.js', function () { + $('#buttonGo').unbind('click'); + $('#togglesearchresultlink').unbind('click'); + $("#togglequerybox").unbind('click'); + $('#togglesearchformlink').unbind('click'); + $("#db_search_form.ajax").die('submit'); +}); + +/** + * Loads the database search results + * + * @param result_path Url of the page to load + * @param table_name Name of table to browse + * + * @return nothing + */ +function loadResult(result_path, table_name, link) +{ + $(function () { + /** Hides the results shown by the delete criteria */ + var $msg = PMA_ajaxShowMessage(PMA_messages.strBrowsing, false); + $('#sqlqueryform').hide(); + $('#togglequerybox').hide(); + /** Load the browse results to the page */ + $("#table-info").show(); + $('#table-link').attr({"href" : 'sql.php?' + link }).text(table_name); + var url = result_path + " #sqlqueryresults"; + $('#browse-results').load(url, null, function () { + $('html, body') + .animate({ + scrollTop: $("#browse-results").offset().top + }, 1000); + PMA_ajaxRemoveMessage($msg); + PMA_makegrid($('#table_results')[0], true, true, true, true); + }).show(); + }); +} + +/** + * Delete the selected search results + * + * @param result_path Url of the page to load + * @param msg Text for the confirmation dialog + * + * @return nothing + */ +function deleteResult(result_path, msg) +{ + $(function () { + /** Hides the results shown by the browse criteria */ + $("#table-info").hide(); + $('#browse-results').hide(); + $('#sqlqueryform').hide(); + $('#togglequerybox').hide(); + /** Conformation message for deletion */ + if (confirm(msg)) { + var $msg = PMA_ajaxShowMessage(PMA_messages.strDeleting, false); + /** Load the deleted option to the page*/ + $('#sqlqueryform').html(''); + var url = result_path + " #result_query, #sqlqueryform"; + $('#browse-results').load(url, function () { + /** Refresh the search results after the deletion */ + document.getElementById('buttonGo').click(); + $('#togglequerybox').html(PMA_messages.strHideQueryBox); + /** Show the results of the deletion option */ + $('#browse-results').show(); + $('#sqlqueryform').show(); + $('#togglequerybox').show(); + $('html, body') + .animate({ + scrollTop: $("#browse-results").offset().top + }, 1000); + PMA_ajaxRemoveMessage($msg); + }); + } + }); +} + +AJAX.registerOnload('db_search.js', function () { + /** Hide the table link in the initial search result */ + var icon = PMA_getImage('s_tbl.png', '', {'id': 'table-image'}).toString(); + $("#table-info").prepend(icon).hide(); + + /** Hide the browse and deleted results in the new search criteria */ + $('#buttonGo').click(function () { + $("#table-info").hide(); + $('#browse-results').hide(); + $('#sqlqueryform').hide(); + $('#togglequerybox').hide(); + }); + /** + * Prepare a div containing a link for toggle the search results + */ + $('
      ') + .insertAfter('#searchresults') + /** don't show it until we have results on-screen */ + .hide(); + + $('
      ').insertAfter("#togglesearchresultsdiv").show(); + /** + * Changing the displayed text according to + * the hide/show criteria in search result forms + */ + $('#togglesearchresultlink') + .html(PMA_messages.strHideSearchResults) + .bind('click', function () { + var $link = $(this); + $('#searchresults').slideToggle(); + if ($link.text() == PMA_messages.strHideSearchResults) { + $link.text(PMA_messages.strShowSearchResults); + } else { + $link.text(PMA_messages.strHideSearchResults); + } + /** avoid default click action */ + return false; + }); + + /** + * Prepare a div containing a link for toggle the search form, + * otherwise it's incorrectly displayed after a couple of clicks + */ + $('
      ') + .insertAfter('#db_search_form') + .hide(); // don't show it until we have results on-screen + + /** + * Changing the displayed text according to + * the hide/show criteria in search form + */ + $("#togglequerybox") + .hide() + .bind('click', function () { + var $link = $(this); + $('#sqlqueryform').slideToggle("medium"); + if ($link.text() == PMA_messages.strHideQueryBox) { + $link.text(PMA_messages.strShowQueryBox); + } else { + $link.text(PMA_messages.strHideQueryBox); + } + /** avoid default click action */ + return false; + }); + + /** don't show it until we have results on-screen */ + + /** + * Changing the displayed text according to + * the hide/show criteria in search criteria form + */ + $('#togglesearchformlink') + .html(PMA_messages.strShowSearchCriteria) + .bind('click', function () { + var $link = $(this); + $('#db_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; + }); + /** + * Ajax Event handler for retrieving the result of an SQL Query + */ + $("#db_search_form.ajax").live('submit', function (event) { + event.preventDefault(); + + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strSearching, false); + // jQuery object to reuse + var $form = $(this); + + PMA_prepareForAjaxRequest($form); + + var url = $form.serialize() + "&submit_search=" + $("#buttonGo").val(); + $.post($form.attr('action'), url, function (data) { + if (data.success === true) { + // found results + $("#searchresults").html(data.message); + + $('#togglesearchresultlink') + // always start with the Show message + .text(PMA_messages.strHideSearchResults); + $('#togglesearchresultsdiv') + // now it's time to show the div containing the link + .show(); + $('#searchresults').show(); + + + $('#db_search_form') + // workaround for Chrome problem (bug #3168569) + .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(); + } else { + // error message (zero rows) + $("#sqlqueryresults").html(data.error); + } + + PMA_ajaxRemoveMessage($msgbox); + }); + }); +}); // end $() diff --git a/sources/js/db_structure.js b/sources/js/db_structure.js new file mode 100644 index 0000000..63d6f43 --- /dev/null +++ b/sources/js/db_structure.js @@ -0,0 +1,385 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used on the database structure page + * @name Database Structure + * + * @requires jQuery + * @requires jQueryUI + * @required js/functions.js + */ + +/** + * AJAX scripts for db_structure.php + * + * Actions ajaxified here: + * Drop Database + * Truncate Table + * Drop Table + * + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('db_structure.js', function () { + $("span.fkc_switch").unbind('click'); + $('#fkc_checkbox').unbind('change'); + $("a.truncate_table_anchor.ajax").die('click'); + $("a.drop_table_anchor.ajax").die('click'); + $('a.drop_tracking_anchor.ajax').die('click'); + $('#real_end_input').die('click'); +}); + +/** + * Adjust number of rows and total size in the summary + * when truncating, creating, dropping or inserting into a table + */ +function PMA_adjustTotals() { + var byteUnits = new Array( + PMA_messages.strB, + PMA_messages.strKiB, + PMA_messages.strMiB, + PMA_messages.strGiB, + PMA_messages.strTiB, + PMA_messages.strPiB, + PMA_messages.strEiB + ); + /** + * @var $allTr jQuery object that references all the rows in the list of tables + */ + var $allTr = $("#tablesForm table.data tbody:first tr"); + // New summary values for the table + var tableSum = $allTr.size(); + var rowsSum = 0; + var sizeSum = 0; + var overheadSum = 0; + var rowSumApproximated = false; + + $allTr.each(function () { + var $this = $(this); + var i, tmpVal; + // Get the number of rows for this SQL table + var strRows = $this.find('.tbl_rows').text(); + // If the value is approximated + if (strRows.indexOf('~') === 0) { + rowSumApproximated = true; + // The approximated value contains a preceding ~ and a following 2 (Eg 100 --> ~1002) + strRows = strRows.substring(1, strRows.length - 1); + } + strRows = strRows.replace(/[,.]/g, ''); + var intRow = parseInt(strRows, 10); + if (! isNaN(intRow)) { + rowsSum += intRow; + } + // Extract the size and overhead + var valSize = 0; + var valOverhead = 0; + var strSize = $.trim($this.find('.tbl_size span:not(.unit)').text()); + var strSizeUnit = $.trim($this.find('.tbl_size span.unit').text()); + var strOverhead = $.trim($this.find('.tbl_overhead span:not(.unit)').text()); + var strOverheadUnit = $.trim($this.find('.tbl_overhead span.unit').text()); + // Given a value and a unit, such as 100 and KiB, for the table size + // and overhead calculate their numeric values in bytes, such as 102400 + for (i = 0; i < byteUnits.length; i++) { + if (strSizeUnit == byteUnits[i]) { + tmpVal = parseFloat(strSize); + valSize = tmpVal * Math.pow(1024, i); + break; + } + } + for (i = 0; i < byteUnits.length; i++) { + if (strOverheadUnit == byteUnits[i]) { + tmpVal = parseFloat(strOverhead); + valOverhead = tmpVal * Math.pow(1024, i); + break; + } + } + sizeSum += valSize; + overheadSum += valOverhead; + }); + // Add some commas for readablility: + // 1000000 becomes 1,000,000 + var strRowSum = rowsSum + ""; + var regex = /(\d+)(\d{3})/; + while (regex.test(strRowSum)) { + strRowSum = strRowSum.replace(regex, '$1' + ',' + '$2'); + } + // If approximated total value add ~ in front + if (rowSumApproximated) { + strRowSum = "~" + strRowSum; + } + // Calculate the magnitude for the size and overhead values + var size_magnitude = 0, overhead_magnitude = 0; + while (sizeSum >= 1024) { + sizeSum /= 1024; + size_magnitude++; + } + while (overheadSum >= 1024) { + overheadSum /= 1024; + overhead_magnitude++; + } + + sizeSum = Math.round(sizeSum * 10) / 10; + overheadSum = Math.round(overheadSum * 10) / 10; + + // Update summary with new data + var $summary = $("#tbl_summary_row"); + $summary.find('.tbl_num').text($.sprintf(PMA_messages.strTables, tableSum)); + $summary.find('.tbl_rows').text(strRowSum); + $summary.find('.tbl_size').text(sizeSum + " " + byteUnits[size_magnitude]); + $summary.find('.tbl_overhead').text(overheadSum + " " + byteUnits[overhead_magnitude]); +} + +AJAX.registerOnload('db_structure.js', function () { + /** + * Handler for the print view multisubmit. + * All other multi submits can be handled via ajax, but this one needs + * special treatment as the results need to open in another browser window + */ + $('#tablesForm').submit(function (event) { + var $form = $(this); + if ($form.find('select[name=submit_mult]').val() === 'print') { + event.preventDefault(); + event.stopPropagation(); + $('form#clone').remove(); + var $clone = $form + .clone() + .hide() + .appendTo('body'); + $clone + .find('select[name=submit_mult]') + .val('print'); + $clone + .attr('target', 'printview') + .attr('id', 'clone') + .submit(); + } + }); + + /** + * Event handler for 'Foreign Key Checks' disabling option + * in the drop table confirmation form + */ + $("span.fkc_switch").click(function (event) { + if ($("#fkc_checkbox").prop('checked')) { + $("#fkc_checkbox").prop('checked', false); + $("#fkc_status").html(PMA_messages.strForeignKeyCheckDisabled); + return; + } + $("#fkc_checkbox").prop('checked', true); + $("#fkc_status").html(PMA_messages.strForeignKeyCheckEnabled); + }); + + $('#fkc_checkbox').change(function () { + if ($(this).prop("checked")) { + $("#fkc_status").html(PMA_messages.strForeignKeyCheckEnabled); + return; + } + $("#fkc_status").html(PMA_messages.strForeignKeyCheckDisabled); + }); // End of event handler for 'Foreign Key Check' + + /** + * Ajax Event handler for 'Truncate Table' + */ + $("a.truncate_table_anchor.ajax").live('click', function (event) { + event.preventDefault(); + + /** + * @var $this_anchor Object referring to the anchor clicked + */ + var $this_anchor = $(this); + + //extract current table name and build the question string + /** + * @var curr_table_name String containing the name of the table to be truncated + */ + var curr_table_name = $this_anchor.parents('tr').children('th').children('a').text(); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_messages.strTruncateTableStrongWarning + ' ' + + $.sprintf(PMA_messages.strDoYouReally, 'TRUNCATE ' + escapeHtml(curr_table_name)); + + $this_anchor.PMA_confirm(question, $this_anchor.attr('href'), function (url) { + + PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + + $.get(url, {'is_js_confirmed' : 1, 'ajax_request' : true}, function (data) { + if (data.success === true) { + PMA_ajaxShowMessage(data.message); + // Adjust table statistics + var $tr = $this_anchor.closest('tr'); + $tr.find('.tbl_rows').text('0'); + $tr.find('.tbl_size, .tbl_overhead').text('-'); + //Fetch inner span of this anchor + //and replace the icon with its disabled version + var span = $this_anchor.html().replace(/b_empty/, 'bd_empty'); + //To disable further attempts to truncate the table, + //replace the a element with its inner span (modified) + $this_anchor + .replaceWith(span) + .removeClass('truncate_table_anchor'); + PMA_adjustTotals(); + } else { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + data.error, false); + } + }); // end $.get() + }); //end $.PMA_confirm() + }); //end of Truncate Table Ajax action + + /** + * Ajax Event handler for 'Drop Table' or 'Drop View' + */ + $("a.drop_table_anchor.ajax").live('click', function (event) { + event.preventDefault(); + + var $this_anchor = $(this); + + //extract current table name and build the question string + /** + * @var $curr_row Object containing reference to the current row + */ + var $curr_row = $this_anchor.parents('tr'); + /** + * @var curr_table_name String containing the name of the table to be truncated + */ + var curr_table_name = $curr_row.children('th').children('a').text(); + /** + * @var is_view Boolean telling if we have a view + */ + var is_view = $curr_row.hasClass('is_view') || $this_anchor.hasClass('view'); + /** + * @var question String containing the question to be asked for confirmation + */ + var question; + if (! is_view) { + question = PMA_messages.strDropTableStrongWarning + ' ' + + $.sprintf(PMA_messages.strDoYouReally, 'DROP TABLE ' + escapeHtml(curr_table_name)); + } else { + question = + $.sprintf(PMA_messages.strDoYouReally, 'DROP VIEW ' + escapeHtml(curr_table_name)); + } + + $this_anchor.PMA_confirm(question, $this_anchor.attr('href'), function (url) { + + var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + + $.get(url, {'is_js_confirmed' : 1, 'ajax_request' : true}, function (data) { + if (data.success === true) { + PMA_ajaxShowMessage(data.message); + toggleRowColors($curr_row.next()); + $curr_row.hide("medium").remove(); + PMA_adjustTotals(); + PMA_reloadNavigation(); + PMA_ajaxRemoveMessage($msg); + } else { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + data.error, false); + } + }); // end $.get() + }); // end $.PMA_confirm() + }); //end of Drop Table Ajax action + + /** + * Ajax Event handler for 'Drop tracking' + */ + $('a.drop_tracking_anchor.ajax').live('click', function (event) { + event.preventDefault(); + + var $anchor = $(this); + + /** + * @var curr_tracking_row Object containing reference to the current tracked table's row + */ + var $curr_tracking_row = $anchor.parents('tr'); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_messages.strDeleteTrackingData; + + $anchor.PMA_confirm(question, $anchor.attr('href'), function (url) { + + PMA_ajaxShowMessage(PMA_messages.strDeletingTrackingData); + + $.get(url, {'is_js_confirmed': 1, 'ajax_request': true}, function (data) { + if (data.success === true) { + var $tracked_table = $curr_tracking_row.parents('table'); + var table_name = $curr_tracking_row.find('td:nth-child(2)').text(); + + // Check how many rows will be left after we remove + if ($tracked_table.find('tbody tr').length === 1) { + // We are removing the only row it has + $('#tracked_tables').hide("slow").remove(); + } else { + // There are more rows left after the deletion + toggleRowColors($curr_tracking_row.next()); + $curr_tracking_row.hide("slow", function () { + $(this).remove(); + }); + } + + // Make the removed table visible in the list of 'Untracked tables'. + var $untracked_table = $('table#noversions'); + + // This won't work if no untracked tables are there. + if ($untracked_table.length > 0) { + var $rows = $untracked_table.find('tbody tr'); + + $rows.each(function (index) { + var $row = $(this); + var tmp_tbl_name = $row.find('td:first-child').text(); + var is_last_iteration = (index == ($rows.length - 1)); + + if (tmp_tbl_name > table_name || is_last_iteration) { + var $cloned = $row.clone(); + + // Change the table name of the cloned row. + $cloned.find('td:first-child').text(table_name); + + // Change the link of the cloned row. + var new_url = $cloned + .find('td:nth-child(2) a') + .attr('href') + .replace('table=' + tmp_tbl_name, 'table=' + encodeURIComponent(table_name)); + $cloned.find('td:nth-child(2) a').attr('href', new_url); + + // Insert the cloned row in an appropriate location. + if (tmp_tbl_name > table_name) { + $cloned.insertBefore($row); + toggleRowColors($row); + return false; + } else { + $cloned.insertAfter($row); + toggleRowColors($cloned); + } + } + }); + } + + PMA_ajaxShowMessage(data.message); + } else { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + data.error, false); + } + }); // end $.get() + }); // end $.PMA_confirm() + }); //end Drop Tracking + + //Calculate Real End for InnoDB + /** + * Ajax Event handler for calculatig the real end for a InnoDB table + * + */ + $('#real_end_input').live('click', function (event) { + event.preventDefault(); + + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_messages.strOperationTakesLongTime; + + $(this).PMA_confirm(question, '', function () { + return true; + }); + return false; + }); //end Calculate Real End for InnoDB + +}); // end $() diff --git a/sources/js/doclinks.js b/sources/js/doclinks.js new file mode 100644 index 0000000..678e7e8 --- /dev/null +++ b/sources/js/doclinks.js @@ -0,0 +1,365 @@ +/** + * Definition of links to MySQL documentation. + */ + +var mysql_doc_keyword = { + /* Multi word */ + 'CHARACTER SET': Array('charset'), + 'SHOW AUTHORS': Array('show-authors'), + 'SHOW BINARY LOGS': Array('show-binary-logs'), + 'SHOW BINLOG EVENTS': Array('show-binlog-events'), + 'SHOW CHARACTER SET': Array('show-character-set'), + 'SHOW COLLATION': Array('show-collation'), + 'SHOW COLUMNS': Array('show-columns'), + 'SHOW CONTRIBUTORS': Array('show-contributors'), + 'SHOW CREATE DATABASE': Array('show-create-database'), + 'SHOW CREATE EVENT': Array('show-create-event'), + 'SHOW CREATE FUNCTION': Array('show-create-function'), + 'SHOW CREATE PROCEDURE': Array('show-create-procedure'), + 'SHOW CREATE TABLE': Array('show-create-table'), + 'SHOW CREATE TRIGGER': Array('show-create-trigger'), + 'SHOW CREATE VIEW': Array('show-create-view'), + 'SHOW DATABASES': Array('show-databases'), + 'SHOW ENGINE': Array('show-engine'), + 'SHOW ENGINES': Array('show-engines'), + 'SHOW ERRORS': Array('show-errors'), + 'SHOW EVENTS': Array('show-events'), + 'SHOW FUNCTION CODE': Array('show-function-code'), + 'SHOW FUNCTION STATUS': Array('show-function-status'), + 'SHOW GRANTS': Array('show-grants'), + 'SHOW INDEX': Array('show-index'), + 'SHOW MASTER STATUS': Array('show-master-status'), + 'SHOW OPEN TABLES': Array('show-open-tables'), + 'SHOW PLUGINS': Array('show-plugins'), + 'SHOW PRIVILEGES': Array('show-privileges'), + 'SHOW PROCEDURE CODE': Array('show-procedure-code'), + 'SHOW PROCEDURE STATUS': Array('show-procedure-status'), + 'SHOW PROCESSLIST': Array('show-processlist'), + 'SHOW PROFILE': Array('show-profile'), + 'SHOW PROFILES': Array('show-profiles'), + 'SHOW RELAYLOG EVENTS': Array('show-relaylog-events'), + 'SHOW SLAVE HOSTS': Array('show-slave-hosts'), + 'SHOW SLAVE STATUS': Array('show-slave-status'), + 'SHOW STATUS': Array('show-status'), + 'SHOW TABLE STATUS': Array('show-table-status'), + 'SHOW TABLES': Array('show-tables'), + 'SHOW TRIGGERS': Array('show-triggers'), + 'SHOW VARIABLES': Array('show-variables'), + 'SHOW WARNINGS': Array('show-warnings'), + 'LOAD DATA INFILE': Array('load-data'), + 'LOAD XML': Array('load-xml'), + 'LOCK TABLES': Array('lock-tables'), + 'UNLOCK TABLES': Array('lock-tables'), + 'ALTER DATABASE': Array('alter-database'), + 'ALTER EVENT': Array('alter-event'), + 'ALTER LOGFILE GROUP': Array('alter-logfile-group'), + 'ALTER FUNCTION': Array('alter-function'), + 'ALTER PROCEDURE': Array('alter-procedure'), + 'ALTER SERVER': Array('alter-server'), + 'ALTER TABLE': Array('alter-table'), + 'ALTER TABLESPACE': Array('alter-tablespace'), + 'ALTER VIEW': Array('alter-view'), + 'CREATE DATABASE': Array('create-database'), + 'CREATE EVENT': Array('create-event'), + 'CREATE FUNCTION': Array('create-function'), + 'CREATE INDEX': Array('create-index'), + 'CREATE LOGFILE GROUP': Array('create-logfile-group'), + 'CREATE PROCEDURE': Array('create-procedure'), + 'CREATE SERVER': Array('create-server'), + 'CREATE TABLE': Array('create-table'), + 'CREATE TABLESPACE': Array('create-tablespace'), + 'CREATE TRIGGER': Array('create-trigger'), + 'CREATE VIEW': Array('create-view'), + 'DROP DATABASE': Array('drop-database'), + 'DROP EVENT': Array('drop-event'), + 'DROP FUNCTION': Array('drop-function'), + 'DROP INDEX': Array('drop-index'), + 'DROP LOGFILE GROUP': Array('drop-logfile-group'), + 'DROP PROCEDURE': Array('drop-procedure'), + 'DROP SERVER': Array('drop-server'), + 'DROP TABLE': Array('drop-table'), + 'DROP TABLESPACE': Array('drop-tablespace'), + 'DROP TRIGGER': Array('drop-trigger'), + 'DROP VIEW': Array('drop-view'), + 'RENAME TABLE': Array('rename-table'), + 'TRUNCATE TABLE': Array('truncate-table'), + + /* Statements */ + 'SELECT': Array('select'), + 'SET': Array('set'), + 'EXPLAIN': Array('explain'), + 'DESCRIBE': Array('describe'), + 'DELETE': Array('delete'), + 'SHOW': Array('show'), + 'UPDATE': Array('update'), + 'INSERT': Array('insert'), + 'REPLACE': Array('replace'), + 'CALL': Array('call'), + 'DO': Array('do'), + 'HANDLER': Array('handler'), + 'COLLATE': Array('charset-collations'), + + /* Functions */ + 'ABS': Array('mathematical-functions', 'function_abs'), + 'ACOS': Array('mathematical-functions', 'function_acos'), + 'ADDDATE': Array('date-and-time-functions', 'function_adddate'), + 'ADDTIME': Array('date-and-time-functions', 'function_addtime'), + 'AES_DECRYPT': Array('encryption-functions', 'function_aes_decrypt'), + 'AES_ENCRYPT': Array('encryption-functions', 'function_aes_encrypt'), + 'AND': Array('logical-operators', 'operator_and'), + 'ASCII': Array('string-functions', 'function_ascii'), + 'ASIN': Array('mathematical-functions', 'function_asin'), + 'ATAN2': Array('mathematical-functions', 'function_atan2'), + 'ATAN': Array('mathematical-functions', 'function_atan'), + 'AVG': Array('group-by-functions', 'function_avg'), + 'BENCHMARK': Array('information-functions', 'function_benchmark'), + 'BIN': Array('string-functions', 'function_bin'), + 'BINARY': Array('cast-functions', 'operator_binary'), + 'BIT_AND': Array('group-by-functions', 'function_bit_and'), + 'BIT_COUNT': Array('bit-functions', 'function_bit_count'), + 'BIT_LENGTH': Array('string-functions', 'function_bit_length'), + 'BIT_OR': Array('group-by-functions', 'function_bit_or'), + 'BIT_XOR': Array('group-by-functions', 'function_bit_xor'), + 'CASE': Array('control-flow-functions', 'operator_case'), + 'CAST': Array('cast-functions', 'function_cast'), + 'CEIL': Array('mathematical-functions', 'function_ceil'), + 'CEILING': Array('mathematical-functions', 'function_ceiling'), + 'CHAR_LENGTH': Array('string-functions', 'function_char_length'), + 'CHAR': Array('string-functions', 'function_char'), + 'CHARACTER_LENGTH': Array('string-functions', 'function_character_length'), + 'CHARSET': Array('information-functions', 'function_charset'), + 'COALESCE': Array('comparison-operators', 'function_coalesce'), + 'COERCIBILITY': Array('information-functions', 'function_coercibility'), + 'COLLATION': Array('information-functions', 'function_collation'), + 'COMPRESS': Array('encryption-functions', 'function_compress'), + 'CONCAT_WS': Array('string-functions', 'function_concat_ws'), + 'CONCAT': Array('string-functions', 'function_concat'), + 'CONNECTION_ID': Array('information-functions', 'function_connection_id'), + 'CONV': Array('mathematical-functions', 'function_conv'), + 'CONVERT_TZ': Array('date-and-time-functions', 'function_convert_tz'), + 'Convert': Array('cast-functions', 'function_convert'), + 'COS': Array('mathematical-functions', 'function_cos'), + 'COT': Array('mathematical-functions', 'function_cot'), + 'COUNT': Array('group-by-functions', 'function_count'), + 'CRC32': Array('mathematical-functions', 'function_crc32'), + 'CURDATE': Array('date-and-time-functions', 'function_curdate'), + 'CURRENT_DATE': Array('date-and-time-functions', 'function_current_date'), + 'CURRENT_TIME': Array('date-and-time-functions', 'function_current_time'), + 'CURRENT_TIMESTAMP': Array('date-and-time-functions', 'function_current_timestamp'), + 'CURRENT_USER': Array('information-functions', 'function_current_user'), + 'CURTIME': Array('date-and-time-functions', 'function_curtime'), + 'DATABASE': Array('information-functions', 'function_database'), + 'DATE_ADD': Array('date-and-time-functions', 'function_date_add'), + 'DATE_FORMAT': Array('date-and-time-functions', 'function_date_format'), + 'DATE_SUB': Array('date-and-time-functions', 'function_date_sub'), + 'DATE': Array('date-and-time-functions', 'function_date'), + 'DATEDIFF': Array('date-and-time-functions', 'function_datediff'), + 'DAY': Array('date-and-time-functions', 'function_day'), + 'DAYNAME': Array('date-and-time-functions', 'function_dayname'), + 'DAYOFMONTH': Array('date-and-time-functions', 'function_dayofmonth'), + 'DAYOFWEEK': Array('date-and-time-functions', 'function_dayofweek'), + 'DAYOFYEAR': Array('date-and-time-functions', 'function_dayofyear'), + 'DECLARE': Array('declare', 'declare'), + 'DECODE': Array('encryption-functions', 'function_decode'), + 'DEFAULT': Array('miscellaneous-functions', 'function_default'), + 'DEGREES': Array('mathematical-functions', 'function_degrees'), + 'DES_DECRYPT': Array('encryption-functions', 'function_des_decrypt'), + 'DES_ENCRYPT': Array('encryption-functions', 'function_des_encrypt'), + 'DIV': Array('arithmetic-functions', 'operator_div'), + 'ELT': Array('string-functions', 'function_elt'), + 'ENCODE': Array('encryption-functions', 'function_encode'), + 'ENCRYPT': Array('encryption-functions', 'function_encrypt'), + 'EXP': Array('mathematical-functions', 'function_exp'), + 'EXPORT_SET': Array('string-functions', 'function_export_set'), + 'EXTRACT': Array('date-and-time-functions', 'function_extract'), + 'ExtractValue': Array('xml-functions', 'function_extractvalue'), + 'FIELD': Array('string-functions', 'function_field'), + 'FIND_IN_SET': Array('string-functions', 'function_find_in_set'), + 'FLOOR': Array('mathematical-functions', 'function_floor'), + 'FORMAT': Array('string-functions', 'function_format'), + 'FOUND_ROWS': Array('information-functions', 'function_found_rows'), + 'FROM_DAYS': Array('date-and-time-functions', 'function_from_days'), + 'FROM_UNIXTIME': Array('date-and-time-functions', 'function_from_unixtime'), + 'GET_FORMAT': Array('date-and-time-functions', 'function_get_format'), + 'GET_LOCK': Array('miscellaneous-functions', 'function_get_lock'), + 'GREATEST': Array('comparison-operators', 'function_greatest'), + 'GROUP_CONCAT': Array('group-by-functions', 'function_group_concat'), + 'HEX': Array('string-functions', 'function_hex'), + 'HOUR': Array('date-and-time-functions', 'function_hour'), + 'IF': Array('control-flow-functions', 'function_if'), + 'IFNULL': Array('control-flow-functions', 'function_ifnull'), + 'IN': Array('comparison-operators', 'function_in'), + 'INET_ATON': Array('miscellaneous-functions', 'function_inet_aton'), + 'INET_NTOA': Array('miscellaneous-functions', 'function_inet_ntoa'), + 'INSTR': Array('string-functions', 'function_instr'), + 'INTERVAL': Array('comparison-operators', 'function_interval'), + 'IS_FREE_LOCK': Array('miscellaneous-functions', 'function_is_free_lock'), + 'IS_USED_LOCK': Array('miscellaneous-functions', 'function_is_used_lock'), + 'IS': Array('comparison-operators', 'operator_is'), + 'ISNULL': Array('comparison-operators', 'function_isnull'), + 'LAST_DAY': Array('date-and-time-functions', 'function_last_day'), + 'LAST_INSERT_ID': Array('information-functions', 'function_last_insert_id'), + 'LCASE': Array('string-functions', 'function_lcase'), + 'LEAST': Array('comparison-operators', 'function_least'), + 'LEFT': Array('string-functions', 'function_left'), + 'LENGTH': Array('string-functions', 'function_length'), + 'LIKE': Array('string-comparison-functions', 'operator_like'), + 'LN': Array('mathematical-functions', 'function_ln'), + 'LOAD_FILE': Array('string-functions', 'function_load_file'), + 'LOCALTIME': Array('date-and-time-functions', 'function_localtime'), + 'LOCALTIMESTAMP': Array('date-and-time-functions', 'function_localtimestamp'), + 'LOCATE': Array('string-functions', 'function_locate'), + 'LOG10': Array('mathematical-functions', 'function_log10'), + 'LOG2': Array('mathematical-functions', 'function_log2'), + 'LOG': Array('mathematical-functions', 'function_log'), + 'LOWER': Array('string-functions', 'function_lower'), + 'LPAD': Array('string-functions', 'function_lpad'), + 'LTRIM': Array('string-functions', 'function_ltrim'), + 'MAKE_SET': Array('string-functions', 'function_make_set'), + 'MAKEDATE': Array('date-and-time-functions', 'function_makedate'), + 'MAKETIME': Array('date-and-time-functions', 'function_maketime'), + 'MASTER_POS_WAIT': Array('miscellaneous-functions', 'function_master_pos_wait'), + 'MATCH': Array('fulltext-search', 'function_match'), + 'MAX': Array('group-by-functions', 'function_max'), + 'MD5': Array('encryption-functions', 'function_md5'), + 'MICROSECOND': Array('date-and-time-functions', 'function_microsecond'), + 'MID': Array('string-functions', 'function_mid'), + 'MIN': Array('group-by-functions', 'function_min'), + 'MINUTE': Array('date-and-time-functions', 'function_minute'), + 'MOD': Array('mathematical-functions', 'function_mod'), + 'MONTH': Array('date-and-time-functions', 'function_month'), + 'MONTHNAME': Array('date-and-time-functions', 'function_monthname'), + 'NAME_CONST': Array('miscellaneous-functions', 'function_name_const'), + 'NOT': Array('logical-operators', 'operator_not'), + 'NOW': Array('date-and-time-functions', 'function_now'), + 'NULLIF': Array('control-flow-functions', 'function_nullif'), + 'OCT': Array('mathematical-functions', 'function_oct'), + 'OCTET_LENGTH': Array('string-functions', 'function_octet_length'), + 'OLD_PASSWORD': Array('encryption-functions', 'function_old_password'), + 'OR': Array('logical-operators', 'operator_or'), + 'ORD': Array('string-functions', 'function_ord'), + 'PASSWORD': Array('encryption-functions', 'function_password'), + 'PERIOD_ADD': Array('date-and-time-functions', 'function_period_add'), + 'PERIOD_DIFF': Array('date-and-time-functions', 'function_period_diff'), + 'PI': Array('mathematical-functions', 'function_pi'), + 'POSITION': Array('string-functions', 'function_position'), + 'POW': Array('mathematical-functions', 'function_pow'), + 'POWER': Array('mathematical-functions', 'function_power'), + 'QUARTER': Array('date-and-time-functions', 'function_quarter'), + 'QUOTE': Array('string-functions', 'function_quote'), + 'RADIANS': Array('mathematical-functions', 'function_radians'), + 'RAND': Array('mathematical-functions', 'function_rand'), + 'REGEXP': Array('regexp', 'operator_regexp'), + 'RELEASE_LOCK': Array('miscellaneous-functions', 'function_release_lock'), + 'REPEAT': Array('string-functions', 'function_repeat'), + 'REVERSE': Array('string-functions', 'function_reverse'), + 'RIGHT': Array('string-functions', 'function_right'), + 'RLIKE': Array('regexp', 'operator_rlike'), + 'ROUND': Array('mathematical-functions', 'function_round'), + 'ROW_COUNT': Array('information-functions', 'function_row_count'), + 'RPAD': Array('string-functions', 'function_rpad'), + 'RTRIM': Array('string-functions', 'function_rtrim'), + 'SCHEMA': Array('information-functions', 'function_schema'), + 'SEC_TO_TIME': Array('date-and-time-functions', 'function_sec_to_time'), + 'SECOND': Array('date-and-time-functions', 'function_second'), + 'SESSION_USER': Array('information-functions', 'function_session_user'), + 'SHA': Array('encryption-functions', 'function_sha1'), + 'SHA1': Array('encryption-functions', 'function_sha1'), + 'SIGN': Array('mathematical-functions', 'function_sign'), + 'SIN': Array('mathematical-functions', 'function_sin'), + 'SLEEP': Array('miscellaneous-functions', 'function_sleep'), + 'SOUNDEX': Array('string-functions', 'function_soundex'), + 'SPACE': Array('string-functions', 'function_space'), + 'SQRT': Array('mathematical-functions', 'function_sqrt'), + 'STD': Array('group-by-functions', 'function_std'), + 'STDDEV_POP': Array('group-by-functions', 'function_stddev_pop'), + 'STDDEV_SAMP': Array('group-by-functions', 'function_stddev_samp'), + 'STDDEV': Array('group-by-functions', 'function_stddev'), + 'STR_TO_DATE': Array('date-and-time-functions', 'function_str_to_date'), + 'STRCMP': Array('string-comparison-functions', 'function_strcmp'), + 'SUBDATE': Array('date-and-time-functions', 'function_subdate'), + 'SUBSTR': Array('string-functions', 'function_substr'), + 'SUBSTRING_INDEX': Array('string-functions', 'function_substring_index'), + 'SUBSTRING': Array('string-functions', 'function_substring'), + 'SUBTIME': Array('date-and-time-functions', 'function_subtime'), + 'SUM': Array('group-by-functions', 'function_sum'), + 'SYSDATE': Array('date-and-time-functions', 'function_sysdate'), + 'SYSTEM_USER': Array('information-functions', 'function_system_user'), + 'TAN': Array('mathematical-functions', 'function_tan'), + 'TIME_FORMAT': Array('date-and-time-functions', 'function_time_format'), + 'TIME_TO_SEC': Array('date-and-time-functions', 'function_time_to_sec'), + 'TIME': Array('date-and-time-functions', 'function_time'), + 'TIMEDIFF': Array('date-and-time-functions', 'function_timediff'), + 'TIMESTAMP': Array('date-and-time-functions', 'function_timestamp'), + 'TIMESTAMPADD': Array('date-and-time-functions', 'function_timestampadd'), + 'TIMESTAMPDIFF': Array('date-and-time-functions', 'function_timestampdiff'), + 'TO_DAYS': Array('date-and-time-functions', 'function_to_days'), + 'TRIM': Array('string-functions', 'function_trim'), + 'TRUNCATE': Array('mathematical-functions', 'function_truncate'), + 'UCASE': Array('string-functions', 'function_ucase'), + 'UNCOMPRESS': Array('encryption-functions', 'function_uncompress'), + 'UNCOMPRESSED_LENGTH': Array('encryption-functions', 'function_uncompressed_length'), + 'UNHEX': Array('string-functions', 'function_unhex'), + 'UNIX_TIMESTAMP': Array('date-and-time-functions', 'function_unix_timestamp'), + 'UpdateXML': Array('xml-functions', 'function_updatexml'), + 'UPPER': Array('string-functions', 'function_upper'), + 'USER': Array('information-functions', 'function_user'), + 'UTC_DATE': Array('date-and-time-functions', 'function_utc_date'), + 'UTC_TIME': Array('date-and-time-functions', 'function_utc_time'), + 'UTC_TIMESTAMP': Array('date-and-time-functions', 'function_utc_timestamp'), + 'UUID_SHORT': Array('miscellaneous-functions', 'function_uuid_short'), + 'UUID': Array('miscellaneous-functions', 'function_uuid'), + 'VALUES': Array('miscellaneous-functions', 'function_values'), + 'VAR_POP': Array('group-by-functions', 'function_var_pop'), + 'VAR_SAMP': Array('group-by-functions', 'function_var_samp'), + 'VARIANCE': Array('group-by-functions', 'function_variance'), + 'VERSION': Array('information-functions', 'function_version'), + 'WEEK': Array('date-and-time-functions', 'function_week'), + 'WEEKDAY': Array('date-and-time-functions', 'function_weekday'), + 'WEEKOFYEAR': Array('date-and-time-functions', 'function_weekofyear'), + 'XOR': Array('logical-operators', 'operator_xor'), + 'YEAR': Array('date-and-time-functions', 'function_year'), + 'YEARWEEK': Array('date-and-time-functions', 'function_yearweek'), + 'SOUNDS_LIKE': Array('string-functions', 'operator_sounds-like'), + 'IS_NOT_NULL': Array('comparison-operators', 'operator_is-not-null'), + 'IS_NOT': Array('comparison-operators', 'operator_is-not'), + 'IS_NULL': Array('comparison-operators', 'operator_is-null'), + 'NOT_LIKE': Array('string-comparison-functions', 'operator_not-like'), + 'NOT_REGEXP': Array('regexp', 'operator_not-regexp'), + 'COUNT_DISTINCT': Array('group-by-functions', 'function_count-distinct'), + 'NOT_IN': Array('comparison-operators', 'function_not-in') +}; + +var mysql_doc_builtin = { + 'TINYINT': Array('numeric-types'), + 'SMALLINT': Array('numeric-types'), + 'MEDIUMINT': Array('numeric-types'), + 'INT': Array('numeric-types'), + 'BIGINT': Array('numeric-types'), + 'DECIMAL': Array('numeric-types'), + 'FLOAT': Array('numeric-types'), + 'DOUBLE': Array('numeric-types'), + 'REAL': Array('numeric-types'), + 'BIT': Array('numeric-types'), + 'BOOLEAN': Array('numeric-types'), + 'SERIAL': Array('numeric-types'), + 'DATE': Array('date-and-time-types'), + 'DATETIME': Array('date-and-time-types'), + 'TIMESTAMP': Array('date-and-time-types'), + 'TIME': Array('date-and-time-types'), + 'YEAR': Array('date-and-time-types'), + 'CHAR': Array('string-types'), + 'VARCHAR': Array('string-types'), + 'TINYTEXT': Array('string-types'), + 'TEXT': Array('string-types'), + 'MEDIUMTEXT': Array('string-types'), + 'LONGTEXT': Array('string-types'), + 'BINARY': Array('string-types'), + 'VARBINARY': Array('string-types'), + 'TINYBLOB': Array('string-types'), + 'MEDIUMBLOB': Array('string-types'), + 'BLOB': Array('string-types'), + 'LONGBLOB': Array('string-types'), + 'ENUM': Array('string-types'), + 'SET': Array('string-types') +}; diff --git a/sources/js/error_report.js b/sources/js/error_report.js new file mode 100644 index 0000000..c376530 --- /dev/null +++ b/sources/js/error_report.js @@ -0,0 +1,310 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * general function, usually for data manipulation pages + * + */ + +var ErrorReport = { + /** + * @var object stores the last exception info + */ + _last_exception: null, + /** + * handles thrown error exceptions based on user preferences + * + * @return void + */ + error_handler: function (exception) { + ErrorReport._last_exception = exception; + $.get("error_report.php", { + ajax_request: true, + token: PMA_commonParams.get('token'), + get_settings: true + }, function (data) { + if (!data.success === true) { + PMA_ajaxShowMessage(data.error, false); + return; + } + if (data.report_setting == "ask") { + ErrorReport._showErrorNotification(); + } else if (data.report_setting == "always") { + report_data = ErrorReport._get_report_data(exception); + post_data = $.extend(report_data, { + send_error_report: true, + automatic: true + }); + $.post("error_report.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_ajaxShowMessage(data.message, false); + } + }); + } + }); + }, + /** + * Shows the modal dialog previewing the report + * + * @param object error report info + * + * @return void + */ + _showReportDialog: function (exception) { + var report_data = ErrorReport._get_report_data(exception); + + /*Remove the hidden dialogs if there are*/ + if ($('#error_report_dialog').length !== 0) { + $('#error_report_dialog').remove(); + } + var $div = $('
      '); + + var button_options = {}; + + button_options[PMA_messages.strSendErrorReport] = function () { + $dialog = $(this); + post_data = $.extend(report_data, { + send_error_report: true, + description: $("#report_description").val(), + always_send: $("#always_send_checkbox")[0].checked + }); + $.post("error_report.php", post_data, function (data) { + $dialog.dialog('close'); + if (data.success === false) { + //in the case of an error, show the error message returned. + PMA_ajaxShowMessage(data.error, false); + } else { + PMA_ajaxShowMessage(data.message, 3000); + } + }); + }; + + button_options[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + + $.post("error_report.php", report_data, function (data) { + if (data.success === false) { + //in the case of an error, show the error message returned. + PMA_ajaxShowMessage(data.error, false); + } else { + // Show dialog if the request was successful + $div + .append(data.message) + .dialog({ + title: PMA_messages.strSubmitErrorReport, + width: 650, + modal: true, + buttons: button_options, + close: function () { + $(this).remove(); + } + }); + } + }); // end $.get() + }, + /** + * Shows the small notification that asks for user permission + * + * @return void + */ + _showErrorNotification: function () { + ErrorReport._removeErrorNotification(); + + $div = $( + '
      ' + ).append( + PMA_getImage("s_error.png") + PMA_messages.strErrorOccurred + ); + + $buttons = $('
      '); + + button_html = ''; + + button_html += ''; + button_html += PMA_getImage('s_cog.png', PMA_messages.strChangeReportSettings); + button_html += ''; + + button_html += ''; + button_html += PMA_getImage('b_close.png', PMA_messages.strIgnore); + button_html += ''; + + $buttons.html(button_html); + + $div.append($buttons); + $div.appendTo(document.body); + $("#change_error_settings").on("click", ErrorReport._redirect_to_settings); + $("#show_error_report").on("click", ErrorReport._createReportDialog); + $("#ignore_error").on("click", ErrorReport._removeErrorNotification); + }, + /** + * Removes the notification if it was displayed before + * + * @return void + */ + _removeErrorNotification: function () { + $("#error_notification").fadeOut(function () { + $(this).remove(); + }); + }, + /** + * Shows the modal dialog previewing the report + * + * @return void + */ + _createReportDialog: function () { + ErrorReport._removeErrorNotification(); + ErrorReport._showReportDialog(ErrorReport._last_exception); + }, + /** + * Returns the needed info about stored microhistory + * + * @return object + */ + _get_microhistory: function () { + cached_pages = AJAX.cache.pages.slice(-7); + remove = ["common_query", "table", "db", "token", "pma_absolute_uri"]; + return { + pages: cached_pages.map(function (page) { + simplepage = { + hash: page.hash + }; + + if (page.params) { + simplepage.params = $.extend({}, page.params); + $.each(simplepage.params, function (param) { + if ($.inArray(param, remove) != -1) { + delete simplepage.params[param]; + } + }); + } + + return simplepage; + }), + current_index: AJAX.cache.current - + (AJAX.cache.pages.length - cached_pages.length) + }; + }, + /** + * Redirects to the settings page containing error report + * preferences + * + * @return void + */ + _redirect_to_settings: function () { + window.location.href = "prefs_forms.php?token=" + PMA_commonParams.get('token'); + }, + /** + * Returns the report data to send to the server + * + * @param object exception info + * + * @return object + */ + _get_report_data: function (exception) { + var report_data = { + "ajax_request": true, + "token": PMA_commonParams.get('token'), + "exception": exception, + "current_url": window.location.href, + "microhistory": ErrorReport._get_microhistory() + }; + if (typeof AJAX.cache.pages[AJAX.cache.current - 1] !== 'undefined') { + report_data.scripts = AJAX.cache.pages[AJAX.cache.current - 1].scripts.map( + function (script) { + return script.name; + } + ); + } + return report_data; + }, + /** + * Wraps all global functions that start with PMA_ + * + * @return void + */ + wrap_global_functions: function () { + for (var key in window) { + var global = window[key]; + if (typeof(global) === "function" && key.indexOf("PMA_") === 0) { + window[key] = ErrorReport.wrap_function(global); + } + } + }, + /** + * Wraps given function in error reporting code and returns wrapped function + * + * @param function function to be wrapped + * + * @return function + */ + wrap_function: function (func) { + if (!func.wrapped) { + var new_func = function () { + try { + return func.apply(this, arguments); + } catch (x) { + TraceKit.report(x); + } + }; + new_func.wrapped = true; + //Set guid of wrapped function same as original function, so it can be removed + //See bug#4146 (problem with jquery draggable and sortable) + new_func.guid = func.guid = func.guid || new_func.guid || jQuery.guid++; + return new_func; + } else { + return func; + } + }, + /** + * Automatically wraps the callback in AJAX.registerOnload + * + * @return void + */ + _wrap_ajax_onload_callback: function () { + var oldOnload = AJAX.registerOnload; + AJAX.registerOnload = function (file, func) { + func = ErrorReport.wrap_function(func); + oldOnload.call(this, file, func); + }; + }, + /** + * Automatically wraps the callback in $.fn.on + * + * @return void + */ + _wrap_$_on_callback: function () { + var oldOn = $.fn.on; + $.fn.on = function () { + for (var i = 1; i <= 3; i++) { + if (typeof(arguments[i]) === "function") { + arguments[i] = ErrorReport.wrap_function(arguments[i]); + break; + } + } + return oldOn.apply(this, arguments); + }; + }, + /** + * Wraps all global functions that start with PMA_ + * also automatically wraps the callback in AJAX.registerOnload + * + * @return void + */ + set_up_error_reporting: function () { + ErrorReport.wrap_global_functions(); + ErrorReport._wrap_ajax_onload_callback(); + ErrorReport._wrap_$_on_callback(); + } + +}; + +TraceKit.report.subscribe(ErrorReport.error_handler); +ErrorReport.set_up_error_reporting(); +$(function () { + ErrorReport.wrap_global_functions(); +}); diff --git a/sources/js/export.js b/sources/js/export.js new file mode 100644 index 0000000..a16bd29 --- /dev/null +++ b/sources/js/export.js @@ -0,0 +1,282 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functions used in the export tab + * + */ + +/** + * Disables the "Dump some row(s)" sub-options + */ +function disable_dump_some_rows_sub_options() +{ + $("label[for='limit_to']").fadeTo('fast', 0.4); + $("label[for='limit_from']").fadeTo('fast', 0.4); + $("input[type='text'][name='limit_to']").prop('disabled', 'disabled'); + $("input[type='text'][name='limit_from']").prop('disabled', 'disabled'); +} + +/** + * Enables the "Dump some row(s)" sub-options + */ +function enable_dump_some_rows_sub_options() +{ + $("label[for='limit_to']").fadeTo('fast', 1); + $("label[for='limit_from']").fadeTo('fast', 1); + $("input[type='text'][name='limit_to']").prop('disabled', ''); + $("input[type='text'][name='limit_from']").prop('disabled', ''); +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('export.js', function () { + $("#plugins").unbind('change'); + $("input[type='radio'][name='sql_structure_or_data']").unbind('change'); + $("input[type='radio'][name='latex_structure_or_data']").unbind('change'); + $("input[type='radio'][name='odt_structure_or_data']").unbind('change'); + $("input[type='radio'][name='texytext_structure_or_data']").unbind('change'); + $("input[type='radio'][name='htmlword_structure_or_data']").unbind('change'); + $("input[type='radio'][name='sql_structure_or_data']").unbind('change'); + $("input[type='radio'][name='output_format']").unbind('change'); + $("#checkbox_sql_include_comments").unbind('change'); + $("#plugins").unbind('change'); + $("input[type='radio'][name='quick_or_custom']").unbind('change'); + $("input[type='radio'][name='allrows']").unbind('change'); +}); + +AJAX.registerOnload('export.js', function () { + /** + * Toggles the hiding and showing of each plugin's options + * according to the currently selected plugin from the dropdown list + */ + $("#plugins").change(function () { + $("#format_specific_opts div.format_specific_options").hide(); + var selected_plugin_name = $("#plugins option:selected").val(); + $("#" + selected_plugin_name + "_options").show(); + }); + + /** + * Toggles the enabling and disabling of the SQL plugin's comment options that apply only when exporting structure + */ + $("input[type='radio'][name='sql_structure_or_data']").change(function () { + var comments_are_present = $("#checkbox_sql_include_comments").prop("checked"); + var show = $("input[type='radio'][name='sql_structure_or_data']:checked").val(); + if (show == 'data') { + // disable the SQL comment options + if (comments_are_present) { + $("#checkbox_sql_dates").prop('disabled', true).parent().fadeTo('fast', 0.4); + } + $("#checkbox_sql_relation").prop('disabled', true).parent().fadeTo('fast', 0.4); + $("#checkbox_sql_mime").prop('disabled', true).parent().fadeTo('fast', 0.4); + } else { + // enable the SQL comment options + if (comments_are_present) { + $("#checkbox_sql_dates").removeProp('disabled').parent().fadeTo('fast', 1); + } + $("#checkbox_sql_relation").removeProp('disabled').parent().fadeTo('fast', 1); + $("#checkbox_sql_mime").removeProp('disabled').parent().fadeTo('fast', 1); + } + }); +}); + + +/** + * Toggles the hiding and showing of plugin structure-specific and data-specific + * options + */ +function toggle_structure_data_opts(pluginName) +{ + var radioFormName = pluginName + "_structure_or_data"; + var dataDiv = "#" + pluginName + "_data"; + var structureDiv = "#" + pluginName + "_structure"; + var show = $("input[type='radio'][name='" + radioFormName + "']:checked").val(); + if (show == 'data') { + $(dataDiv).slideDown('slow'); + $(structureDiv).slideUp('slow'); + } else { + $(structureDiv).slideDown('slow'); + if (show == 'structure') { + $(dataDiv).slideUp('slow'); + } else { + $(dataDiv).slideDown('slow'); + } + } +} + +AJAX.registerOnload('export.js', function () { + $("input[type='radio'][name='latex_structure_or_data']").change(function () { + toggle_structure_data_opts("latex"); + }); + $("input[type='radio'][name='odt_structure_or_data']").change(function () { + toggle_structure_data_opts("odt"); + }); + $("input[type='radio'][name='texytext_structure_or_data']").change(function () { + toggle_structure_data_opts("texytext"); + }); + $("input[type='radio'][name='htmlword_structure_or_data']").change(function () { + toggle_structure_data_opts("htmlword"); + }); + $("input[type='radio'][name='sql_structure_or_data']").change(function () { + toggle_structure_data_opts("sql"); + }); +}); + +/** + * Toggles the disabling of the "save to file" options + */ +function toggle_save_to_file() +{ + if (!$("#radio_dump_asfile").prop("checked")) { + $("#ul_save_asfile > li").fadeTo('fast', 0.4); + $("#ul_save_asfile > li > input").prop('disabled', true); + $("#ul_save_asfile > li> select").prop('disabled', true); + } else { + $("#ul_save_asfile > li").fadeTo('fast', 1); + $("#ul_save_asfile > li > input").removeProp('disabled'); + $("#ul_save_asfile > li> select").removeProp('disabled'); + } +} + +AJAX.registerOnload('export.js', function () { + toggle_save_to_file(); + $("input[type='radio'][name='output_format']").change(toggle_save_to_file); +}); + +/** + * For SQL plugin, toggles the disabling of the "display comments" options + */ +function toggle_sql_include_comments() +{ + $("#checkbox_sql_include_comments").change(function () { + if (!$("#checkbox_sql_include_comments").prop("checked")) { + $("#ul_include_comments > li").fadeTo('fast', 0.4); + $("#ul_include_comments > li > input").prop('disabled', true); + } else { + // If structure is not being exported, the comment options for structure should not be enabled + if ($("#radio_sql_structure_or_data_data").prop("checked")) { + $("#text_sql_header_comment").removeProp('disabled').parent("li").fadeTo('fast', 1); + } else { + $("#ul_include_comments > li").fadeTo('fast', 1); + $("#ul_include_comments > li > input").removeProp('disabled'); + } + } + }); +} + +AJAX.registerOnload('export.js', function () { + /** + * For SQL plugin, if "CREATE TABLE options" is checked/unchecked, check/uncheck each of its sub-options + */ + var $create = $("#checkbox_sql_create_table_statements"); + var $create_options = $("#ul_create_table_statements input"); + $create.change(function () { + $create_options.prop('checked', $(this).prop("checked")); + }); + $create_options.change(function () { + if ($create_options.is(":checked")) { + $create.prop('checked', true); + } + }); + + /** + * Disables the view output as text option if the output must be saved as a file + */ + $("#plugins").change(function () { + var active_plugin = $("#plugins option:selected").val(); + var force_file = $("#force_file_" + active_plugin).val(); + if (force_file == "true") { + $("#radio_view_as_text").prop('disabled', true).parent().fadeTo('fast', 0.4); + } else { + $("#radio_view_as_text").removeProp('disabled').parent().fadeTo('fast', 1); + } + }); +}); + +/** + * Toggles display of options when quick and custom export are selected + */ +function toggle_quick_or_custom() +{ + if ($("#radio_custom_export").prop("checked")) { + $("#databases_and_tables").show(); + $("#rows").show(); + $("#output").show(); + $("#format_specific_opts").show(); + $("#output_quick_export").hide(); + var selected_plugin_name = $("#plugins option:selected").val(); + $("#" + selected_plugin_name + "_options").show(); + } else { + $("#databases_and_tables").hide(); + $("#rows").hide(); + $("#output").hide(); + $("#format_specific_opts").hide(); + $("#output_quick_export").show(); + } +} +var time_out; +function check_time_out(time_limit) +{ + //margin of one second to avoid race condition to set/access session variable + time_limit = time_limit + 1; + var href = "export.php"; + var params = { + 'ajax_request' : true, + 'token' : PMA_commonParams.get('token'), + 'check_time_out' : true + }; + clearTimeout(time_out); + time_out = setTimeout(function(){ + $.get(href, params, function (data) { + if (data['message'] !== 'success') { + PMA_ajaxShowMessage( + '
      ' + + PMA_messages.strTimeOutError + + '
      ', + false + ); + } + }); + }, time_limit * 1000); + +} +AJAX.registerOnload('export.js', function () { + $("input[type='radio'][name='quick_or_custom']").change(toggle_quick_or_custom); + + /** + * Sets up the interface for Javascript-enabled browsers since the default is for + * Javascript-disabled browsers + * TODO: drop non-JS behaviour + */ + if ($("input[type='hidden'][name='export_method']").val() != "custom-no-form") { + $("#quick_or_custom").show(); + } + $("#scroll_to_options_msg").hide(); + $("#format_specific_opts div.format_specific_options") + .hide() + .css({ + "border": 0, + "margin": 0, + "padding": 0 + }) + .find("h3") + .remove(); + toggle_quick_or_custom(); + toggle_structure_data_opts($("select#plugins").val()); + toggle_sql_include_comments(); + + /** + * Initially disables the "Dump some row(s)" sub-options + */ + disable_dump_some_rows_sub_options(); + + /** + * Disables the "Dump some row(s)" sub-options when it is not selected + */ + $("input[type='radio'][name='allrows']").change(function () { + if ($("input[type='radio'][name='allrows']").prop("checked")) { + enable_dump_some_rows_sub_options(); + } else { + disable_dump_some_rows_sub_options(); + } + }); +}); diff --git a/sources/js/functions.js b/sources/js/functions.js new file mode 100644 index 0000000..c9e9691 --- /dev/null +++ b/sources/js/functions.js @@ -0,0 +1,4070 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * general function, usually for data manipulation pages + * + */ + +/** + * @var $table_clone reference to the action links on the tbl_structure page + */ +var $table_clone = false; + +/** + * @var sql_box_locked lock for the sqlbox textarea in the querybox/querywindow + */ +var sql_box_locked = false; + +/** + * @var array holds elements which content should only selected once + */ +var only_once_elements = []; + +/** + * @var int ajax_message_count Number of AJAX messages shown since page load + */ +var ajax_message_count = 0; + +/** + * @var codemirror_editor object containing CodeMirror editor of the query editor in SQL tab + */ +var codemirror_editor = false; + +/** + * @var codemirror_editor object containing CodeMirror editor of the inline query editor + */ +var codemirror_inline_editor = false; + +/** + * @var chart_activeTimeouts object active timeouts that refresh the charts. When disabling a realtime chart, this can be used to stop the continuous ajax requests + */ +var chart_activeTimeouts = {}; + +/** + * Make sure that ajax requests will not be cached + * by appending a random variable to their parameters + */ +$.ajaxPrefilter(function (options, originalOptions, jqXHR) { + var nocache = new Date().getTime() + "" + Math.floor(Math.random() * 1000000); + if (typeof options.data == "string") { + options.data += "&_nocache=" + nocache; + } else if (typeof options.data == "object") { + options.data = $.extend(originalOptions.data, {'_nocache' : nocache}); + } +}); + +/** + * Add a hidden field to the form to indicate that this will be an + * Ajax request (only if this hidden field does not exist) + * + * @param object the form + */ +function PMA_prepareForAjaxRequest($form) +{ + if (! $form.find('input:hidden').is('#ajax_request_hidden')) { + $form.append(''); + } +} + +/** + * Generate a new password and copy it to the password input areas + * + * @param object the form that holds the password fields + * + * @return boolean always true + */ +function suggestPassword(passwd_form) +{ + // restrict the password to just letters and numbers to avoid problems: + // "editors and viewers regard the password as multiple words and + // things like double click no longer work" + var pwchars = "abcdefhjmnpqrstuvwxyz23456789ABCDEFGHJKLMNPQRSTUVWYXZ"; + var passwordlength = 16; // do we want that to be dynamic? no, keep it simple :) + var passwd = passwd_form.generated_pw; + passwd.value = ''; + + for (var i = 0; i < passwordlength; i++) { + passwd.value += pwchars.charAt(Math.floor(Math.random() * pwchars.length)); + } + passwd_form.text_pma_pw.value = passwd.value; + passwd_form.text_pma_pw2.value = passwd.value; + return true; +} + +/** + * Version string to integer conversion. + */ +function parseVersionString(str) +{ + if (typeof(str) != 'string') { return false; } + var add = 0; + // Parse possible alpha/beta/rc/ + var state = str.split('-'); + if (state.length >= 2) { + if (state[1].substr(0, 2) == 'rc') { + add = - 20 - parseInt(state[1].substr(2), 10); + } else if (state[1].substr(0, 4) == 'beta') { + add = - 40 - parseInt(state[1].substr(4), 10); + } else if (state[1].substr(0, 5) == 'alpha') { + add = - 60 - parseInt(state[1].substr(5), 10); + } else if (state[1].substr(0, 3) == 'dev') { + /* We don't handle dev, it's git snapshot */ + add = 0; + } + } + // Parse version + var x = str.split('.'); + // Use 0 for non existing parts + var maj = parseInt(x[0], 10) || 0; + var min = parseInt(x[1], 10) || 0; + var pat = parseInt(x[2], 10) || 0; + var hotfix = parseInt(x[3], 10) || 0; + return maj * 100000000 + min * 1000000 + pat * 10000 + hotfix * 100 + add; +} + +/** + * Indicates current available version on main page. + */ +function PMA_current_version(data) +{ + if (data && data.version && data.date) { + var current = parseVersionString(pmaversion); + var latest = parseVersionString(data.version); + var version_information_message = PMA_messages.strLatestAvailable + ' ' + escapeHtml(data.version); + if (latest > current) { + var message = $.sprintf( + PMA_messages.strNewerVersion, + escapeHtml(data.version), + escapeHtml(data.date) + ); + var htmlClass = 'notice'; + if (Math.floor(latest / 10000) === Math.floor(current / 10000)) { + /* Security update */ + htmlClass = 'error'; + } + $('#maincontainer').after('
      ' + message + '
      '); + } + if (latest === current) { + version_information_message = ' (' + PMA_messages.strUpToDate + ')'; + } + $('#li_pma_version').append(version_information_message); + } +} + +/** + * Loads Git revision data from ajax for index.php + */ +function PMA_display_git_revision() +{ + $('#is_git_revision').remove(); + $.get( + "index.php", + { + "server": PMA_commonParams.get('server'), + "token": PMA_commonParams.get('token'), + "git_revision": true, + "ajax_request": true + }, + function (data) { + if (data.success === true) { + $(data.message).insertAfter('#li_pma_version'); + } + } + ); +} + +/** + * for libraries/display_change_password.lib.php + * libraries/user_password.php + * + */ + +function displayPasswordGenerateButton() +{ + $('#tr_element_before_generate_password').parent().append('' + PMA_messages.strGeneratePassword + ''); + $('#div_element_before_generate_password').parent().append('
      '); +} + +/* + * Adds a date/time picker to an element + * + * @param object $this_element a jQuery object pointing to the element + */ +function PMA_addDatepicker($this_element, options) +{ + var showTimeOption = false; + if ($this_element.is('.datetimefield')) { + showTimeOption = true; + } + + var defaultOptions = { + showOn: 'button', + buttonImage: themeCalendarImage, // defined in js/messages.php + buttonImageOnly: true, + stepMinutes: 1, + stepHours: 1, + showSecond: true, + showMillisec: true, + showMicrosec: true, + showTimepicker: showTimeOption, + showButtonPanel: false, + dateFormat: 'yy-mm-dd', // yy means year with four digits + timeFormat: 'HH:mm:ss.lc', + constrainInput: false, + altFieldTimeOnly: false, + showAnim: '', + beforeShow: function (input, inst) { + // Remember that we came from the datepicker; this is used + // in tbl_change.js by verificationsAfterFieldChange() + $this_element.data('comes_from', 'datepicker'); + + // Fix wrong timepicker z-index, doesn't work without timeout + setTimeout(function () { + $('#ui-timepicker-div').css('z-index', $('#ui-datepicker-div').css('z-index')); + }, 0); + }, + onClose: function (dateText, dp_inst) { + // The value is no more from the date picker + $this_element.data('comes_from', ''); + } + }; + if (showTimeOption || (typeof(options) != 'undefined' && options.showTimepicker)) { + $this_element.datetimepicker($.extend(defaultOptions, options)); + } else { + $this_element.datepicker($.extend(defaultOptions, options)); + } +} + +/** + * selects the content of a given object, f.e. a textarea + * + * @param object element element of which the content will be selected + * @param var lock variable which holds the lock for this element + * or true, if no lock exists + * @param boolean only_once if true this is only done once + * f.e. only on first focus + */ +function selectContent(element, lock, only_once) +{ + if (only_once && only_once_elements[element.name]) { + return; + } + + only_once_elements[element.name] = true; + + if (lock) { + return; + } + + element.select(); +} + +/** + * Displays a confirmation box before submitting a "DROP/DELETE/ALTER" query. + * This function is called while clicking links + * + * @param object the link + * @param object the sql query to submit + * + * @return boolean whether to run the query or not + */ +function confirmLink(theLink, theSqlQuery) +{ + // Confirmation is not required in the configuration file + // or browser is Opera (crappy js implementation) + if (PMA_messages.strDoYouReally === '' || typeof(window.opera) != 'undefined') { + return true; + } + + var is_confirmed = confirm($.sprintf(PMA_messages.strDoYouReally, theSqlQuery)); + if (is_confirmed) { + if ($(theLink).hasClass('formLinkSubmit')) { + var name = 'is_js_confirmed'; + if ($(theLink).attr('href').indexOf('usesubform') != -1) { + name = 'subform[' + $(theLink).attr('href').substr('#').match(/usesubform\[(\d+)\]/i)[1] + '][is_js_confirmed]'; + } + + $(theLink).parents('form').append(''); + } else if (typeof(theLink.href) != 'undefined') { + theLink.href += '&is_js_confirmed=1'; + } else if (typeof(theLink.form) != 'undefined') { + theLink.form.action += '?is_js_confirmed=1'; + } + } + + return is_confirmed; +} // end of the 'confirmLink()' function + +/** + * Displays an error message if a "DROP DATABASE" statement is submitted + * while it isn't allowed, else confirms a "DROP/DELETE/ALTER" query before + * sumitting it if required. + * This function is called by the 'checkSqlQuery()' js function. + * + * @param object the form + * @param object the sql query textarea + * + * @return boolean whether to run the query or not + * + * @see checkSqlQuery() + */ +function confirmQuery(theForm1, sqlQuery1) +{ + // Confirmation is not required in the configuration file + if (PMA_messages.strDoYouReally === '') { + return true; + } + + // "DROP DATABASE" statement isn't allowed + if (PMA_messages.strNoDropDatabases !== '') { + var drop_re = new RegExp('(^|;)\\s*DROP\\s+(IF EXISTS\\s+)?DATABASE\\s', 'i'); + if (drop_re.test(sqlQuery1.value)) { + alert(PMA_messages.strNoDropDatabases); + theForm1.reset(); + sqlQuery1.focus(); + return false; + } // end if + } // end if + + // Confirms a "DROP/DELETE/ALTER/TRUNCATE" statement + // + // TODO: find a way (if possible) to use the parser-analyser + // for this kind of verification + // For now, I just added a ^ to check for the statement at + // beginning of expression + + var do_confirm_re_0 = new RegExp('^\\s*DROP\\s+(IF EXISTS\\s+)?(TABLE|DATABASE|PROCEDURE)\\s', 'i'); + var do_confirm_re_1 = new RegExp('^\\s*ALTER\\s+TABLE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+DROP\\s', 'i'); + var do_confirm_re_2 = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i'); + var do_confirm_re_3 = new RegExp('^\\s*TRUNCATE\\s', 'i'); + + if (do_confirm_re_0.test(sqlQuery1.value) || + do_confirm_re_1.test(sqlQuery1.value) || + do_confirm_re_2.test(sqlQuery1.value) || + do_confirm_re_3.test(sqlQuery1.value)) { + var message; + if (sqlQuery1.value.length > 100) { + message = sqlQuery1.value.substr(0, 100) + '\n ...'; + } else { + message = sqlQuery1.value; + } + var is_confirmed = confirm($.sprintf(PMA_messages.strDoYouReally, message)); + // statement is confirmed -> update the + // "is_js_confirmed" form field so the confirm test won't be + // run on the server side and allows to submit the form + if (is_confirmed) { + theForm1.elements['is_js_confirmed'].value = 1; + return true; + } + // statement is rejected -> do not submit the form + else { + window.focus(); + sqlQuery1.focus(); + return false; + } // end if (handle confirm box result) + } // end if (display confirm box) + + return true; +} // end of the 'confirmQuery()' function + +/** + * Displays an error message if the user submitted the sql query form with no + * sql query, else checks for "DROP/DELETE/ALTER" statements + * + * @param object the form + * + * @return boolean always false + * + * @see confirmQuery() + */ +function checkSqlQuery(theForm) +{ + var sqlQuery = theForm.elements['sql_query']; + var isEmpty = 1; + var space_re = new RegExp('\\s+'); + if (typeof(theForm.elements['sql_file']) != 'undefined' && + theForm.elements['sql_file'].value.replace(space_re, '') !== '') { + return true; + } + if (typeof(theForm.elements['sql_localfile']) != 'undefined' && + theForm.elements['sql_localfile'].value.replace(space_re, '') !== '') { + return true; + } + if (isEmpty && typeof(theForm.elements['id_bookmark']) != 'undefined' && + (theForm.elements['id_bookmark'].value !== null || theForm.elements['id_bookmark'].value !== '') && + theForm.elements['id_bookmark'].selectedIndex !== 0) { + return true; + } + // Checks for "DROP/DELETE/ALTER" statements + if (sqlQuery.value.replace(space_re, '') !== '') { + if (confirmQuery(theForm, sqlQuery)) { + return true; + } else { + return false; + } + } + theForm.reset(); + isEmpty = 1; + + if (isEmpty) { + sqlQuery.select(); + alert(PMA_messages.strFormEmpty); + sqlQuery.focus(); + return false; + } + + return true; +} // end of the 'checkSqlQuery()' function + +/** + * Check if a form's element is empty. + * An element containing only spaces is also considered empty + * + * @param object the form + * @param string the name of the form field to put the focus on + * + * @return boolean whether the form field is empty or not + */ +function emptyCheckTheField(theForm, theFieldName) +{ + var theField = theForm.elements[theFieldName]; + var space_re = new RegExp('\\s+'); + return (theField.value.replace(space_re, '') === '') ? 1 : 0; +} // end of the 'emptyCheckTheField()' function + + +/** + * Check whether a form field is empty or not + * + * @param object the form + * @param string the name of the form field to put the focus on + * + * @return boolean whether the form field is empty or not + */ +function emptyFormElements(theForm, theFieldName) +{ + var theField = theForm.elements[theFieldName]; + var isEmpty = emptyCheckTheField(theForm, theFieldName); + + + return isEmpty; +} // end of the 'emptyFormElements()' function + + +/** + * Ensures a value submitted in a form is numeric and is in a range + * + * @param object the form + * @param string the name of the form field to check + * @param integer the minimum authorized value + * @param integer the maximum authorized value + * + * @return boolean whether a valid number has been submitted or not + */ +function checkFormElementInRange(theForm, theFieldName, message, min, max) +{ + var theField = theForm.elements[theFieldName]; + var val = parseInt(theField.value, 10); + + if (typeof(min) == 'undefined') { + min = 0; + } + if (typeof(max) == 'undefined') { + max = Number.MAX_VALUE; + } + + // It's not a number + if (isNaN(val)) { + theField.select(); + alert(PMA_messages.strEnterValidNumber); + theField.focus(); + return false; + } + // It's a number but it is not between min and max + else if (val < min || val > max) { + theField.select(); + alert($.sprintf(message, val)); + theField.focus(); + return false; + } + // It's a valid number + else { + theField.value = val; + } + return true; + +} // end of the 'checkFormElementInRange()' function + + +function checkTableEditForm(theForm, fieldsCnt) +{ + // TODO: avoid sending a message if user just wants to add a line + // on the form but has not completed at least one field name + + var atLeastOneField = 0; + var i, elm, elm2, elm3, val, id; + + for (i = 0; i < fieldsCnt; i++) { + id = "#field_" + i + "_2"; + elm = $(id); + val = elm.val(); + if (val == 'VARCHAR' || val == 'CHAR' || val == 'BIT' || val == 'VARBINARY' || val == 'BINARY') { + elm2 = $("#field_" + i + "_3"); + val = parseInt(elm2.val(), 10); + elm3 = $("#field_" + i + "_1"); + if (isNaN(val) && elm3.val() !== "") { + elm2.select(); + alert(PMA_messages.strEnterValidLength); + elm2.focus(); + return false; + } + } + + if (atLeastOneField === 0) { + id = "field_" + i + "_1"; + if (!emptyCheckTheField(theForm, id)) { + atLeastOneField = 1; + } + } + } + if (atLeastOneField === 0) { + var theField = theForm.elements["field_0_1"]; + alert(PMA_messages.strFormEmpty); + theField.focus(); + return false; + } + + // at least this section is under jQuery + if ($("input.textfield[name='table']").val() === "") { + alert(PMA_messages.strFormEmpty); + $("input.textfield[name='table']").focus(); + return false; + } + + + return true; +} // enf of the 'checkTableEditForm()' function + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $('input:checkbox.checkall').die('click'); +}); +AJAX.registerOnload('functions.js', function () { + /** + * Row marking in horizontal mode (use "live" so that it works also for + * next pages reached via AJAX); a tr may have the class noclick to remove + * this behavior. + */ + + $('input:checkbox.checkall').live('click', function (e) { + var $tr = $(this).closest('tr'); + + // make the table unselectable (to prevent default highlighting when shift+click) + //$tr.parents('table').noSelect(); + + if (!e.shiftKey || last_clicked_row == -1) { + // usual click + + // XXX: FF fires two click events for
      ', {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})); + if (! checkSqlQuery($fake_form[0])) { + return false; + } + $fake_form.appendTo($('body')).submit(); + }); + + $("input#sql_query_edit_discard").live('click', function () { + $('div#inline_editor_outer').siblings('code.sql').show(); + $('div#inline_editor_outer').remove(); + }); + + $('input.sqlbutton').click(function (evt) { + insertQuery(evt.target.id); + return false; + }); + + $("#export_type").change(function () { + if ($("#export_type").val() == 'svg') { + $("#show_grid_opt").prop("disabled", true); + $("#orientation_opt").prop("disabled", true); + $("#with_doc").prop("disabled", true); + $("#show_table_dim_opt").removeProp("disabled"); + $("#all_tables_same_width").removeProp("disabled"); + $("#paper_opt").removeProp("disabled"); + $("#show_color_opt").removeProp("disabled"); + //$(this).css("background-color","yellow"); + } else if ($("#export_type").val() == 'dia') { + $("#show_grid_opt").prop("disabled", true); + $("#with_doc").prop("disabled", true); + $("#show_table_dim_opt").prop("disabled", true); + $("#all_tables_same_width").prop("disabled", true); + $("#paper_opt").removeProp("disabled"); + $("#show_color_opt").removeProp("disabled"); + $("#orientation_opt").removeProp("disabled"); + } else if ($("#export_type").val() == 'eps') { + $("#show_grid_opt").prop("disabled", true); + $("#orientation_opt").removeProp("disabled"); + $("#with_doc").prop("disabled", true); + $("#show_table_dim_opt").prop("disabled", true); + $("#all_tables_same_width").prop("disabled", true); + $("#paper_opt").prop("disabled", true); + $("#show_color_opt").prop("disabled", true); + } else if ($("#export_type").val() == 'pdf') { + $("#show_grid_opt").removeProp("disabled"); + $("#orientation_opt").removeProp("disabled"); + $("#with_doc").removeProp("disabled"); + $("#show_table_dim_opt").removeProp("disabled"); + $("#all_tables_same_width").removeProp("disabled"); + $("#paper_opt").removeProp("disabled"); + $("#show_color_opt").removeProp("disabled"); + } else { + // nothing + } + }); + + if ($('#input_username')) { + if ($('#input_username').val() === '') { + $('#input_username').focus(); + } else { + $('#input_password').focus(); + } + } +}); + +/** + * 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 = $('#sql_query_edit').css('height'); + codemirror_inline_editor = CodeMirror.fromTextArea($inline_editor[0], { + lineNumbers: true, + matchBrackets: true, + indentUnit: 4, + mode: "text/x-mysql", + lineWrapping: true + }); + codemirror_inline_editor.getScrollerElement().style.height = height; + codemirror_inline_editor.refresh(); + codemirror_inline_editor.focus(); + $(codemirror_inline_editor.getWrapperElement()).bind( + 'keydown', + catchKeypressesFromSqlTextboxes + ); + } else { + $inline_editor.focus().bind( + 'keydown', + catchKeypressesFromSqlTextboxes + ); + } + } +} + +function catchKeypressesFromSqlTextboxes(event) { + // ctrl-enter is 10 in chrome and ie, but 13 in ff + if (event.ctrlKey && (event.keyCode == 13 || event.keyCode == 10)) { + if ($('#sql_query_edit').length > 0) { + $("#sql_query_edit_save").trigger('click'); + } else if ($('#sqlquery').length > 0) { + $("#button_submit_query").trigger('click'); + } + } +} + +/** + * Adds doc link to single highlighted SQL element + */ +function PMA_doc_add($elm, params) +{ + var url = $.sprintf( + 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); + } + } + }); +} + +/** + * 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 + * @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) +{ + /** + * @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; + } + // Create a parent element for the AJAX messages, if necessary + if ($('#loading_parent').length === 0) { + $('
      ') + .prependTo("body"); + } + // 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('.dismissable')) { + $(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('.dismissable')) { + if ($.isFunction($this_msgbox.tooltip)) { + $this_msgbox.tooltip('destroy'); + } + } else { + $this_msgbox.remove(); + } + } +} + +// 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() + */ + $('span.ajax_notification.dismissable').live('click', 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 + */ + $('span.ajax_notification a, span.ajax_notification button, span.ajax_notification input') + .live('mouseover', function () { + $(this).parents('span.ajax_notification').tooltip('disable'); + }) + .live('mouseout', function () { + $(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 with jqplot. Used in sql.js + * and in server_status_monitor.js + */ +function PMA_createProfilingChartJqplot(target, data) +{ + return $.jqplot(target, [data], + { + seriesDefaults: { + renderer: $.jqplot.PieRenderer, + rendererOptions: { + showDataLabels: true + } + }, + highlighter: { + show: true, + tooltipLocation: 'se', + sizeAdjust: 0, + tooltipAxes: 'pieref', + useAxesFormatters: false, + formatString: '%s, %.9Ps' + }, + legend: { + show: true, + location: 'e' + }, + // from http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines#Color_Palette + seriesColors: [ + '#fce94f', + '#fcaf3e', + '#e9b96e', + '#8ae234', + '#729fcf', + '#ad7fa8', + '#ef2929', + '#eeeeec', + '#888a85', + '#c4a000', + '#ce5c00', + '#8f5902', + '#4e9a06', + '#204a87', + '#5c3566', + '#a40000', + '#babdb6', + '#2e3436' + ] + } + ); +} + +/** + * 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, 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, endBlock; + // How much to indent in the current line + var indentLevel = 0; + // Holds the "root-level" statements + var statementPart, 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 indentatin 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 & identation++ + //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 + */ + +jQuery.fn.PMA_confirm = function (question, url, callbackFn) { + var confirmState = PMA_commonParams.get('confirm'); + // when the Confirm directive is set to false in config.inc.php + // and not changed in user prefs, confirmState is "" + // when it's unticked in user prefs, confirmState is 1 + if (confirmState === "" || confirmState === "1") { + // 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 = {}; + button_options[PMA_messages.strOK] = function () { + $(this).dialog("close"); + + if ($.isFunction(callbackFn)) { + callbackFn.call(this, url); + } + }; + button_options[PMA_messages.strCancel] = function () { + $(this).dialog("close"); + }; + + $('
      ', {'id': 'confirm_dialog'}) + .prepend(question) + .dialog({ + buttons: button_options, + close: function () { + $(this).remove(); + }, + modal: true + }); +}; + +/** + * jQuery function to sort a table's body after a new row has been appended to it. + * Also fixes the even/odd classes of the table rows at the end. + * + * @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; + }); + + //Re-check the classes of each row + $(this).find('tr:odd') + .removeClass('even').addClass('odd') + .end() + .find('tr:even') + .removeClass('odd').addClass('even'); + }); +}; + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $("#create_table_form_minimal.ajax").die('submit'); + $("form.create_table_form.ajax").die('submit'); + $("form.create_table_form.ajax input[name=submit_num_fields]").die('click'); + $("form.create_table_form.ajax input").die('keyup'); +}); + +/** + * jQuery coding for 'Create Table'. Used on db_operations.php, + * db_structure.php and db_tracking.php (i.e., wherever + * libraries/display_create_table.lib.php is used) + * + * Attach Ajax Event handlers for Create Table + */ +AJAX.registerOnload('functions.js', function () { + /** + * Attach event handler for submission of create table form (save) + */ + $("form.create_table_form.ajax").live('submit', 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_ajaxShowMessage(PMA_messages.strProcessingRequest); + PMA_prepareForAjaxRequest($form); + //User wants to submit the form + $.post($form.attr('action'), $form.serialize() + "&do_save_data=1", function (data) { + if (data.success === true) { + $('#properties_message') + .removeClass('error') + .html(''); + PMA_ajaxShowMessage(data.message); + // Only if the create table dialog (distinct panel) exists + if ($("#create_table_dialog").length > 0) { + $("#create_table_dialog").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(); + } else { + PMA_ajaxShowMessage( + '
      ' + data.error + '
      ', + false + ); + } + }); // end $.post() + } // end if (checkTableEditForm() ) + }); // end create table form (save) + + /** + * Attach event handler for create table form (add fields) + */ + $("form.create_table_form.ajax input[name=submit_num_fields]").live('click', function (event) { + event.preventDefault(); + /** + * @var the_form object referring to the create table form + */ + var $form = $(this).closest('form'); + + 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() + "&submit_num_fields=1", function (data) { + if (data.success) { + $("#page_content").html(data.message); + PMA_highlightSQL($('#page_content')); + PMA_verifyColumnsProperties(); + PMA_ajaxRemoveMessage($msgbox); + } else { + PMA_ajaxShowMessage(data.error); + } + }); //end $.post() + }); // end create table form (add fields) + + $("form.create_table_form.ajax input").live('keydown', function (event) { + if (event.keyCode == 13) { + event.preventDefault(); + event.stopImmediatePropagation(); + $(this) + .closest('form') + .append('') + .submit(); + } + }); +}); + + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $("#copyTable.ajax").die('submit'); + $("#moveTableForm").die('submit'); + $("#tableOptionsForm").die('submit'); + $("#tbl_maintenance li a.maintain_action.ajax").die('click'); +}); +/** + * jQuery coding for 'Table operations'. Used on tbl_operations.php + * Attach Ajax Event handlers for Table operations + */ +AJAX.registerOnload('functions.js', function () { + /** + *Ajax action for submitting the "Copy table" + **/ + $("#copyTable.ajax").live('submit', function (event) { + event.preventDefault(); + var $form = $(this); + PMA_prepareForAjaxRequest($form); + $.post($form.attr('action'), $form.serialize() + "&submit_copy=Go", function (data) { + if (data.success === true) { + if ($form.find("input[name='switch_to_new']").prop('checked')) { + PMA_commonParams.set( + 'db', + data.db + ); + 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" + */ + $("#moveTableForm").live('submit', function (event) { + event.preventDefault(); + var $form = $(this); + var db = $form.find('select[name=target_db]').val(); + var tbl = $form.find('input[name=new_name]').val(); + PMA_prepareForAjaxRequest($form); + $.post($form.attr('action'), $form.serialize() + "&submit_move=1", function (data) { + if (data.success === true) { + PMA_commonParams.set('db', db); + PMA_commonParams.set('table', tbl); + PMA_commonActions.refreshMain(false, 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" + */ + $("#tableOptionsForm").live('submit', function (event) { + event.preventDefault(); + event.stopPropagation(); + var $form = $(this); + var $tblNameField = $form.find('input[name=new_name]'); + if ($tblNameField.val() !== $tblNameField[0].defaultValue) { + // reload page and navigation if the table has been renamed + PMA_prepareForAjaxRequest($form); + var tbl = $tblNameField.val(); + $.post($form.attr('action'), $form.serialize(), function (data) { + if (data.success === true) { + PMA_commonParams.set('table', tbl); + PMA_commonActions.refreshMain(false, function () { + $('#page_content').html(data.message); + PMA_highlightSQL($('#page_content')); + }); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } else { + $form.removeClass('ajax').submit().addClass('ajax'); + } + }); + + /** + *Ajax events for actions in the "Table maintenance" + **/ + $("#tbl_maintenance li a.maintain_action.ajax").live('click', function (event) { + event.preventDefault(); + if ($("#sqlqueryresults").length !== 0) { + $("#sqlqueryresults").remove(); + } + if ($("#result_query").length !== 0) { + $("#result_query").remove(); + } + //variables which stores the common attributes + $.post($(this).attr('href'), { ajax_request: 1 }, function (data) { + function scrollToTop() { + $('html, body').animate({ scrollTop: 0 }); + } + if (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 (data.success === true) { + var $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 { + var $temp_div = $("
      "); + $temp_div.html(data.error); + var $error = $temp_div.find("code").addClass("error"); + PMA_ajaxShowMessage($error, false); + } + }); // end $.post() + });//end of table maintanance ajax click +}); //end $(document).ready for 'Table operations' + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $("#drop_db_anchor.ajax").die('click'); +}); +/** + * Attach Ajax event handlers for Drop Database. Moved here from db_structure.js + * as it was also required on db_create.php + */ +AJAX.registerOnload('functions.js', function () { + $("#drop_db_anchor.ajax").live('click', function (event) { + event.preventDefault(); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_messages.strDropDatabaseStrongWarning + ' '; + question += $.sprintf( + PMA_messages.strDoYouReally, + 'DROP DATABASE ' + escapeHtml(PMA_commonParams.get('db')) + ); + $(this).PMA_confirm(question, $(this).attr('href'), function (url) { + PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + $.get(url, {'is_js_confirmed': '1', 'ajax_request': true}, function (data) { + if (data.success) { + //Database deleted successfully, refresh both the frames + PMA_reloadNavigation(); + PMA_commonParams.set('db', ''); + PMA_commonActions.refreshMain( + 'server_databases.php', + function () { + PMA_ajaxShowMessage(data.message); + } + ); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }); + }); +}); // end of $() for Drop Database + +/** + * 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; +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $('#change_password_anchor.ajax').die('click'); +}); +/** + * Attach Ajax event handlers for 'Change Password' on index.php + */ +AJAX.registerOnload('functions.js', function () { + + /** + * Attach Ajax event handler on the change password anchor + */ + $('#change_password_anchor.ajax').live('click', 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() + '&change_pw=' + this_value, function (data) { + if (data.success === true) { + $("#page_content").prepend(data.message); + PMA_highlightSQL($('#page_content')); + $("#change_password_dialog").hide().remove(); + $("#edit_user_dialog").dialog("close").remove(); + PMA_ajaxRemoveMessage($msgbox); + } + else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }; + + button_options[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + $.get($(this).attr('href'), {'ajax_request': true}, function (data) { + if (data.success) { + $('
      ') + .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(); + displayPasswordGenerateButton(); + $('#fieldset_change_password_footer').hide(); + PMA_ajaxRemoveMessage($msgbox); + $('#change_password_form').bind('submit', function (e) { + e.preventDefault(); + $(this) + .closest('.ui-dialog') + .find('.ui-dialog-buttonpane .ui-button') + .first() + .click(); + }); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // 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 () { + $("select.column_type").die('change'); + $("select.default_type").die('change'); + $('input.allow_null').die('change'); +}); +/** + * 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 live() to work also in the Create Table dialog + $("select.column_type").live('change', function () { + PMA_showNoticeForEnum($(this)); + }); + $("select.default_type").live('change', function () { + PMA_hideShowDefaultValue($(this)); + }); + $('input.allow_null').live('change', function () { + PMA_validateDefaultValue($(this)); + }); +}); + +function PMA_verifyColumnsProperties() +{ + $("select.column_type").each(function () { + PMA_showNoticeForEnum($(this)); + }); + $("select.default_type").each(function () { + PMA_hideShowDefaultValue($(this)); + }); +} + +/** + * Hides/shows the default value input field, depending on the default type + * Ticks the NULL checkbox if NULL is chosen as default value. + */ +function PMA_hideShowDefaultValue($default_type) +{ + if ($default_type.val() == 'USER_DEFINED') { + $default_type.siblings('.default_value').show().focus(); + } else { + $default_type.siblings('.default_value').hide(); + if ($default_type.val() == 'NULL') { + var $null_checkbox = $default_type.closest('tr').find('.allow_null'); + $null_checkbox.prop('checked', true); + } + } +} + +/** + * 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'); + } + } +} + + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $("a.open_enum_editor").die('click'); + $("input.add_value").die('click'); + $("#enum_editor td.drop").die('click'); +}); +/** + * @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 () { + $("a.open_enum_editor").live('click', 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/, + '"' + 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, next, 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.png'); + 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.png') + + 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, + 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( + $.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; + }); + + // When "add a new value" is clicked, append an empty text field + $("input.add_value").live('click', 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.png') + + "" + ) + .find('tr:last') + .show('fast'); + } + }); + + // Removes the specified row from the enum editor + $("#enum_editor td.drop").live('click', 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_type = $("#select_index_type"); + + // Index is a primary key + if ($the_idx_type.find("option:selected").val() == 'PRIMARY') { + $the_idx_name.val('PRIMARY'); + $the_idx_name.prop("disabled", true); + } + + // Other cases + 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 () { + $('#index_frm input[type=submit]').die('click'); +}); +AJAX.registerOnload('functions.js', function () { + /** + * Handler for adding more columns to an index in the editor + */ + $('#index_frm input[type=submit]').live('click', function (event) { + event.preventDefault(); + var rows_to_add = $(this) + .closest('fieldset') + .find('.slider') + .slider('value'); + while (rows_to_add--) { + var $newrow = $('#index_columns') + .find('tbody > tr:first') + .clone() + .appendTo( + $('#index_columns').find('tbody') + ); + $newrow.find(':input').each(function () { + $(this).val(''); + }); + // focus index size input on column picked + $newrow.find('select').change(function () { + if ($(this).find("option:selected").val() === '') { + return true; + } + $(this).closest("tr").find("input").focus(); + }); + } + }); +}); + +function indexEditorDialog(url, title, callback_success, callback_failure) +{ + /*Remove the hidden dialogs if there are*/ + if ($('#edit_index_dialog').length !== 0) { + $('#edit_index_dialog').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() + "&do_save_data=1", function (data) { + if ($("#sqlqueryresults").length !== 0) { + $("#sqlqueryresults").remove(); + } + if (data.success === true) { + PMA_ajaxShowMessage(data.message); + if ($('#result_query').length) { + $('#result_query').remove(); + } + if (data.sql_query) { + $('
      ') + .html(data.sql_query) + .prependTo('#page_content'); + PMA_highlightSQL($('#page_content')); + } + $("#result_query .notice").remove(); + $("#result_query").prepend(data.message); + /*Reload the field form*/ + $("#table_index").remove(); + var $temp_div = $("
      ").append(data.index_table); + $temp_div.find("#table_index").insertAfter("#index_header"); + if ($("#edit_index_dialog").length > 0) { + $("#edit_index_dialog").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.strCancel] = function () { + $(this).dialog('close'); + }; + var $msgbox = PMA_ajaxShowMessage(); + $.get("tbl_indexes.php", url, 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 + $div + .append(data.message) + .dialog({ + title: title, + width: 450, + open: PMA_verifyColumnsProperties, + modal: true, + buttons: button_options, + close: function () { + $(this).remove(); + } + }); + checkIndexType(); + checkIndexName("index_frm"); + PMA_showHints($div); + // Add a slider for selecting how many columns to add to the index + $div.find('.slider').slider({ + animate: true, + value: 1, + min: 1, + max: 16, + slide: function (event, ui) { + $(this).closest('fieldset').find('input[type=submit]').val( + $.sprintf(PMA_messages.strAddToIndex, ui.value) + ); + } + }); + // focus index size input on column picked + $div.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 = $div.find('input#input_index_name'); + input.val() || input.focus(); + } + }); // end $.get() +} + +/** + * Function to display tooltips that were + * generated on the PHP side by PMA_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 inital page load +$(function () { + // Initialise the menu resize plugin + $('#topmenu').menuResizer(PMA_mainMenuResizerCallback); + // register resize event + $(window).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, url, removeClass, 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'; + } + $.post(url, {'ajax_request': true}, function (data) { + if (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').unbind('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 () { + $('.vpointer').die('hover'); + $('.vmarker').die('click'); + $('#pageselector').die('change'); + $('a.formLinkSubmit').die('click'); + $('#update_recent_tables').unbind('ready'); +}); +/** + * Vertical pointer + */ +AJAX.registerOnload('functions.js', function () { + $('.vpointer').live('hover', + //handlerInOut + function (e) { + var $this_td = $(this); + var row_num = PMA_getRowNumber($this_td.attr('class')); + // for all td of the same vertical row, toggle hover + $('.vpointer').filter('.row_' + row_num).toggleClass('hover'); + } + ); + + + /** + * Vertical marker + */ + $('.vmarker').live('click', function (e) { + // do not trigger when clicked on anchor + if ($(e.target).is('a, img, a *')) { + return; + } + + var $this_td = $(this); + var row_num = PMA_getRowNumber($this_td.attr('class')); + + // XXX: FF fires two click events for
      '; + $retval .= $this->getMessage(); + } + if ($this->_isEnabled && empty($_REQUEST['recent_table'])) { + $retval .= $this->_addRecentTable( + $GLOBALS['db'], + $GLOBALS['table'] + ); + } + } + return $retval; + } + + /** + * Returns the message to be displayed at the top of + * the page, including the executed SQL query, if any. + * + * @return string + */ + public function getMessage() + { + $retval = ''; + $message = ''; + if (! empty($GLOBALS['message'])) { + $message = $GLOBALS['message']; + unset($GLOBALS['message']); + } else if (! empty($_REQUEST['message'])) { + $message = $_REQUEST['message']; + } + if (! empty($message)) { + if (isset($GLOBALS['buffer_message'])) { + $buffer_message = $GLOBALS['buffer_message']; + } + $retval .= PMA_Util::getMessage($message); + if (isset($buffer_message)) { + $GLOBALS['buffer_message'] = $buffer_message; + } + } + return $retval; + } + + /** + * Sends out the HTTP headers + * + * @return void + */ + public function sendHttpHeaders() + { + $https = $GLOBALS['PMA_Config']->isHttps(); + $mapTilesUrls = ' *.tile.openstreetmap.org *.tile.opencyclemap.org'; + + /** + * Sends http headers + */ + $GLOBALS['now'] = gmdate('D, d M Y H:i:s') . ' GMT'; + if (! defined('TESTSUITE')) { + /* Prevent against ClickJacking by disabling framing */ + if (! $GLOBALS['cfg']['AllowThirdPartyFraming']) { + header( + 'X-Frame-Options: DENY' + ); + } + header( + "X-Content-Security-Policy: default-src 'self' " + . 'https://www.google.com ' + . $GLOBALS['cfg']['CSPAllow'] . ';' + . "options inline-script eval-script;" + . "img-src 'self' data: " + . $GLOBALS['cfg']['CSPAllow'] + . ($https ? "" : $mapTilesUrls) + // for reCAPTCHA + . " https://www.google.com" + . ";" + ); + if (PMA_USR_BROWSER_AGENT == 'SAFARI' + && PMA_USR_BROWSER_VER < '6.0.0' + ) { + header( + "X-WebKit-CSP: allow 'self' " + . 'https://www.google.com ' + . $GLOBALS['cfg']['CSPAllow'] . ';' + . "options inline-script eval-script;" + . "img-src 'self' data: " + . $GLOBALS['cfg']['CSPAllow'] + . ($https ? "" : $mapTilesUrls) + // for reCAPTCHA + . " https://www.google.com" + . ";" + ); + } else { + header( + "X-WebKit-CSP: default-src 'self' " + . 'https://www.google.com ' + . $GLOBALS['cfg']['CSPAllow'] . ';' + . "script-src 'self' " + . 'https://www.google.com ' + . $GLOBALS['cfg']['CSPAllow'] + . " 'unsafe-inline' 'unsafe-eval';" + . "style-src 'self' 'unsafe-inline' " + . 'https://www.google.com ' + . ';' + . "img-src 'self' data: " + . $GLOBALS['cfg']['CSPAllow'] + . ($https ? "" : $mapTilesUrls) + // for reCAPTCHA + . " https://www.google.com" + . ";" + ); + } + } + PMA_noCacheHeader(); + if (! defined('IS_TRANSFORMATION_WRAPPER') && ! defined('TESTSUITE')) { + // Define the charset to be used + header('Content-Type: text/html; charset=utf-8'); + } + $this->_headerIsSent = true; + } + + /** + * Returns the DOCTYPE and the start HTML tag + * + * @return string DOCTYPE and HTML tags + */ + private function _getHtmlStart() + { + $lang = $GLOBALS['available_languages'][$GLOBALS['lang']][1]; + $dir = $GLOBALS['text_dir']; + + $retval = ""; + $retval .= ""; + + return $retval; + } + + /** + * Returns the META tags + * + * @return string the META tags + */ + private function _getMetaTags() + { + $retval = ''; + $retval .= ''; + $retval .= ''; + if (! $GLOBALS['cfg']['AllowThirdPartyFraming']) { + $retval .= ''; + } + return $retval; + } + + /** + * Returns the LINK tags for the favicon and the stylesheets + * + * @return string the LINK tags + */ + private function _getLinkTags() + { + $retval = '' + . ''; + // stylesheets + $basedir = defined('PMA_PATH_TO_BASEDIR') ? PMA_PATH_TO_BASEDIR : ''; + $common_url = PMA_URL_getCommon(array('server' => $GLOBALS['server'])); + $theme_id = $GLOBALS['PMA_Config']->getThemeUniqueValue(); + $theme_path = $GLOBALS['pmaThemePath']; + + if ($this->_isPrintView) { + $retval .= ''; + } else { + $retval .= ''; + $retval .= ''; + } + + return $retval; + } + + /** + * Returns the TITLE tag + * + * @return string the TITLE tag + */ + public function getTitleTag() + { + $retval = ""; + $retval .= $this->_getPageTitle(); + $retval .= ""; + return $retval; + } + + /** + * If the page is missing the title, this function + * will set it to something reasonable + * + * @return string + */ + private function _getPageTitle() + { + if (empty($this->_title)) { + if ($GLOBALS['server'] > 0) { + if (! empty($GLOBALS['table'])) { + $temp_title = $GLOBALS['cfg']['TitleTable']; + } else if (! empty($GLOBALS['db'])) { + $temp_title = $GLOBALS['cfg']['TitleDatabase']; + } elseif (! empty($GLOBALS['cfg']['Server']['host'])) { + $temp_title = $GLOBALS['cfg']['TitleServer']; + } else { + $temp_title = $GLOBALS['cfg']['TitleDefault']; + } + $this->_title = htmlspecialchars( + PMA_Util::expandUserString($temp_title) + ); + } else { + $this->_title = 'phpMyAdmin'; + } + } + return $this->_title; + } + + /** + * Returns the close tag to the HEAD + * and the start tag for the BODY + * + * @return string HEAD and BODY tags + */ + private function _getBodyStart() + { + $retval = "_bodyId)) { + $retval .= " id='" . $this->_bodyId . "'"; + } + $retval .= ">"; + return $retval; + } + + /** + * Returns some warnings to be displayed at the top of the page + * + * @return string The warnings + */ + private function _getWarnings() + { + $retval = ''; + if ($this->_warningsEnabled) { + $retval .= ""; + } + return $retval; + } + + /** + * Add recently used table and reload the navigation. + * + * @param string $db Database name where the table is located. + * @param string $table The table name + * + * @return string + */ + private function _addRecentTable($db, $table) + { + $retval = ''; + if ($this->_menuEnabled + && strlen($table) + && $GLOBALS['cfg']['NumRecentTables'] > 0 + ) { + $tmp_result = PMA_RecentTable::getInstance()->add($db, $table); + if ($tmp_result === true) { + $params = array('ajax_request' => true, 'recent_table' => true); + $url = 'index.php' . PMA_URL_getCommon($params); + $retval = ''; + } else { + $error = $tmp_result; + $retval = $error->getDisplay(); + } + } + return $retval; + } +} + +?> diff --git a/sources/libraries/Index.class.php b/sources/libraries/Index.class.php new file mode 100644 index 0000000..e6e9755 --- /dev/null +++ b/sources/libraries/Index.class.php @@ -0,0 +1,924 @@ +set($params); + } + + /** + * Creates(if not already created) and returns the corresponding Index object + * + * @param string $schema database name + * @param string $table table name + * @param string $index_name index name + * + * @return PMA_Index corresponding Index object + */ + static public function singleton($schema, $table, $index_name = '') + { + PMA_Index::_loadIndexes($table, $schema); + if (! isset(PMA_Index::$_registry[$schema][$table][$index_name])) { + $index = new PMA_Index; + if (strlen($index_name)) { + $index->setName($index_name); + PMA_Index::$_registry[$schema][$table][$index->getName()] = $index; + } + return $index; + } else { + return PMA_Index::$_registry[$schema][$table][$index_name]; + } + } + + /** + * returns an array with all indexes from the given table + * + * @param string $table table + * @param string $schema schema + * + * @return array array of indexes + */ + static public function getFromTable($table, $schema) + { + PMA_Index::_loadIndexes($table, $schema); + + if (isset(PMA_Index::$_registry[$schema][$table])) { + return PMA_Index::$_registry[$schema][$table]; + } else { + return array(); + } + } + + /** + * return primary if set, false otherwise + * + * @param string $table table + * @param string $schema schema + * + * @return mixed primary index or false if no one exists + */ + static public function getPrimary($table, $schema) + { + PMA_Index::_loadIndexes($table, $schema); + + if (isset(PMA_Index::$_registry[$schema][$table]['PRIMARY'])) { + return PMA_Index::$_registry[$schema][$table]['PRIMARY']; + } else { + return false; + } + } + + /** + * Load index data for table + * + * @param string $table table + * @param string $schema schema + * + * @return boolean whether loading was successful + */ + static private function _loadIndexes($table, $schema) + { + if (isset(PMA_Index::$_registry[$schema][$table])) { + return true; + } + + $_raw_indexes = $GLOBALS['dbi']->getTableIndexes($schema, $table); + foreach ($_raw_indexes as $_each_index) { + $_each_index['Schema'] = $schema; + $keyName = $_each_index['Key_name']; + if (! isset(PMA_Index::$_registry[$schema][$table][$keyName])) { + $key = new PMA_Index($_each_index); + PMA_Index::$_registry[$schema][$table][$keyName] = $key; + } else { + $key = PMA_Index::$_registry[$schema][$table][$keyName]; + } + + $key->addColumn($_each_index); + } + + return true; + } + + /** + * Add column to index + * + * @param array $params column params + * + * @return void + */ + public function addColumn($params) + { + if (strlen($params['Column_name'])) { + $this->_columns[$params['Column_name']] = new PMA_Index_Column($params); + } + } + + /** + * Adds a list of columns to the index + * + * @param array $columns array containing details about the columns + * + * @return void + */ + public function addColumns($columns) + { + $_columns = array(); + + if (isset($columns['names'])) { + // coming from form + // $columns[names][] + // $columns[sub_parts][] + foreach ($columns['names'] as $key => $name) { + $sub_part = isset($columns['sub_parts'][$key]) + ? $columns['sub_parts'][$key] : ''; + $_columns[] = array( + 'Column_name' => $name, + 'Sub_part' => $sub_part, + ); + } + } else { + // coming from SHOW INDEXES + // $columns[][name] + // $columns[][sub_part] + // ... + $_columns = $columns; + } + + foreach ($_columns as $column) { + $this->addColumn($column); + } + } + + /** + * Returns true if $column indexed in this index + * + * @param string $column the column + * + * @return boolean true if $column indexed in this index + */ + public function hasColumn($column) + { + return isset($this->_columns[$column]); + } + + /** + * Sets index details + * + * @param array $params index details + * + * @return void + */ + public function set($params) + { + if (isset($params['columns'])) { + $this->addColumns($params['columns']); + } + if (isset($params['Schema'])) { + $this->_schema = $params['Schema']; + } + if (isset($params['Table'])) { + $this->_table = $params['Table']; + } + if (isset($params['Key_name'])) { + $this->_name = $params['Key_name']; + } + if (isset($params['Index_type'])) { + $this->_type = $params['Index_type']; + } + if (isset($params['Comment'])) { + $this->_remarks = $params['Comment']; + } + if (isset($params['Index_comment'])) { + $this->_comment = $params['Index_comment']; + } + if (isset($params['Non_unique'])) { + $this->_non_unique = $params['Non_unique']; + } + if (isset($params['Packed'])) { + $this->_packed = $params['Packed']; + } + if ('PRIMARY' == $this->_name) { + $this->_choice = 'PRIMARY'; + } elseif ('FULLTEXT' == $this->_type) { + $this->_choice = 'FULLTEXT'; + } elseif ('SPATIAL' == $this->_type) { + $this->_choice = 'SPATIAL'; + } elseif ('0' == $this->_non_unique) { + $this->_choice = 'UNIQUE'; + } else { + $this->_choice = 'INDEX'; + } + } + + /** + * Returns the number of columns of the index + * + * @return integer the number of the columns + */ + public function getColumnCount() + { + return count($this->_columns); + } + + /** + * Returns the index comment + * + * @return string index comment + */ + public function getComment() + { + return $this->_comment; + } + + /** + * Returns index remarks + * + * @return string index remarks + */ + public function getRemarks() + { + return $this->_remarks; + } + + /** + * Returns concatenated remarks and comment + * + * @return string concatenated remarks and comment + */ + public function getComments() + { + $comments = $this->getRemarks(); + if (strlen($comments)) { + $comments .= "\n"; + } + $comments .= $this->getComment(); + + return $comments; + } + + /** + * Returns index type ((BTREE, SPATIAL, FULLTEXT, HASH, RTREE) + * + * @return string index type + */ + public function getType() + { + return $this->_type; + } + + /** + * Returns index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT) + * + * @return string index choice + */ + public function getChoice() + { + return $this->_choice; + } + + /** + * Return a list of all index choices + * + * @return array index choices + */ + static public function getIndexChoices() + { + return array( + 'PRIMARY', + 'INDEX', + 'UNIQUE', + 'SPATIAL', + 'FULLTEXT', + ); + } + + /** + * Returns HTML for the index choice selector + * + * @return string HTML for the index choice selector + */ + public function generateIndexSelector() + { + $html_options = ''; + + foreach (PMA_Index::getIndexChoices() as $each_index_choice) { + if ($each_index_choice === 'PRIMARY' + && $this->_choice !== 'PRIMARY' + && PMA_Index::getPrimary($this->_table, $this->_schema) + ) { + // skip PRIMARY if there is already one in the table + continue; + } + $html_options .= '' . "\n"; + } + + return $html_options; + } + + /** + * Returns how the index is packed + * + * @return string how the index is packed + */ + public function getPacked() + { + return $this->_packed; + } + + /** + * Returns 'No'/false if the index is not packed, + * how the index is packed if packed + * + * @param boolean $as_text whether to output should be in text + * + * @return mixed how index is paked + */ + public function isPacked($as_text = false) + { + if ($as_text) { + $r = array( + '0' => __('No'), + '1' => __('Yes'), + ); + } else { + $r = array( + '0' => false, + '1' => true, + ); + } + + if (null === $this->_packed) { + return $r[0]; + } + + return $this->_packed; + } + + /** + * Returns integer 0 if the index cannot contain duplicates, 1 if it can + * + * @return integer 0 if the index cannot contain duplicates, 1 if it can + */ + public function getNonUnique() + { + return $this->_non_unique; + } + + /** + * Returns whether the index is a 'Unique' index + * + * @param boolean $as_text whether to output should be in text + * + * @return mixed whether the index is a 'Unique' index + */ + public function isUnique($as_text = false) + { + if ($as_text) { + $r = array( + '0' => __('Yes'), + '1' => __('No'), + ); + } else { + $r = array( + '0' => true, + '1' => false, + ); + } + + return $r[$this->_non_unique]; + } + + /** + * Returns the name of the index + * + * @return string the name of the index + */ + public function getName() + { + return $this->_name; + } + + /** + * Sets the name of the index + * + * @param string $name index name + * + * @return void + */ + public function setName($name) + { + $this->_name = (string) $name; + } + + /** + * Returns the columns of the index + * + * @return array the columns of the index + */ + public function getColumns() + { + return $this->_columns; + } + + /** + * Show index data + * + * @param string $table The table name + * @param string $schema The schema name + * @param boolean $print_mode Whether the output is for the print mode + * + * @return array Index collection array + * + * @access public + */ + static public function getView($table, $schema, $print_mode = false) + { + $indexes = PMA_Index::getFromTable($table, $schema); + + $no_indexes_class = count($indexes) > 0 ? ' hide' : ''; + $no_indexes = "
      "; + $no_indexes .= PMA_Message::notice(__('No index defined!'))->getDisplay(); + $no_indexes .= '
      '; + + if (! $print_mode) { + $r = '
      '; + $r .= '' . __('Indexes'); + $r .= PMA_Util::showMySQLDocu('optimizing-database-structure'); + + $r .= ''; + $r .= $no_indexes; + if (count($indexes) < 1) { + $r .= '
      '; + return $r; + } + $r .= PMA_Index::findDuplicates($table, $schema); + } else { + $r = '

      ' . __('Indexes') . '

      '; + $r .= $no_indexes; + if (count($indexes) < 1) { + return $r; + } + } + $r .= ''; + $r .= ''; + $r .= ''; + if (! $print_mode) { + $r .= ''; + } + $r .= ''; + $r .= ''; + $r .= ''; + $r .= ''; + $r .= ''; + $r .= ''; + $r .= ''; + $r .= ''; + if (PMA_MYSQL_INT_VERSION > 50500) { + $r .= ''; + } + $r .= ''; + $r .= ''; + $r .= ''; + + $odd_row = true; + foreach ($indexes as $index) { + $row_span = ' rowspan="' . $index->getColumnCount() . '" '; + + $r .= ''; + + if (! $print_mode) { + $this_params = $GLOBALS['url_params']; + $this_params['index'] = $index->getName(); + $r .= '' . "\n"; + $this_params = $GLOBALS['url_params']; + if ($index->getName() == 'PRIMARY') { + $this_params['sql_query'] = 'ALTER TABLE ' + . PMA_Util::backquote($table) + . ' DROP PRIMARY KEY;'; + $this_params['message_to_show'] + = __('The primary key has been dropped'); + $js_msg = PMA_jsFormat( + 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY' + ); + } else { + $this_params['sql_query'] = 'ALTER TABLE ' + . PMA_Util::backquote($table) . ' DROP INDEX ' + . PMA_Util::backquote($index->getName()) . ';'; + $this_params['message_to_show'] = sprintf( + __('Index %s has been dropped.'), $index->getName() + ); + + $js_msg = PMA_jsFormat( + 'ALTER TABLE ' . $table . ' DROP INDEX ' + . $index->getName() . ';' + ); + + } + + $r .= '' . "\n"; + } + + if (! $print_mode) { + $r .= ''; + } else { + $r .= ''; + } + $r .= ''; + $r .= ''; + $r .= ''; + + foreach ($index->getColumns() as $column) { + if ($column->getSeqInIndex() > 1) { + $r .= ''; + } + $r .= ''; + $r .= ''; + $r .= ''; + $r .= ''; + + if (PMA_MYSQL_INT_VERSION > 50500 + && $column->getSeqInIndex() == 1 + ) { + $r .= ''; + } + $r .= ''; + } // end foreach $index['Sequences'] + + $odd_row = ! $odd_row; + } // end while + $r .= ''; + $r .= '
      ' . __('Action') . '' . __('Keyname') . '' . __('Type') . '' . __('Unique') . '' . __('Packed') . '' . __('Column') . '' . __('Cardinality') . '' . __('Collation') . '' . __('Null') . '' . __('Comment') . '
      ' + . ' ' . PMA_Util::getIcon('b_edit.png', __('Edit')) . '' + . ''; + $r .= ''; + $r .= ' ' + . PMA_Util::getIcon('b_drop.png', __('Drop')) . '' + . '' + . htmlspecialchars($index->getName()) + . '' + . htmlspecialchars($index->getName()) + . '' + . htmlspecialchars($index->getType()) + . '' . $index->isUnique(true) . '' . $index->isPacked(true) . '
      ' . htmlspecialchars($column->getName()); + if ($column->getSubPart()) { + $r .= ' (' . $column->getSubPart() . ')'; + } + $r .= '' + . htmlspecialchars($column->getCardinality()) + . '' + . htmlspecialchars($column->getCollation()) + . '' + . htmlspecialchars($column->getNull(true)) + . '' + . htmlspecialchars($index->getComments()) . '
      '; + if (! $print_mode) { + $r .= ''; + } + + return $r; + } + + /** + * Gets the properties in an array for comparison purposes + * + * @return array an array containing the properties of the index + */ + public function getCompareData() + { + $data = array( + // 'Non_unique' => $this->_non_unique, + 'Packed' => $this->_packed, + 'Index_type' => $this->_type, + ); + + foreach ($this->_columns as $column) { + $data['columns'][] = $column->getCompareData(); + } + + return $data; + } + + /** + * Function to check over array of indexes and look for common problems + * + * @param string $table table name + * @param string $schema schema name + * + * @return string Output HTML + * @access public + */ + static public function findDuplicates($table, $schema) + { + $indexes = PMA_Index::getFromTable($table, $schema); + + $output = ''; + + // count($indexes) < 2: + // there is no need to check if there less than two indexes + if (count($indexes) < 2) { + return $output; + } + + // remove last index from stack and ... + while ($while_index = array_pop($indexes)) { + // ... compare with every remaining index in stack + foreach ($indexes as $each_index) { + if ($each_index->getCompareData() !== $while_index->getCompareData()) { + continue; + } + + // did not find any difference + // so it makes no sense to have this two equal indexes + + $message = PMA_Message::notice( + __('The indexes %1$s and %2$s seem to be equal and one of them could possibly be removed.') + ); + $message->addParam($each_index->getName()); + $message->addParam($while_index->getName()); + $output .= $message->getDisplay(); + + // there is no need to check any further indexes if we have already + // found that this one has a duplicate + continue 2; + } + } + return $output; + } +} + +/** + * Index column wrapper + * + * @package PhpMyAdmin + */ +class PMA_Index_Column +{ + /** + * @var string The column name + */ + private $_name = ''; + + /** + * @var integer The column sequence number in the index, starting with 1. + */ + private $_seq_in_index = 1; + + /** + * @var string How the column is sorted in the index. “A” (Ascending) or + * NULL (Not sorted) + */ + private $_collation = null; + + /** + * The number of indexed characters if the column is only partly indexed, + * NULL if the entire column is indexed. + * + * @var integer + */ + private $_sub_part = null; + + /** + * Contains YES if the column may contain NULL. + * If not, the column contains NO. + * + * @var string + */ + private $_null = ''; + + /** + * An estimate of the number of unique values in the index. This is updated + * by running ANALYZE TABLE or myisamchk -a. Cardinality is counted based on + * statistics stored as integers, so the value is not necessarily exact even + * for small tables. The higher the cardinality, the greater the chance that + * MySQL uses the index when doing joins. + * + * @var integer + */ + private $_cardinality = null; + + /** + * Constructor + * + * @param array $params an array containing the parameters of the index column + */ + public function __construct($params = array()) + { + $this->set($params); + } + + /** + * Sets parameters of the index column + * + * @param array $params an array containing the parameters of the index column + * + * @return void + */ + public function set($params) + { + if (isset($params['Column_name'])) { + $this->_name = $params['Column_name']; + } + if (isset($params['Seq_in_index'])) { + $this->_seq_in_index = $params['Seq_in_index']; + } + if (isset($params['Collation'])) { + $this->_collation = $params['Collation']; + } + if (isset($params['Cardinality'])) { + $this->_cardinality = $params['Cardinality']; + } + if (isset($params['Sub_part'])) { + $this->_sub_part = $params['Sub_part']; + } + if (isset($params['Null'])) { + $this->_null = $params['Null']; + } + } + + /** + * Returns the column name + * + * @return string column name + */ + public function getName() + { + return $this->_name; + } + + /** + * Return the column collation + * + * @return string column collation + */ + public function getCollation() + { + return $this->_collation; + } + + /** + * Returns the cardinality of the column + * + * @return int cardinality of the column + */ + public function getCardinality() + { + return $this->_cardinality; + } + + /** + * Returns whether the column is nullable + * + * @param boolean $as_text whether to returned the string representation + * + * @return mixed nullability of the column. True/false or Yes/No depending + * on the value of the $as_text parameter + */ + public function getNull($as_text = false) + { + return $as_text + ? (!$this->_null || $this->_null == 'NO' ? __('No') : __('Yes')) + : $this->_null; + } + + /** + * Returns the sequence number of the column in the index + * + * @return int sequence number of the column in the index + */ + public function getSeqInIndex() + { + return $this->_seq_in_index; + } + + /** + * Returns the number of indexed characters if the column is only + * partly indexed + * + * @return int the number of indexed characters + */ + public function getSubPart() + { + return $this->_sub_part; + } + + /** + * Gets the properties in an array for comparison purposes + * + * @return array an array containing the properties of the index column + */ + public function getCompareData() + { + return array( + 'Column_name' => $this->_name, + 'Seq_in_index' => $this->_seq_in_index, + 'Collation' => $this->_collation, + 'Sub_part' => $this->_sub_part, + 'Null' => $this->_null, + ); + } +} +?> diff --git a/sources/libraries/List.class.php b/sources/libraries/List.class.php new file mode 100644 index 0000000..eb40b86 --- /dev/null +++ b/sources/libraries/List.class.php @@ -0,0 +1,132 @@ +getEmpty(); + } + + /** + * defines what is an empty item (0, '', false or null) + * + * @return mixed an empty item + */ + public function getEmpty() + { + return $this->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, otheriwse 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
      + * + * more advanced usage example: + * + * // create a localized success message + * $message = PMA_Message::success('strSomeLocaleMessage'); + * + * // create another message, a hint, with a localized string which expects + * // two parameters: $strSomeTooltip = 'Read the %smanual%s' + * $hint = PMA_Message::notice('strSomeTooltip'); + * // replace placeholders with the following params + * $hint->addParam('[doc@cfg_Example]'); + * $hint->addParam('[/doc]'); + * // add this hint as a tooltip + * $hint = showHint($hint); + * + * // add the retrieved tooltip reference to the original message + * $message->addMessage($hint); + * + * // create another message ... + * $more = PMA_Message::notice('strSomeMoreLocale'); + * $more->addString('strSomeEvenMoreLocale', '
      '); + * $more->addParam('parameter for strSomeMoreLocale'); + * $more->addParam('more parameter for strSomeMoreLocale'); + * + * // and add it also to the original message + * $message->addMessage($more); + * // finally add another raw message + * $message->addMessage('some final words', ' - '); + * + * // display() will now print all messages in the same order as they are added + * $message->display(); + * // strSomeLocaleMessage 1 strSomeMoreLocale
      + * // strSomeEvenMoreLocale - some final words + *
      + * + * @package PhpMyAdmin + */ +class PMA_Message +{ + const SUCCESS = 1; // 0001 + const NOTICE = 2; // 0010 + const ERROR = 8; // 1000 + + const SANITIZE_NONE = 0; // 0000 0000 + const SANITIZE_STRING = 16; // 0001 0000 + const SANITIZE_PARAMS = 32; // 0010 0000 + const SANITIZE_BOOTH = 48; // 0011 0000 + + /** + * message levels + * + * @var array + */ + static public $level = array ( + PMA_Message::SUCCESS => 'success', + PMA_Message::NOTICE => 'notice', + PMA_Message::ERROR => 'error', + ); + + /** + * The message number + * + * @access protected + * @var integer + */ + protected $number = PMA_Message::NOTICE; + + /** + * The locale string identifier + * + * @access protected + * @var string + */ + protected $string = ''; + + /** + * The formatted message + * + * @access protected + * @var string + */ + protected $message = ''; + + /** + * Whether the message was already displayed + * + * @access protected + * @var boolean + */ + protected $isDisplayed = false; + + /** + * Unique id + * + * @access protected + * @var string + */ + protected $hash = null; + + /** + * holds parameters + * + * @access protected + * @var array + */ + protected $params = array(); + + /** + * holds additional messages + * + * @access protected + * @var array + */ + protected $addedMessages = array(); + + /** + * Constructor + * + * @param string $string The message to be displayed + * @param integer $number A numeric representation of the type of message + * @param array $params An array of parameters to use in the message + * @param integer $sanitize A flag to indicate what to sanitize, see + * constant definitions above + */ + public function __construct($string = '', $number = PMA_Message::NOTICE, + $params = array(), $sanitize = PMA_Message::SANITIZE_NONE + ) { + $this->setString($string, $sanitize & PMA_Message::SANITIZE_STRING); + $this->setNumber($number); + $this->setParams($params, $sanitize & PMA_Message::SANITIZE_PARAMS); + } + + /** + * magic method: return string representation for this object + * + * @return string + */ + public function __toString() + { + return $this->getMessage(); + } + + /** + * get PMA_Message of type success + * + * shorthand for getting a simple success message + * + * @param string $string A localized string + * e.g. __('Your SQL query has been + * executed successfully') + * + * @return PMA_Message + * @static + */ + static public function success($string = '') + { + if (empty($string)) { + $string = __('Your SQL query has been executed successfully'); + } + + return new PMA_Message($string, PMA_Message::SUCCESS); + } + + /** + * get PMA_Message of type error + * + * shorthand for getting a simple error message + * + * @param string $string A localized string e.g. __('Error') + * + * @return PMA_Message + * @static + */ + static public function error($string = '') + { + if (empty($string)) { + $string = __('Error'); + } + + return new PMA_Message($string, PMA_Message::ERROR); + } + + /** + * get PMA_Message of type notice + * + * shorthand for getting a simple notice message + * + * @param string $string A localized string + * e.g. __('The additional features for working with + * linked tables have been deactivated. To find out + * why click %shere%s.') + * + * @return PMA_Message + * @static + */ + static public function notice($string) + { + return new PMA_Message($string, PMA_Message::NOTICE); + } + + /** + * get PMA_Message with customized content + * + * shorthand for getting a customized message + * + * @param string $message A localized string + * @param integer $type A numeric representation of the type of message + * + * @return PMA_Message + * @static + */ + static public function raw($message, $type = PMA_Message::NOTICE) + { + $r = new PMA_Message('', $type); + $r->setMessage($message); + return $r; + } + + /** + * get PMA_Message for number of affected rows + * + * shorthand for getting a customized message + * + * @param integer $rows Number of rows + * + * @return PMA_Message + * @static + */ + static public function getMessageForAffectedRows($rows) + { + $message = PMA_Message::success( + _ngettext('%1$d row affected.', '%1$d rows affected.', $rows) + ); + $message->addParam($rows); + return $message; + } + + /** + * get PMA_Message for number of deleted rows + * + * shorthand for getting a customized message + * + * @param integer $rows Number of rows + * + * @return PMA_Message + * @static + */ + static public function getMessageForDeletedRows($rows) + { + $message = PMA_Message::success( + _ngettext('%1$d row deleted.', '%1$d rows deleted.', $rows) + ); + $message->addParam($rows); + return $message; + } + + /** + * get PMA_Message for number of inserted rows + * + * shorthand for getting a customized message + * + * @param integer $rows Number of rows + * + * @return PMA_Message + * @static + */ + static public function getMessageForInsertedRows($rows) + { + $message = PMA_Message::success( + _ngettext('%1$d row inserted.', '%1$d rows inserted.', $rows) + ); + $message->addParam($rows); + return $message; + } + + /** + * get PMA_Message of type error with custom content + * + * shorthand for getting a customized error message + * + * @param string $message A localized string + * + * @return PMA_Message + * @static + */ + static public function rawError($message) + { + return PMA_Message::raw($message, PMA_Message::ERROR); + } + + /** + * get PMA_Message of type notice with custom content + * + * shorthand for getting a customized notice message + * + * @param string $message A localized string + * + * @return PMA_Message + * @static + */ + static public function rawNotice($message) + { + return PMA_Message::raw($message, PMA_Message::NOTICE); + } + + /** + * get PMA_Message of type success with custom content + * + * shorthand for getting a customized success message + * + * @param string $message A localized string + * + * @return PMA_Message + * @static + */ + static public function rawSuccess($message) + { + return PMA_Message::raw($message, PMA_Message::SUCCESS); + } + + /** + * returns whether this message is a success message or not + * and optionaly makes this message a success message + * + * @param boolean $set Whether to make this message of SUCCESS type + * + * @return boolean whether this is a success message or not + */ + public function isSuccess($set = false) + { + if ($set) { + $this->setNumber(PMA_Message::SUCCESS); + } + + return $this->getNumber() === PMA_Message::SUCCESS; + } + + /** + * returns whether this message is a notice message or not + * and optionally makes this message a notice message + * + * @param boolean $set Whether to make this message of NOTICE type + * + * @return boolean whether this is a notice message or not + */ + public function isNotice($set = false) + { + if ($set) { + $this->setNumber(PMA_Message::NOTICE); + } + + return $this->getNumber() === PMA_Message::NOTICE; + } + + /** + * returns whether this message is an error message or not + * and optionally makes this message an error message + * + * @param boolean $set Whether to make this message of ERROR type + * + * @return boolean Whether this is an error message or not + */ + public function isError($set = false) + { + if ($set) { + $this->setNumber(PMA_Message::ERROR); + } + + return $this->getNumber() === PMA_Message::ERROR; + } + + /** + * set raw message (overrides string) + * + * @param string $message A localized string + * @param boolean $sanitize Whether to sanitize $message or not + * + * @return void + */ + public function setMessage($message, $sanitize = false) + { + if ($sanitize) { + $message = PMA_Message::sanitize($message); + } + $this->message = $message; + } + + /** + * set string (does not take effect if raw message is set) + * + * @param string $string string to set + * @param boolean $sanitize whether to sanitize $string or not + * + * @return void + */ + public function setString($string, $sanitize = true) + { + if ($sanitize) { + $string = PMA_Message::sanitize($string); + } + $this->string = $string; + } + + /** + * set message type number + * + * @param integer $number message type number to set + * + * @return void + */ + public function setNumber($number) + { + $this->number = $number; + } + + /** + * add parameter, usually in conjunction with strings + * + * usage + * + * $message->addParam('strLocale', false); + * $message->addParam('[em]some string[/em]'); + * $message->addParam('', false); + * + * + * @param mixed $param parameter to add + * @param boolean $raw whether parameter should be passed as is + * without html escaping + * + * @return void + */ + public function addParam($param, $raw = true) + { + if ($param instanceof PMA_Message) { + $this->params[] = $param; + } elseif ($raw) { + $this->params[] = htmlspecialchars($param); + } else { + $this->params[] = PMA_Message::notice($param); + } + } + + /** + * add another string to be concatenated on displaying + * + * @param string $string to be added + * @param string $separator to use between this and previous string/message + * + * @return void + */ + public function addString($string, $separator = ' ') + { + $this->addedMessages[] = $separator; + $this->addedMessages[] = PMA_Message::notice($string); + } + + /** + * add a bunch of messages at once + * + * @param array $messages to be added + * @param string $separator to use between this and previous string/message + * + * @return void + */ + public function addMessages($messages, $separator = ' ') + { + foreach ($messages as $message) { + $this->addMessage($message, $separator); + } + } + + /** + * add another raw message to be concatenated on displaying + * + * @param mixed $message to be added + * @param string $separator to use between this and previous string/message + * + * @return void + */ + public function addMessage($message, $separator = ' ') + { + if (strlen($separator)) { + $this->addedMessages[] = $separator; + } + + if ($message instanceof PMA_Message) { + $this->addedMessages[] = $message; + } else { + $this->addedMessages[] = PMA_Message::rawNotice($message); + } + } + + /** + * set all params at once, usually used in conjunction with string + * + * @param array $params parameters to set + * @param boolean $sanitize whether to sanitize params + * + * @return void + */ + public function setParams($params, $sanitize = false) + { + if ($sanitize) { + $params = PMA_Message::sanitize($params); + } + $this->params = $params; + } + + /** + * return all parameters + * + * @return array + */ + public function getParams() + { + return $this->params; + } + + /** + * return all added messages + * + * @return array + */ + public function getAddedMessages() + { + return $this->addedMessages; + } + + /** + * Sanitizes $message + * + * @param mixed $message the message(s) + * + * @return mixed the sanitized message(s) + * @access public + * @static + */ + static public function sanitize($message) + { + if (is_array($message)) { + foreach ($message as $key => $val) { + $message[$key] = PMA_Message::sanitize($val); + } + + return $message; + } + + return htmlspecialchars($message); + } + + /** + * decode $message, taking into account our special codes + * for formatting + * + * @param string $message the message + * + * @return string the decoded message + * @access public + * @static + */ + static public function decodeBB($message) + { + return PMA_sanitize($message, false, true); + } + + /** + * wrapper for sprintf() + * + * @return string formatted + */ + static public function format() + { + $params = func_get_args(); + if (isset($params[1]) && is_array($params[1])) { + array_unshift($params[1], $params[0]); + $params = $params[1]; + } + + return call_user_func_array('sprintf', $params); + } + + /** + * returns unique PMA_Message::$hash, if not exists it will be created + * + * @return string PMA_Message::$hash + */ + public function getHash() + { + if (null === $this->hash) { + $this->hash = md5( + $this->getNumber() . + $this->string . + $this->message + ); + } + + return $this->hash; + } + + /** + * returns compiled message + * + * @return string complete message + */ + public function getMessage() + { + $message = $this->message; + + if (0 === strlen($message)) { + $string = $this->getString(); + if (isset($GLOBALS[$string])) { + $message = $GLOBALS[$string]; + } elseif (0 === strlen($string)) { + $message = ''; + } else { + $message = $string; + } + } + + if ($this->isDisplayed()) { + $message = $this->getMessageWithIcon($message); + } + if (count($this->getParams()) > 0) { + $message = PMA_Message::format($message, $this->getParams()); + } + + $message = PMA_Message::decodeBB($message); + + foreach ($this->getAddedMessages() as $add_message) { + $message .= $add_message; + } + + return $message; + } + + /** + * returns PMA_Message::$string + * + * @return string PMA_Message::$string + */ + public function getString() + { + return $this->string; + } + + /** + * returns PMA_Message::$number + * + * @return integer PMA_Message::$number + */ + public function getNumber() + { + return $this->number; + } + + /** + * returns level of message + * + * @return string level of message + */ + public function getLevel() + { + return PMA_Message::$level[$this->getNumber()]; + } + + /** + * Displays the message in HTML + * + * @return void + */ + public function display() + { + echo $this->getDisplay(); + $this->isDisplayed(true); + } + + /** + * returns HTML code for displaying this message + * + * @return string whole message box + */ + public function getDisplay() + { + $this->isDisplayed(true); + return '
      ' + . $this->getMessage() . '
      '; + } + + /** + * sets and returns whether the message was displayed or not + * + * @param boolean $isDisplayed whether to set displayed flag + * + * @return boolean PMA_Message::$isDisplayed + */ + public function isDisplayed($isDisplayed = false) + { + if ($isDisplayed) { + $this->isDisplayed = true; + } + + return $this->isDisplayed; + } + + /** + * Returns the message with corresponding image icon + * + * @param string $message the message(s) + * + * @return string message with icon + */ + public function getMessageWithIcon($message) + { + $image = ''; + if ('error' == $this->getLevel()) { + $image = 's_error.png'; + } elseif ('success' == $this->getLevel()) { + $image = 's_success.png'; + } else { + $image = 's_notice.png'; + } + $message = PMA_Message::notice(PMA_Util::getImage($image)) . " " . $message; + return $message; + + } +} +?> diff --git a/sources/libraries/OutputBuffering.class.php b/sources/libraries/OutputBuffering.class.php new file mode 100644 index 0000000..ff1f0b7 --- /dev/null +++ b/sources/libraries/OutputBuffering.class.php @@ -0,0 +1,142 @@ +_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) { + // If output buffering is enabled in php.ini it's not possible to + // add the ob_gzhandler without a warning message from php 4.3.0. + // Being better safe than sorry, check for any existing output + // handler instead of just checking the 'output_buffering' setting. + $mode = 0; + } 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. + // Usefull 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 PMA_OutputBuffering object + * + * @return PMA_OutputBuffering object + */ + public static function getInstance() + { + if (empty(self::$_instance)) { + self::$_instance = new PMA_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) { + ob_start('ob_gzhandler'); + } + ob_start(); + if (! defined('TESTSUITE')) { + header('X-ob_mode: ' . $this->_mode); + } + register_shutdown_function('PMA_OutputBuffering::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 = PMA_OutputBuffering::getInstance(); + if ($buffer->_on) { + $buffer->_on = false; + $buffer->_content = ob_get_contents(); + ob_end_clean(); + } + PMA_Response::response(); + } + + /** + * 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/sources/libraries/PDF.class.php b/sources/libraries/PDF.class.php new file mode 100644 index 0000000..026a87f --- /dev/null +++ b/sources/libraries/PDF.class.php @@ -0,0 +1,145 @@ +SetAuthor('phpMyAdmin ' . PMA_VERSION); + $this->AddFont('DejaVuSans', '', 'dejavusans.php'); + $this->AddFont('DejaVuSans', 'B', 'dejavusansb.php'); + $this->SetFont(PMA_PDF_FONT, '', 14); + $this->setFooterFont(array(PMA_PDF_FONT, '', 14)); + } + + /** + * This function must be named "Footer" to work with the TCPDF library + * + * @return void + */ + function Footer() + { + // Check if footer for this page already exists + if (!isset($this->footerset[$this->page])) { + $this->SetY(-15); + $this->SetFont(PMA_PDF_FONT, '', 14); + $this->Cell( + 0, 6, + __('Page number:') . ' ' + . $this->getAliasNumPage() . '/' . $this->getAliasNbPages(), + 'T', 0, 'C' + ); + $this->Cell(0, 6, PMA_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 + */ + 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 expading. + * + * @return void + */ + function _putpages() + { + if (count($this->Alias) > 0) { + $nb = count($this->pages); + for ($n = 1;$n <= $nb;$n++) { + $this->pages[$n] = strtr($this->pages[$n], $this->Alias); + } + } + parent::_putpages(); + } + + /** + * Displays an error message + * + * @param string $error_message the error mesage + * + * @return void + */ + function Error($error_message = '') + { + PMA_Message::error( + __('Error while creating PDF:') . ' ' . $error_message + )->display(); + exit; + } + + /** + * Sends file as a download to user. + * + * @param string $filename file name + * + * @return void + */ + function Download($filename) + { + $pdfData = $this->getPDFData(); + PMA_Response::getInstance()->disable(); + PMA_downloadHeader($filename, 'application/pdf', strlen($pdfData)); + echo $pdfData; + } +} diff --git a/sources/libraries/PMA.php b/sources/libraries/PMA.php new file mode 100644 index 0000000..021917d --- /dev/null +++ b/sources/libraries/PMA.php @@ -0,0 +1,110 @@ +getDatabaseList(); + break; + case 'userlink' : + return $this->userlink; + break; + case 'controllink' : + return $this->controllink; + break; + } + + return null; + } + + /** + * magic access to protected/inaccessible members/properties + * + * @param string $param parameter name + * @param mixed $value value to set + * + * @return void + * @see http://php.net/language.oop5.overloading + */ + public function __set($param, $value) + { + switch ($param) { + case 'userlink' : + $this->userlink = $value; + break; + case 'controllink' : + $this->controllink = $value; + break; + } + } + + /** + * Accessor to PMA::$databases + * + * @return PMA_List_Databases + */ + public function getDatabaseList() + { + if (null === $this->databases) { + $this->databases = new PMA_List_Database( + $this->userlink, + $this->controllink + ); + } + + return $this->databases; + } +} +?> diff --git a/sources/libraries/Partition.class.php b/sources/libraries/Partition.class.php new file mode 100644 index 0000000..5125885 --- /dev/null +++ b/sources/libraries/Partition.class.php @@ -0,0 +1,79 @@ +fetchResult( + "SELECT `PARTITION_NAME` FROM `information_schema`.`PARTITIONS`" + . " WHERE `TABLE_SCHEMA` = '" . $db + . "' AND `TABLE_NAME` = '" . $table . "'" + ); + } else { + return array(); + } + } + + /** + * 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 (PMA_MYSQL_INT_VERSION >= 50100) { + if (PMA_MYSQL_INT_VERSION < 50600) { + if ($GLOBALS['dbi']->fetchValue( + "SHOW VARIABLES LIKE 'have_partitioning';" + )) { + $have_partitioning = true; + } + } else { + // see http://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/sources/libraries/RecentTable.class.php b/sources/libraries/RecentTable.class.php new file mode 100644 index 0000000..65b798f --- /dev/null +++ b/sources/libraries/RecentTable.class.php @@ -0,0 +1,220 @@ +_pmaTable + = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) . "." + . PMA_Util::backquote($GLOBALS['cfg']['Server']['recent']); + } + $server_id = $GLOBALS['server']; + if (! isset($_SESSION['tmpval']['recent_tables'][$server_id])) { + $_SESSION['tmpval']['recent_tables'][$server_id] + = isset($this->_pmaTable) ? $this->getFromDb() : array(); + } + $this->tables =& $_SESSION['tmpval']['recent_tables'][$server_id]; + } + + /** + * Returns class instance. + * + * @return PMA_RecentTable + */ + public static function getInstance() + { + if (is_null(self::$_instance)) { + self::$_instance = new PMA_RecentTable(); + } + return self::$_instance; + } + + /** + * Returns recently used tables 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->_pmaTable . + " WHERE `username` = '" . $GLOBALS['cfg']['Server']['user'] . "'"; + + $return = array(); + $result = PMA_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 tables into phpMyAdmin database. + * + * @return true|PMA_Message + */ + public function saveToDb() + { + $username = $GLOBALS['cfg']['Server']['user']; + $sql_query + = " REPLACE INTO " . $this->_pmaTable . " (`username`, `tables`)" . + " VALUES ('" . $username . "', '" + . PMA_Util::sqlAddSlashes( + json_encode($this->tables) + ) . "')"; + + $success = $GLOBALS['dbi']->tryQuery($sql_query, $GLOBALS['controllink']); + + if (! $success) { + $message = PMA_Message::error(__('Could not save recent table')); + $message->addMessage('

      '); + $message->addMessage( + PMA_Message::rawError( + $GLOBALS['dbi']->getError($GLOBALS['controllink']) + ) + ); + return $message; + } + return true; + } + + /** + * Trim recent table according to the NumRecentTables configuration. + * + * @return boolean True if trimming occurred + */ + public function trim() + { + $max = max($GLOBALS['cfg']['NumRecentTables'], 0); + $trimming_occurred = count($this->tables) > $max; + while (count($this->tables) > $max) { + array_pop($this->tables); + } + return $trimming_occurred; + } + + /** + * Return options for HTML select. + * + * @return string + */ + public function getHtmlSelectOption() + { + // trim and save, in case where the configuration is changed + if ($this->trim() && isset($this->_pmaTable)) { + $this->saveToDb(); + } + + $html = ''; + if (count($this->tables)) { + foreach ($this->tables as $table) { + $html .= ''; + } + } else { + $html .= ''; + } + return $html; + } + + /** + * Return HTML select. + * + * @return string + */ + public function getHtmlSelect() + { + $html = ''; + + return $html; + } + + /** + * Add recently used tables. + * + * @param string $db database name where the table is located + * @param string $table table name + * + * @return true|PMA_Message True if success, PMA_Message if not + */ + public function add($db, $table) + { + $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 (isset($this->_pmaTable)) { + return $this->saveToDb(); + } + } + return true; + } + +} +?> diff --git a/sources/libraries/Response.class.php b/sources/libraries/Response.class.php new file mode 100644 index 0000000..10858d2 --- /dev/null +++ b/sources/libraries/Response.class.php @@ -0,0 +1,380 @@ +start(); + } + $this->_header = new PMA_Header(); + $this->_HTML = ''; + $this->_JSON = array(); + $this->_footer = new PMA_Footer(); + + $this->_isSuccess = true; + $this->_isAjax = false; + $this->_isAjaxPage = false; + if (isset($_REQUEST['ajax_request']) && $_REQUEST['ajax_request'] == true) { + $this->_isAjax = true; + } + if (isset($_REQUEST['ajax_page_request']) + && $_REQUEST['ajax_page_request'] == true + ) { + $this->_isAjaxPage = true; + } + $this->_header->setAjax($this->_isAjax); + $this->_footer->setAjax($this->_isAjax); + $this->_CWD = getcwd(); + } + + /** + * Returns the singleton PMA_Response object + * + * @return PMA_Response object + */ + public static function getInstance() + { + if (empty(self::$_instance)) { + self::$_instance = new PMA_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 isSuccess($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(); + } + + /** + * Returns a PMA_Header object + * + * @return PMA_Header + */ + public function getHeader() + { + return $this->_header; + } + + /** + * Returns a PMA_Footer object + * + * @return PMA_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 PMA_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 PMA_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 it's 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() + { + if (! isset($this->_JSON['message'])) { + $this->_JSON['message'] = $this->_getDisplay(); + } else if ($this->_JSON['message'] instanceof PMA_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->_isAjaxPage && $this->_isSuccess) { + $this->addJSON('_title', $this->getHeader()->getTitleTag()); + + $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('unencoded')); + $this->addJSON('_displayMessage', $this->getHeader()->getMessage()); + $errors = $this->_footer->getErrorMessages(); + if (strlen($errors)) { + $this->addJSON('_errors', $errors); + } + if (empty($GLOBALS['error_message'])) { + // set current db, table and sql query in the querywindow + $query = ''; + $maxChars = $GLOBALS['cfg']['MaxCharactersInDisplayedSQL']; + if (isset($GLOBALS['sql_query']) + && strlen($GLOBALS['sql_query']) < $maxChars + ) { + $query = PMA_escapeJsString($GLOBALS['sql_query']); + } + $this->addJSON( + '_reloadQuerywindow', + array( + 'db' => PMA_ifSetOr($GLOBALS['db'], ''), + 'table' => PMA_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. + if (! defined('TESTSUITE')) { + header('Cache-Control: no-cache'); + header('Content-Type: application/json'); + } + + echo json_encode($this->_JSON); + } + + /** + * Sends an HTML response to the browser + * + * @static + * @return void + */ + public static function response() + { + $response = PMA_Response::getInstance(); + chdir($response->getCWD()); + $buffer = PMA_OutputBuffering::getInstance(); + if (empty($response->_HTML)) { + $response->_HTML = $buffer->getContents(); + } + if ($response->isAjax()) { + $response->_ajaxResponse(); + } else { + $response->_htmlResponse(); + } + $buffer->flush(); + exit; + } +} + +?> diff --git a/sources/libraries/Scripts.class.php b/sources/libraries/Scripts.class.php new file mode 100644 index 0000000..1ceb370 --- /dev/null +++ b/sources/libraries/Scripts.class.php @@ -0,0 +1,248 @@ +"; + } + } + $static_scripts = sprintf( + '', + PMA_URL_getCommon(array(), 'none'), implode("&", $scripts) + ); + return $static_scripts . $dynamic_scripts; + } + + /** + * Generates new PMA_Scripts objects + * + * @return PMA_Scripts object + */ + public function __construct() + { + $this->_files = array(); + $this->_code = ''; + $this->_events = array(); + + } + + /** + * Adds a new file to the list of scripts + * + * @param string $filename The name of the file to include + * @param bool $conditional_ie Whether to wrap the script tag in + * conditional comments for IE + * + * @return void + */ + public function addFile($filename, $conditional_ie = false) + { + $hash = md5($filename); + if (empty($this->_files[$hash])) { + $has_onload = $this->_eventBlacklist($filename); + $this->_files[$hash] = array( + 'has_onload' => $has_onload, + 'filename' => $filename, + 'conditional_ie' => $conditional_ie + ); + } + } + + /** + * 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, 'navigation.js') !== false + || strpos($filename, 'get_image.js.php') !== false + || strpos($filename, 'cross_framing_protection.js') !== false + ) { + return 0; + } else { + 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"; + } + + /** + * Adds a new event to the list of events + * + * @param string $event The name of the event to register + * @param string $function The code to execute when the event fires + * E.g: 'function () { doSomething(); }' + * or 'doSomething' + * + * @return void + */ + public function addEvent($event, $function) + { + $this->_events[] = array( + 'event' => $event, + 'function' => $function + ); + } + + /** + * 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 (strpos($file['filename'], "?") === false) { + if (! $file['conditional_ie'] || PMA_USR_BROWSER_AGENT == 'IE') { + $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)', + PMA_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 .= PMA_escapeJsString($file['filename']); + $code .= '");'; + } + } + $code .= '});'; + $this->addCode($code); + + $retval .= ''; + + return $retval; + } +} diff --git a/sources/libraries/ServerStatusData.class.php b/sources/libraries/ServerStatusData.class.php new file mode 100644 index 0000000..028eada --- /dev/null +++ b/sources/libraries/ServerStatusData.class.php @@ -0,0 +1,407 @@ +selfUrl = basename($GLOBALS['PMA_PHP_SELF']); + /** + * get status from server + */ + $server_status = $GLOBALS['dbi']->fetchResult('SHOW GLOBAL STATUS', 0, 1); + if (PMA_DRIZZLE) { + // Drizzle doesn't put query statistics into variables, add it + $sql = "SELECT concat('Com_', variable_name), variable_value " + . "FROM data_dictionary.GLOBAL_STATEMENTS"; + $statements = $GLOBALS['dbi']->fetchResult($sql, 0, 1); + $server_status = array_merge($server_status, $statements); + } + + /** + * 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 + */ + // Key_buffer_fraction + if (isset($server_status['Key_blocks_unused']) + && isset($server_variables['key_cache_block_size']) + && isset($server_variables['key_buffer_size']) + ) { + $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_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; + } + + /** + * split variables in sections + */ + $allocations = array( + // variable name => 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', + ); + + $sections = 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') + ); + + /** + * define some needfull links/commands + */ + // variable or section name => (name => url) + $links = array(); + + $links['table'][__('Flush (close) all tables')] = $this->selfUrl + . PMA_URL_getCommon( + array( + 'flush' => 'TABLES' + ) + ); + $links['table'][__('Show open tables')] + = 'sql.php' . PMA_URL_getCommon( + array( + 'sql_query' => 'SHOW OPEN TABLES', + 'goto' => $this->selfUrl, + ) + ); + + if ($GLOBALS['server_master_status']) { + $links['repl'][__('Show slave hosts')] + = 'sql.php' . PMA_URL_getCommon( + array( + 'sql_query' => 'SHOW SLAVE HOSTS', + 'goto' => $this->selfUrl, + ) + ); + $links['repl'][__('Show master status')] = '#replication_master'; + } + if ($GLOBALS['server_slave_status']) { + $links['repl'][__('Show slave status')] = '#replication_slave'; + } + + $links['repl']['doc'] = 'replication'; + + $links['qcache'][__('Flush query cache')] + = $this->selfUrl + . PMA_URL_getCommon( + array( + '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')] + = 'server_engines.php?engine=InnoDB&' . PMA_URL_getCommon(); + $links['innodb'][__('InnoDB Status')] + = 'server_engines.php' + . PMA_URL_getCommon( + array( + 'engine' => 'InnoDB', + 'page' => 'Status' + ) + ); + $links['innodb']['doc'] = 'innodb'; + + + // 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 + $categoryUsed = array(); + + // sort vars into arrays + foreach ($server_status as $name => $value) { + $section_found = false; + foreach ($allocations as $filter => $section) { + if (strpos($name, $filter) !== false) { + $allocationMap[$name] = $section; + $categoryUsed[$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'; + $categoryUsed['other'] = true; + } + } + + if (PMA_DRIZZLE) { + $used_queries = $GLOBALS['dbi']->fetchResult( + 'SELECT * FROM data_dictionary.global_statements', + 0, + 1 + ); + unset($used_queries['admin_commands']); + } else { + // 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; + if (strtolower($GLOBALS['cfg']['Server']['host']) === '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->categoryUsed = $categoryUsed; + } + + /** + * cleanup of some deprecated values + * + * @param array $server_status status array to process + * + * @return array + */ + public static function cleanDeprecated($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; + } + + /** + * cleanup of some deprecated values + * + * @return array + */ + public function getMenuHtml() + { + $url_params = PMA_URL_getCommon(); + $items = array( + array( + 'name' => __('Server'), + 'url' => 'server_status.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; + } +} + +?> diff --git a/sources/libraries/StorageEngine.class.php b/sources/libraries/StorageEngine.class.php new file mode 100644 index 0000000..19269d0 --- /dev/null +++ b/sources/libraries/StorageEngine.class.php @@ -0,0 +1,434 @@ +fetchResult($sql, 'Engine'); + } else { + $storage_engines + = $GLOBALS['dbi']->fetchResult('SHOW STORAGE ENGINES', 'Engine'); + } + } + + 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? + * + * @static + * @return string html selectbox + */ + static public function getHtmlSelect( + $name = 'engine', $id = null, + $selected = null, $offerUnavailableEngines = false + ) { + $selected = strtolower($selected); + $output = '' . "\n"; + return $output; + } + + /** + * public static final PMA_StorageEngine getEngine() + * + * Loads the corresponding engine plugin, if available. + * + * @param string $engine The engine ID + * + * @return PMA_StorageEngine The engine plugin + */ + static public function getEngine($engine) + { + $engine = str_replace('/', '', str_replace('.', '', $engine)); + $filename = './libraries/engines/' . strtolower($engine) . '.lib.php'; + if (file_exists($filename) && include_once $filename) { + $class_name = 'PMA_StorageEngine_' . $engine; + $engine_object = new $class_name($engine); + } else { + $engine_object = new PMA_StorageEngine($engine); + } + return $engine_object; + } + + /** + * return 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 = PMA_StorageEngine::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 + */ + function getHtmlVariables() + { + $odd_row = false; + $ret = ''; + + foreach ($this->getVariablesStatus() as $details) { + $ret .= '' . "\n" + . ' ' . "\n"; + if (! empty($details['desc'])) { + $ret .= ' ' + . PMA_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 .= PMA_Util::formatNumber($details['value']) . ' '; + break; + default: + $ret .= htmlspecialchars($details['value']) . ' '; + } + $ret .= '' . "\n" + . '' . "\n"; + $odd_row = ! $odd_row; + } + + 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 + */ + function resolveTypeSize($value) + { + return PMA_Util::formatByteDown($value); + } + + /** + * returns array with detailed info about engine specific server variables + * + * @return array with detailed info about specific engine server variables + */ + 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 + && strpos(strtolower($row['Variable_name']), 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; + } + + /** + * Constructor + * + * @param string $engine The engine ID + */ + function __construct($engine) + { + $storage_engines = PMA_StorageEngine::getStorageEngines(); + if (! empty($storage_engines[$engine])) { + $this->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; + } + } + } + + /** + * public String getTitle() + * + * Reveals the engine's title + * + * @return string The title + */ + function getTitle() + { + return $this->title; + } + + /** + * public String getComment() + * + * Fetches the server's comment about this engine + * + * @return string The comment + */ + function getComment() + { + return $this->comment; + } + + /** + * public String getSupportInformationMessage() + * + * @return string The localized message. + */ + 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)); + } + + /** + * public string[][] getVariables() + * + * 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. + * + * @abstract + * @return Array The list of variables. + */ + function getVariables() + { + return array(); + } + + /** + * returns string with filename for the MySQL helppage + * about this storage engine + * + * @return string mysql helppage filename + */ + function getMysqlHelpPage() + { + return $this->engine . '-storage-engine'; + } + + /** + * public string getVariablesLikePattern() + * + * @abstract + * @return string SQL query LIKE pattern + */ + function getVariablesLikePattern() + { + return false; + } + + /** + * public String[] getInfoPages() + * + * Returns a list of available information pages with labels + * + * @abstract + * @return array The list + */ + function getInfoPages() + { + return array(); + } + + /** + * public String getPage() + * + * Generates the requested information page + * + * @param string $id The page ID + * + * @abstract + * @return string The page + * boolean or false on error. + */ + function getPage($id) + { + return false; + } +} + +?> diff --git a/sources/libraries/String.class.php b/sources/libraries/String.class.php new file mode 100644 index 0000000..a5fbe8a --- /dev/null +++ b/sources/libraries/String.class.php @@ -0,0 +1,261 @@ +_byte = new PMA_StringMB(); + } else { + include_once 'libraries/StringNative.class.php'; + $this->_byte = new PMA_StringNative(); + } + + if (@extension_loaded('ctype')) { + include_once 'libraries/StringCType.class.php'; + $this->_type = new PMA_StringCType(); + } else { + include_once 'libraries/StringNativeType.class.php'; + $this->_type = new PMA_StringNativeType(); + } + } + + /** + * Checks if a given character position in the string is escaped or not + * + * @param string $string string to check for + * @param integer $pos the character to check for + * @param integer $start starting position in the string + * + * @return boolean whether the character is escaped or not + */ + public function charIsEscaped($string, $pos, $start = 0) + { + $pos = max(intval($pos), 0); + $start = max(intval($start), 0); + $len = $this->strlen($string); + // Base case: + // Check for string length or invalid input or special case of input + // (pos == $start) + if ($pos <= $start || $len <= max($pos, $start)) { + return false; + } + + $pos--; + $escaped = false; + while ($pos >= $start && $this->substr($string, $pos, 1) == '\\') { + $escaped = !$escaped; + $pos--; + } // end while + + return $escaped; + } + + /** + * Checks if a character is an SQL identifier + * + * @param string $c character to check for + * @param boolean $dot_is_valid whether the dot character is valid or not + * + * @return boolean whether the character is an SQL identifier or not + */ + public function isSqlIdentifier($c, $dot_is_valid = false) + { + return ($this->isAlnum($c) + || ($ord_c = $this->ord($c)) && $ord_c >= 192 && $ord_c != 215 && + $ord_c != 249 + || $c == '_' + || $c == '$' + || ($dot_is_valid != false && $c == '.')); + } + + /** + * Returns length of string depending on current charset. + * + * @param string $string string to count + * + * @return int string length + */ + + public function strlen($string) + { + return $this->_byte->strlen($string); + } + + /** + * Returns substring from string, works depending on current charset. + * + * @param string $string string to count + * @param int $start start of substring + * @param int $length length of substring + * + * @return string the sub string + */ + public function substr($string, $start, $length = 2147483647) + { + return $this->_byte->substr($string, $start, $length); + } + + /** + * Returns postion of $needle in $haystack or false if not found + * + * @param string $haystack the string being checked + * @param string $needle the string to find in haystack + * @param int $offset the search offset + * + * @return integer position of $needle in $haystack or false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return $this->_byte->strpos($haystack, $needle, $offset); + } + + /** + * Make a string lowercase + * + * @param string $string the string being lowercased + * + * @return string the lower case string + */ + public function strtolower($string) + { + return $this->_byte->strtolower($string); + } + + /** + * Get the ordinal value of a string + * + * @param string $string the string for which ord is required + * + * @return string the ord value + */ + public function ord($string) + { + return $this->_byte->ord($string); + } + + /** + * Checks if a character is an alphanumeric one + * + * @param string $c character to check for + * + * @return boolean whether the character is an alphanumeric one or not + */ + public function isAlnum($c) + { + return $this->_type->isAlnum($c); + } + + /** + * Checks if a character is an alphabetic one + * + * @param string $c character to check for + * + * @return boolean whether the character is an alphabetic one or not + */ + public function isAlpha($c) + { + return $this->_type->isAlpha($c); + } + + /** + * Checks if a character is a digit + * + * @param string $c character to check for + * + * @return boolean whether the character is a digit or not + */ + public function isDigit($c) + { + return $this->_type->isDigit($c); + } + + /** + * Checks if a character is an upper alphabetic one + * + * @param string $c character to check for + * + * @return boolean whether the character is an upper alphabetic one or not + */ + public function isUpper($c) + { + return $this->_type->isUpper($c); + } + + /** + * Checks if a character is a lower alphabetic one + * + * @param string $c character to check for + * + * @return boolean whether the character is a lower alphabetic one or not + */ + public function isLower($c) + { + return $this->_type->isLower($c); + } + + /** + * Checks if a character is a space one + * + * @param string $c character to check for + * + * @return boolean whether the character is a space one or not + */ + public function isSpace($c) + { + return $this->_type->isSpace($c); + } + + /** + * Checks if a character is an hexadecimal digit + * + * @param string $c character to check for + * + * @return boolean whether the character is an hexadecimal digit or not + */ + public function isHexDigit($c) + { + return $this->_type->isHexDigit($c); + } + + /** + * Checks if a number is in a range + * + * @param integer $num number to check for + * @param integer $lower lower bound + * @param integer $upper upper bound + * + * @return boolean whether the number is in the range or not + */ + public function numberInRangeInclusive($num, $lower, $upper) + { + return $this->_type->numberInRangeInclusive($num, $lower, $upper); + } +} +?> \ No newline at end of file diff --git a/sources/libraries/StringAbstractType.class.php b/sources/libraries/StringAbstractType.class.php new file mode 100644 index 0000000..6424844 --- /dev/null +++ b/sources/libraries/StringAbstractType.class.php @@ -0,0 +1,35 @@ += $lower && $num <= $upper); + } +} +?> \ No newline at end of file diff --git a/sources/libraries/StringByte.int.php b/sources/libraries/StringByte.int.php new file mode 100644 index 0000000..99e15a3 --- /dev/null +++ b/sources/libraries/StringByte.int.php @@ -0,0 +1,60 @@ + \ No newline at end of file diff --git a/sources/libraries/StringCType.class.php b/sources/libraries/StringCType.class.php new file mode 100644 index 0000000..5972f12 --- /dev/null +++ b/sources/libraries/StringCType.class.php @@ -0,0 +1,110 @@ + diff --git a/sources/libraries/StringMB.class.php b/sources/libraries/StringMB.class.php new file mode 100644 index 0000000..fb4cdc9 --- /dev/null +++ b/sources/libraries/StringMB.class.php @@ -0,0 +1,92 @@ + diff --git a/sources/libraries/StringNative.class.php b/sources/libraries/StringNative.class.php new file mode 100644 index 0000000..b333610 --- /dev/null +++ b/sources/libraries/StringNative.class.php @@ -0,0 +1,87 @@ + diff --git a/sources/libraries/StringNativeType.class.php b/sources/libraries/StringNativeType.class.php new file mode 100644 index 0000000..04b7679 --- /dev/null +++ b/sources/libraries/StringNativeType.class.php @@ -0,0 +1,138 @@ +isAlpha($c) || $this->isDigit($c)); + } // end of the "isAlnum()" function + + /** + * Checks if a character is an alphabetic one + * + * @param string $c character to check for + * + * @return boolean whether the character is an alphabetic one or not + */ + public function isAlpha($c) + { + return ($this->isUpper($c) || $this->isLower($c)); + } // end of the "isAlpha()" function + + /** + * Checks if a character is a digit + * + * @param string $c character to check for + * + * @return boolean whether the character is a digit or not + */ + public function isDigit($c) + { + $ord_zero = 48; //ord('0'); + $ord_nine = 57; //ord('9'); + $ord_c = ord($c); + + return $this->numberInRangeInclusive($ord_c, $ord_zero, $ord_nine); + } // end of the "isDigit()" function + + /** + * Checks if a character is an upper alphabetic one + * + * @param string $c character to check for + * + * @return boolean whether the character is an upper alphabetic one or not + */ + public function isUpper($c) + { + $ord_zero = 65; //ord('A'); + $ord_nine = 90; //ord('Z'); + $ord_c = ord($c); + + return $this->numberInRangeInclusive($ord_c, $ord_zero, $ord_nine); + } // end of the "isUpper()" function + + /** + * Checks if a character is a lower alphabetic one + * + * @param string $c character to check for + * + * @return boolean whether the character is a lower alphabetic one or not + */ + public function isLower($c) + { + $ord_zero = 97; //ord('a'); + $ord_nine = 122; //ord('z'); + $ord_c = ord($c); + + return $this->numberInRangeInclusive($ord_c, $ord_zero, $ord_nine); + } // end of the "isLower()" function + + /** + * Checks if a character is a space one + * + * @param string $c character to check for + * + * @return boolean whether the character is a space one or not + */ + public function isSpace($c) + { + $ord_space = 32; //ord(' ') + $ord_tab = 9; //ord('\t') + $ord_CR = 13; //ord('\n') + $ord_NOBR = 160; //ord('U+00A0); + $ord_c = ord($c); + + return ($ord_c == $ord_space + || $ord_c == $ord_NOBR + || $this->numberInRangeInclusive($ord_c, $ord_tab, $ord_CR)); + } // end of the "isSpace()" function + + /** + * Checks if a character is an hexadecimal digit + * + * @param string $c character to check for + * + * @return boolean whether the character is an hexadecimal digit or not + */ + public function isHexDigit($c) + { + $ord_Aupper = 65; //ord('A'); + $ord_Fupper = 70; //ord('F'); + $ord_Alower = 97; //ord('a'); + $ord_Flower = 102; //ord('f'); + $ord_zero = 48; //ord('0'); + $ord_nine = 57; //ord('9'); + $ord_c = ord($c); + + return ($this->numberInRangeInclusive($ord_c, $ord_zero, $ord_nine) + || $this->numberInRangeInclusive($ord_c, $ord_Aupper, $ord_Fupper) + || $this->numberInRangeInclusive($ord_c, $ord_Alower, $ord_Flower)); + } // end of the "isHexDigit()" function +} +?> diff --git a/sources/libraries/StringType.int.php b/sources/libraries/StringType.int.php new file mode 100644 index 0000000..dfcb7de --- /dev/null +++ b/sources/libraries/StringType.int.php @@ -0,0 +1,85 @@ + \ No newline at end of file diff --git a/sources/libraries/Table.class.php b/sources/libraries/Table.class.php new file mode 100644 index 0000000..936eff0 --- /dev/null +++ b/sources/libraries/Table.class.php @@ -0,0 +1,1711 @@ +setName($table_name); + $this->setDbName($db_name); + } + + /** + * returns table name + * + * @see PMA_Table::getName() + * @return string table name + */ + function __toString() + { + return $this->getName(); + } + + /** + * return the last error + * + * @return string the last error + */ + function getLastError() + { + return end($this->errors); + } + + /** + * return the last message + * + * @return string the last message + */ + function getLastMessage() + { + return end($this->messages); + } + + /** + * sets table name + * + * @param string $table_name new table name + * + * @return void + */ + function setName($table_name) + { + $this->name = $table_name; + } + + /** + * returns table name + * + * @param boolean $backquoted whether to quote name with backticks `` + * + * @return string table name + */ + function getName($backquoted = false) + { + if ($backquoted) { + return PMA_Util::backquote($this->name); + } + return $this->name; + } + + /** + * sets database name for this table + * + * @param string $db_name database name + * + * @return void + */ + function setDbName($db_name) + { + $this->db_name = $db_name; + } + + /** + * returns database name for this table + * + * @param boolean $backquoted whether to quote name with backticks `` + * + * @return string database name for this table + */ + function getDbName($backquoted = false) + { + if ($backquoted) { + return PMA_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 + */ + function getFullName($backquoted = false) + { + return $this->getDbName($backquoted) . '.' + . $this->getName($backquoted); + } + + /** + * returns whether the table is actually a view + * + * @param string $db database + * @param string $table table + * + * @return boolean whether the given is a view + */ + static public function isView($db = null, $table = null) + { + if (empty($db) || empty($table)) { + return false; + } + + // use cached data or load information with SHOW command + if (isset(PMA_Table::$cache[$db][$table]) + ) { + $type = PMA_Table::sGetStatusInfo($db, $table, 'TABLE_TYPE'); + return $type == 'VIEW'; + } + + // query information_schema + $result = $GLOBALS['dbi']->fetchResult( + "SELECT TABLE_NAME + FROM information_schema.VIEWS + WHERE TABLE_SCHEMA = '" . PMA_Util::sqlAddSlashes($db) . "' + AND TABLE_NAME = '" . PMA_Util::sqlAddSlashes($table) . "'" + ); + return $result ? true : false; + } + + /** + * Returns whether the table is actually an updatable view + * + * @param string $db database + * @param string $table table + * + * @return boolean whether the given is an updatable view + */ + static public function isUpdatableView($db = null, $table = null) + { + if (empty($db) || empty($table)) { + return false; + } + + $result = $GLOBALS['dbi']->fetchResult( + "SELECT TABLE_NAME + FROM information_schema.VIEWS + WHERE TABLE_SCHEMA = '" . PMA_Util::sqlAddSlashes($db) . "' + AND TABLE_NAME = '" . PMA_Util::sqlAddSlashes($table) . "' + AND IS_UPDATABLE = 'YES'" + ); + return $result ? true : false; + } + + /** + * Returns the analysis of 'SHOW CREATE TABLE' query for the table. + * In case of a view, the values are taken from the information_schema. + * + * @param string $db database + * @param string $table table + * + * @return array analysis of 'SHOW CREATE TABLE' query for the table + */ + static public function analyzeStructure($db = null, $table = null) + { + if (empty($db) || empty($table)) { + return false; + } + + $analyzed_sql = array(); + if (self::isView($db, $table)) { + // For a view, 'SHOW CREATE TABLE' returns the definition, + // but the structure of the view. So, we try to mock + // the result of analyzing 'SHOW CREATE TABLE' query. + $analyzed_sql[0] = array(); + $analyzed_sql[0]['create_table_fields'] = array(); + + $results = $GLOBALS['dbi']->fetchResult( + "SELECT COLUMN_NAME, DATA_TYPE + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = '" . PMA_Util::sqlAddSlashes($db) . "' + AND TABLE_NAME = '" . PMA_Util::sqlAddSlashes($table) . "'" + ); + foreach ($results as $result) { + $analyzed_sql[0]['create_table_fields'][$result['COLUMN_NAME']] + = array('type' => strtoupper($result['DATA_TYPE'])); + } + } else { + $show_create_table = $GLOBALS['dbi']->fetchValue( + 'SHOW CREATE TABLE ' + . PMA_Util::backquote($db) + . '.' . PMA_Util::backquote($table), + 0, + 1 + ); + $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table)); + } + return $analyzed_sql; + } + + /** + * sets given $value for given $param + * + * @param string $param name + * @param mixed $value value + * + * @return void + */ + function set($param, $value) + { + $this->settings[$param] = $value; + } + + /** + * returns value for given setting/param + * + * @param string $param name for value to return + * + * @return mixed value for $param + */ + function get($param) + { + if (isset($this->settings[$param])) { + return $this->settings[$param]; + } + + return null; + } + + /** + * Checks if this is a merge table + * + * If the ENGINE of the table is MERGE or MRG_MYISAM (alias), + * this is a merge table. + * + * @param string $db the database name + * @param string $table the table name + * + * @return boolean true if it is a merge table + */ + static public function isMerge($db = null, $table = null) + { + $engine = null; + // if called static, with parameters + if (! empty($db) && ! empty($table)) { + $engine = PMA_Table::sGetStatusInfo( + $db, $table, 'ENGINE', null, true + ); + } + + // did we get engine? + if (empty($engine)) { + return false; + } + + // any of known merge engines? + return in_array(strtoupper($engine), array('MERGE', 'MRG_MYISAM')); + } + + /** + * Returns tooltip for the table + * Format : () + * + * @param string $db database name + * @param string $table table name + * + * @return string tooltip fot the table + */ + static public function sGetToolTip($db, $table) + { + return PMA_Table::sGetStatusInfo($db, $table, 'Comment') + . ' (' . PMA_Table::countRecords($db, $table) + . ' ' . __('Rows') . ')'; + } + + /** + * Returns full table status info, or specific if $info provided + * this info is collected from information_schema + * + * @param string $db database name + * @param string $table table name + * @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 + */ + static public function sGetStatusInfo($db, $table, $info = null, + $force_read = false, $disable_error = false + ) { + if (! empty($_SESSION['is_multi_query'])) { + $disable_error = true; + } + + if (! isset(PMA_Table::$cache[$db][$table]) + || $force_read + // sometimes there is only one entry (ExactRows) so + // we have to get the table's details + || count(PMA_Table::$cache[$db][$table]) == 1 + ) { + $GLOBALS['dbi']->getTablesFull($db, $table); + } + + if (! isset(PMA_Table::$cache[$db][$table])) { + // 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 PMA_Table::$cache[$db][$table]; + } + + // array_key_exists allows for null values + if (!array_key_exists($info, PMA_Table::$cache[$db][$table])) { + if (! $disable_error) { + trigger_error( + __('unknown table status: ') . $info, + E_USER_WARNING + ); + } + return false; + } + + return PMA_Table::$cache[$db][$table][$info]; + } + + /** + * generates column specification for ALTER or CREATE TABLE syntax + * + * @param string $name name + * @param string $type type ('INT', 'VARCHAR', 'BIT', ...) + * @param string $index index + * @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 array &$field_primary list of fields for PRIMARY KEY + * @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, $index, $length = '', + $attribute = '', $collation = '', $null = false, + $default_type = 'USER_DEFINED', $default_value = '', $extra = '', + $comment = '', &$field_primary = null, $move_to = '' + ) { + $is_timestamp = strpos(strtoupper($type), 'TIMESTAMP') !== false; + + $query = PMA_Util::backquote($name) . ' ' . $type; + + if ($length != '' + && ! preg_match( + '@^(DATE|DATETIME|TIME|TINYBLOB|TINYTEXT|BLOB|TEXT|' + . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|SERIAL|BOOLEAN|UUID)$@i', + $type + ) + ) { + $query .= '(' . $length . ')'; + } + + if ($attribute != '') { + $query .= ' ' . $attribute; + } + + $matches = preg_match( + '@^(TINYTEXT|TEXT|MEDIUMTEXT|LONGTEXT|VARCHAR|CHAR|ENUM|SET)$@i', + $type + ); + if (! empty($collation) && $collation != 'NULL' && $matches) { + $query .= PMA_generateCharsetQueryPart($collation); + } + + if ($null !== false) { + if ($null == 'NULL') { + $query .= ' NULL'; + } else { + $query .= ' NOT NULL'; + } + } + + 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 \'' + . PMA_Util::sqlAddSlashes($default_value) . '\''; + } + } else { + $query .= ' DEFAULT \'' + . PMA_Util::sqlAddSlashes($default_value) . '\''; + } + break; + case 'NULL' : + //If user uncheck null checkbox and not change default value null, + //default value will be ignored. + if ($null !== false && $null != 'NULL') { + break; + } + case 'CURRENT_TIMESTAMP' : + $query .= ' DEFAULT ' . $default_type; + break; + case 'NONE' : + default : + break; + } + + if (!empty($extra)) { + $query .= ' ' . $extra; + // Force an auto_increment field to be part of the primary key + // even if user did not tick the PK box; + if ($extra == 'AUTO_INCREMENT') { + $primary_cnt = count($field_primary); + if (1 == $primary_cnt) { + for ($j = 0; $j < $primary_cnt; $j++) { + if ($field_primary[$j] == $index) { + break; + } + } + if (isset($field_primary[$j]) && $field_primary[$j] == $index) { + $query .= ' PRIMARY KEY'; + unset($field_primary[$j]); + } + } else { + // but the PK could contain other columns so do not append + // a PRIMARY KEY clause, just add a member to $field_primary + $found_in_pk = false; + for ($j = 0; $j < $primary_cnt; $j++) { + if ($field_primary[$j] == $index) { + $found_in_pk = true; + break; + } + } // end for + if (! $found_in_pk) { + $field_primary[] = $index; + } + } + } // end if (auto_increment) + } + if (!empty($comment)) { + $query .= " COMMENT '" . PMA_Util::sqlAddSlashes($comment) . "'"; + } + + // move column + if ($move_to == '-first') { // dash can't appear as part of column name + $query .= ' FIRST'; + } elseif ($move_to != '') { + $query .= ' AFTER ' . PMA_Util::backquote($move_to); + } + return $query; + } // end function + + /** + * Counts and returns (or displays) the number of records in a table + * + * @param string $db the current database name + * @param string $table the current table name + * @param bool $force_exact whether to force an exact count + * @param bool $is_view whether the table is a view + * + * @return mixed the number of records if "retain" param is true, + * otherwise true + */ + static public function countRecords($db, $table, $force_exact = false, + $is_view = null + ) { + if (isset(PMA_Table::$cache[$db][$table]['ExactRows'])) { + $row_count = PMA_Table::$cache[$db][$table]['ExactRows']; + } else { + $row_count = false; + + if (null === $is_view) { + $is_view = PMA_Table::isView($db, $table); + } + + if (! $force_exact) { + if (! isset(PMA_Table::$cache[$db][$table]['Rows']) && ! $is_view) { + $tmp_tables = $GLOBALS['dbi']->getTablesFull($db, $table); + if (isset($tmp_tables[$table])) { + PMA_Table::$cache[$db][$table] = $tmp_tables[$table]; + } + } + if (isset(PMA_Table::$cache[$db][$table]['Rows'])) { + $row_count = PMA_Table::$cache[$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'] + ) { + // Make an exception for views in I_S and D_D schema in + // Drizzle, as these map to in-memory data and should execute + // fast enough + if (! $is_view + || (PMA_DRIZZLE && $GLOBALS['dbi']->isSystemSchema($db)) + ) { + $row_count = $GLOBALS['dbi']->fetchValue( + 'SELECT COUNT(*) FROM ' . PMA_Util::backquote($db) . '.' + . PMA_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 = 0; + } 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 = $GLOBALS['dbi']->tryQuery( + 'SELECT 1 FROM ' . PMA_Util::backquote($db) . '.' + . PMA_Util::backquote($table) . ' LIMIT ' + . $GLOBALS['cfg']['MaxExactCountViews'], + null, + PMA_DatabaseInterface::QUERY_STORE + ); + if (!$GLOBALS['dbi']->getError()) { + $row_count = $GLOBALS['dbi']->numRows($result); + $GLOBALS['dbi']->freeResult($result); + } + } + } + if ($row_count) { + PMA_Table::$cache[$db][$table]['ExactRows'] = $row_count; + } + } + } + + return $row_count; + } // end of the 'PMA_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 array &$field_primary list of fields for PRIMARY KEY + * @param string $index index + * @param string $move_to new position for column + * + * @see PMA_Table::generateFieldSpec() + * + * @return string field specification + */ + static public function generateAlter($oldcol, $newcol, $type, $length, + $attribute, $collation, $null, $default_type, $default_value, + $extra, $comment, &$field_primary, $index, $move_to + ) { + return PMA_Util::backquote($oldcol) . ' ' + . PMA_Table::generateFieldSpec( + $newcol, $type, $index, $length, $attribute, + $collation, $null, $default_type, $default_value, $extra, + $comment, $field_primary, $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|true + */ + static public function duplicateInfo($work, $pma_table, $get_fields, + $where_fields, $new_fields + ) { + $last_id = -1; + + if (isset($GLOBALS['cfgRelation']) && $GLOBALS['cfgRelation'][$work]) { + $select_parts = array(); + $row_fields = array(); + foreach ($get_fields as $get_field) { + $select_parts[] = PMA_Util::backquote($get_field); + $row_fields[$get_field] = 'cc'; + } + + $where_parts = array(); + foreach ($where_fields as $_where => $_value) { + $where_parts[] = PMA_Util::backquote($_where) . ' = \'' + . PMA_Util::sqlAddSlashes($_value) . '\''; + } + + $new_parts = array(); + $new_value_parts = array(); + foreach ($new_fields as $_where => $_value) { + $new_parts[] = PMA_Util::backquote($_where); + $new_value_parts[] = PMA_Util::sqlAddSlashes($_value); + } + + $table_copy_query = ' + SELECT ' . implode(', ', $select_parts) . ' + FROM ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . PMA_Util::backquote($GLOBALS['cfgRelation'][$pma_table]) . ' + WHERE ' . implode(' AND ', $where_parts); + + // must use PMA_DatabaseInterface::QUERY_STORE here, since we execute + // another query inside the loop + $table_copy_rs = PMA_queryAsControlUser( + $table_copy_query, true, PMA_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[] = PMA_Util::sqlAddSlashes($_val); + } + } + + $new_table_query = 'INSERT IGNORE INTO ' + . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' + . PMA_Util::backquote($GLOBALS['cfgRelation'][$pma_table]) + . ' (' . implode(', ', $select_parts) + . ', ' . implode(', ', $new_parts) + . ') VALUES (\'' + . implode('\', \'', $value_parts) . '\', \'' + . implode('\', \'', $new_value_parts) . '\')'; + + PMA_queryAsControlUser($new_table_query); + $last_id = $GLOBALS['dbi']->insertId(); + } // end while + + $GLOBALS['dbi']->freeResult($table_copy_rs); + + return $last_id; + } + + return true; + } // end of 'PMA_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 + */ + static public function moveCopy($source_db, $source_table, $target_db, + $target_table, $what, $move, $mode + ) { + global $err_url; + + /* Try moving table directly */ + if ($move && $what == 'data') { + $tbl = new PMA_Table($source_table, $source_db); + $result = $tbl->rename($target_table, $target_db); + if ($result) { + $GLOBALS['message'] = $tbl->getLastMessage(); + return true; + } + } + + // set export settings we need + $GLOBALS['sql_backquotes'] = 1; + $GLOBALS['asfile'] = 1; + + // Ensure the target is valid + if (! $GLOBALS['pma']->databases->exists($source_db, $target_db)) { + if (! $GLOBALS['pma']->databases->exists($source_db)) { + $GLOBALS['message'] = PMA_Message::rawError( + sprintf( + __('Source database `%s` was not found!'), + htmlspecialchars($source_db) + ) + ); + } + if (! $GLOBALS['pma']->databases->exists($target_db)) { + $GLOBALS['message'] = PMA_Message::rawError( + sprintf( + __('Target database `%s` was not found!'), + htmlspecialchars($target_db) + ) + ); + } + return false; + } + + $source = PMA_Util::backquote($source_db) + . '.' . PMA_Util::backquote($source_table); + if (! isset($target_db) || ! strlen($target_db)) { + $target_db = $source_db; + } + + // Doing a select_db could avoid some problems with replicated databases, + // when moving table from replicated one to not replicated one + $GLOBALS['dbi']->selectDb($target_db); + + $target = PMA_Util::backquote($target_db) + . '.' . PMA_Util::backquote($target_table); + + // do not create the table if dataonly + if ($what != 'dataonly') { + include_once "libraries/plugin_interface.lib.php"; + // get Export SQL instance + $export_sql_plugin = PMA_getPlugin( + "export", + "sql", + 'libraries/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']; + } + + $sql_structure = $export_sql_plugin->getTableDef( + $source_db, $source_table, "\n", $err_url, false, false + ); + unset($no_constraints_comments); + $parsed_sql = PMA_SQP_parse($sql_structure); + $analyzed_sql = PMA_SQP_analyze($parsed_sql); + $i = 0; + if (empty($analyzed_sql[0]['create_table_fields'])) { + // this is not a CREATE TABLE, so find the first VIEW + $target_for_view = PMA_Util::backquote($target_db); + while (true) { + if ($parsed_sql[$i]['type'] == 'alpha_reservedWord' + && $parsed_sql[$i]['data'] == 'VIEW' + ) { + break; + } + $i++; + } + } + unset($analyzed_sql); + if (PMA_DRIZZLE) { + $table_delimiter = 'quote_backtick'; + } else { + $server_sql_mode = $GLOBALS['dbi']->fetchValue( + "SHOW VARIABLES LIKE 'sql_mode'", + 0, + 1 + ); + // ANSI_QUOTES might be a subset of sql_mode, for example + // REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ANSI + if (false !== strpos($server_sql_mode, 'ANSI_QUOTES')) { + $table_delimiter = 'quote_double'; + } else { + $table_delimiter = 'quote_backtick'; + } + unset($server_sql_mode); + } + + /* Find table name in query and replace it */ + while ($parsed_sql[$i]['type'] != $table_delimiter) { + $i++; + } + + /* no need to backquote() */ + if (isset($target_for_view)) { + // this a view definition; we just found the first db name + // that follows DEFINER VIEW + // so change it for the new db name + $parsed_sql[$i]['data'] = $target_for_view; + // then we have to find all references to the source db + // and change them to the target db, ensuring we stay into + // the $parsed_sql limits + $last = $parsed_sql['len'] - 1; + $backquoted_source_db = PMA_Util::backquote($source_db); + for (++$i; $i <= $last; $i++) { + if ($parsed_sql[$i]['type'] == $table_delimiter + && $parsed_sql[$i]['data'] == $backquoted_source_db + && $parsed_sql[$i - 1]['type'] != 'punct_qualifier' + ) { + $parsed_sql[$i]['data'] = $target_for_view; + } + } + unset($last,$backquoted_source_db); + } else { + $parsed_sql[$i]['data'] = $target; + } + + /* Generate query back */ + $sql_structure = PMA_SQP_format($parsed_sql, 'query_only'); + // If table exists, and 'add drop table' is selected: Drop it! + $drop_query = ''; + if (isset($_REQUEST['drop_if_exists']) + && $_REQUEST['drop_if_exists'] == 'true' + ) { + if (PMA_Table::isView($target_db, $target_table)) { + $drop_query = 'DROP VIEW'; + } else { + $drop_query = 'DROP TABLE'; + } + $drop_query .= ' IF EXISTS ' + . PMA_Util::backquote($target_db) . '.' + . PMA_Util::backquote($target_table); + $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; + } + + @$GLOBALS['dbi']->query($sql_structure); + $GLOBALS['sql_query'] .= "\n" . $sql_structure . ';'; + + if (($move || isset($GLOBALS['add_constraints'])) + && !empty($GLOBALS['sql_constraints_query']) + ) { + $parsed_sql = PMA_SQP_parse($GLOBALS['sql_constraints_query']); + $i = 0; + + // find the first $table_delimiter, it must be the source + // table name + while ($parsed_sql[$i]['type'] != $table_delimiter) { + $i++; + // maybe someday we should guard against going over limit + //if ($i == $parsed_sql['len']) { + // break; + //} + } + + // replace it by the target table name, no need + // to backquote() + $parsed_sql[$i]['data'] = $target; + + // now we must remove all $table_delimiter that follow a + // CONSTRAINT keyword, because a constraint name must be + // unique in a db + + $cnt = $parsed_sql['len'] - 1; + + for ($j = $i; $j < $cnt; $j++) { + if ($parsed_sql[$j]['type'] == 'alpha_reservedWord' + && strtoupper($parsed_sql[$j]['data']) == 'CONSTRAINT' + ) { + if ($parsed_sql[$j+1]['type'] == $table_delimiter) { + $parsed_sql[$j+1]['data'] = ''; + } + } + } + + // Generate query back + $GLOBALS['sql_constraints_query'] = PMA_SQP_format( + $parsed_sql, 'query_only' + ); + 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']); + } + } + } else { + $GLOBALS['sql_query'] = ''; + } + + // Copy the data unless this is a VIEW + if (($what == 'data' || $what == 'dataonly') + && ! PMA_Table::isView($target_db, $target_table) + ) { + $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 . ';'; + + $sql_insert_data = 'INSERT INTO ' . $target + . ' SELECT * FROM ' . $source; + $GLOBALS['dbi']->query($sql_insert_data); + $GLOBALS['sql_query'] .= "\n\n" . $sql_insert_data . ';'; + } + + $GLOBALS['cfgRelation'] = PMA_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); + + if (PMA_Table::isView($source_db, $source_table)) { + $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 + PMA_REL_renameTable( + $source_db, $target_db, + $source_table, $target_table + ); + + $GLOBALS['sql_query'] .= "\n\n" . $sql_drop_query . ';'; + // end if ($move) + } else { + // we are copying + // Create new entries as duplicates from old PMA DBs + if ($what != 'dataonly' && ! isset($maintain_relations)) { + if ($GLOBALS['cfgRelation']['commwork']) { + // Get all comments and MIME-Types for current table + $comments_copy_rs = PMA_queryAsControlUser( + 'SELECT column_name, comment' + . ($GLOBALS['cfgRelation']['mimework'] + ? ', mimetype, transformation, transformation_options' + : '') + . ' FROM ' + . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' + . PMA_Util::backquote($GLOBALS['cfgRelation']['column_info']) + . ' WHERE ' + . ' db_name = \'' + . PMA_Util::sqlAddSlashes($source_db) . '\'' + . ' AND ' + . ' table_name = \'' + . PMA_Util::sqlAddSlashes($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 ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.' . PMA_Util::backquote($GLOBALS['cfgRelation']['column_info']) + . ' (db_name, table_name, column_name, comment' . ($GLOBALS['cfgRelation']['mimework'] ? ', mimetype, transformation, transformation_options' : '') . ') ' + . ' VALUES(' + . '\'' . PMA_Util::sqlAddSlashes($target_db) . '\',' + . '\'' . PMA_Util::sqlAddSlashes($target_table) . '\',' + . '\'' . PMA_Util::sqlAddSlashes($comments_copy_row['column_name']) . '\'' + . ($GLOBALS['cfgRelation']['mimework'] ? ',\'' . PMA_Util::sqlAddSlashes($comments_copy_row['comment']) . '\',' + . '\'' . PMA_Util::sqlAddSlashes($comments_copy_row['mimetype']) . '\',' + . '\'' . PMA_Util::sqlAddSlashes($comments_copy_row['transformation']) . '\',' + . '\'' . PMA_Util::sqlAddSlashes($comments_copy_row['transformation_options']) . '\'' : '') + . ')'; + PMA_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 + ); + PMA_Table::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 + ); + PMA_Table::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 + ); + PMA_Table::duplicateInfo( + 'relwork', + 'relation', + $get_fields, + $where_fields, + $new_fields + ); + + + $get_fields = array('x', 'y', 'v', 'h'); + $where_fields = array( + 'db_name' => $source_db, + 'table_name' => $source_table + ); + $new_fields = array( + 'db_name' => $target_db, + 'table_name' => $target_table + ); + PMA_Table::duplicateInfo( + 'designerwork', + 'designer_coords', + $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 = PMA_Table::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 + ); + PMA_Table::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 + * + * @todo add check for valid chars in filename on current system/os + * @see http://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) + { + if ($table_name !== trim($table_name)) { + // trailing spaces + return false; + } + + if (! strlen($table_name)) { + // zero length + return false; + } + + if (preg_match('/[.\/\\\\]+/i', $table_name)) { + // illegal char . / \ + return false; + } + + return true; + } + + /** + * renames table + * + * @param string $new_name new table name + * @param string $new_db new database name + * + * @return bool success + */ + function rename($new_name, $new_db = null) + { + if (null !== $new_db && $new_db !== $this->getDbName()) { + // Ensure the target is valid + if (! $GLOBALS['pma']->databases->exists($new_db)) { + $this->errors[] = __('Invalid database:') . ' ' . $new_db; + return false; + } + } else { + $new_db = $this->getDbName(); + } + + $new_table = new PMA_Table($new_name, $new_db); + + if ($this->getFullName() === $new_table->getFullName()) { + return true; + } + + if (! PMA_Table::isValidName($new_name)) { + $this->errors[] = __('Invalid table name:') . ' ' + . $new_table->getFullName(); + return false; + } + + // If the table is moved to a different database drop its triggers first + $triggers = $GLOBALS['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 ' + . PMA_Util::backquote($this->getDbName()) + . '.' . PMA_Util::backquote($trigger['name']) . ';'; + $GLOBALS['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 (! $GLOBALS['dbi']->query($GLOBALS['sql_query'])) { + // Restore triggers in the old database + if ($handle_triggers) { + $GLOBALS['dbi']->selectDb($this->getDbName()); + foreach ($triggers as $trigger) { + $GLOBALS['dbi']->query($trigger['create']); + } + } + $this->errors[] = sprintf( + __('Error renaming table %1$s to %2$s'), + $this->getFullName(), + $new_table->getFullName() + ); + return false; + } + + $old_name = $this->getName(); + $old_db = $this->getDbName(); + $this->setName($new_name); + $this->setDbName($new_db); + + // Renable table in configuration storage + PMA_REL_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 unqiue 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 = $GLOBALS['dbi']->getTableIndexesSql( + $this->getDbName(), + $this->getName(), + 'Non_unique = 0' + ); + $uniques = $GLOBALS['dbi']->fetchResult( + $sql, + array('Key_name', null), + 'Column_name' + ); + + $return = array(); + foreach ($uniques as $index) { + if (count($index) > 1) { + continue; + } + $return[] = ($fullName ? $this->getFullName($backquoted) . '.' : '') + . ($backquoted ? PMA_Util::backquote($index[0]) : $index[0]); + } + + return $return; + } + + /** + * Get all indexed columns + * + * returns an array with all columns make use of an index, in fact only + * first columns in an index + * + * e.g. index(col1, col2) would only return col1 + * + * @param bool $backquoted whether to quote name with backticks `` + * + * @return array + */ + public function getIndexedColumns($backquoted = true) + { + $sql = $GLOBALS['dbi']->getTableIndexesSql( + $this->getDbName(), + $this->getName(), + 'Seq_in_index = 1' + ); + $indexed = $GLOBALS['dbi']->fetchResult($sql, 'Column_name', 'Column_name'); + + $return = array(); + foreach ($indexed as $column) { + $return[] = $this->getFullName($backquoted) . '.' + . ($backquoted ? PMA_Util::backquote($column) : $column); + } + + return $return; + } + + /** + * Get all columns + * + * returns an array with all columns + * + * @param bool $backquoted whether to quote name with backticks `` + * + * @return array + */ + public function getColumns($backquoted = true) + { + $sql = 'SHOW COLUMNS FROM ' . $this->getFullName(true); + $indexed = $GLOBALS['dbi']->fetchResult($sql, 'Field', 'Field'); + + $return = array(); + foreach ($indexed as $column) { + $return[] = $this->getFullName($backquoted) . '.' + . ($backquoted ? PMA_Util::backquote($column) : $column); + } + + return $return; + } + + /** + * Return UI preferences for this table from phpMyAdmin database. + * + * @return array + */ + protected function getUiPrefsFromDb() + { + $pma_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) ."." + . PMA_Util::backquote($GLOBALS['cfg']['Server']['table_uiprefs']); + + // Read from phpMyAdmin database + $sql_query = " SELECT `prefs` FROM " . $pma_table + . " WHERE `username` = '" . $GLOBALS['cfg']['Server']['user'] . "'" + . " AND `db_name` = '" . PMA_Util::sqlAddSlashes($this->db_name) . "'" + . " AND `table_name` = '" . PMA_Util::sqlAddSlashes($this->name) . "'"; + + $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query)); + if (isset($row[0])) { + return json_decode($row[0], true); + } else { + return array(); + } + } + + /** + * Save this table's UI preferences into phpMyAdmin database. + * + * @return true|PMA_Message + */ + protected function saveUiPrefsToDb() + { + $pma_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) . "." + . PMA_Util::backquote($GLOBALS['cfg']['Server']['table_uiprefs']); + + $username = $GLOBALS['cfg']['Server']['user']; + $sql_query = " REPLACE INTO " . $pma_table + . " VALUES ('" . $username . "', '" . PMA_Util::sqlAddSlashes($this->db_name) + . "', '" . PMA_Util::sqlAddSlashes($this->name) . "', '" + . PMA_Util::sqlAddSlashes(json_encode($this->uiprefs)) . "', NULL)"; + + $success = $GLOBALS['dbi']->tryQuery($sql_query, $GLOBALS['controllink']); + + if (!$success) { + $message = PMA_Message::error( + __('Could not save table UI preferences') + ); + $message->addMessage('

      '); + $message->addMessage( + PMA_Message::rawError( + $GLOBALS['dbi']->getError($GLOBALS['controllink']) + ) + ); + 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 = $GLOBALS['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 = $GLOBALS['dbi']->tryQuery( + $sql_query, $GLOBALS['controllink'] + ); + + if (!$success) { + $message = PMA_Message::error( + sprintf( + __('Failed to cleanup table UI preferences (see $cfg[\'Servers\'][$i][\'MaxTableUiprefs\'] %s)'), + PMA_Util::showDocu('config', 'cfg_Servers_MaxTableUiprefs') + ) + ); + $message->addMessage('

      '); + $message->addMessage( + PMA_Message::rawError( + $GLOBALS['dbi']->getError($GLOBALS['controllink']) + ) + ); + print_r($message); + 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() + { + $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] + = (strlen($GLOBALS['cfg']['Server']['pmadb']) + && strlen($GLOBALS['cfg']['Server']['table_uiprefs'])) + ? $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])) { + // 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, + strlen($each_col) - strlen($colname) + ) === 0) { + return $this->uiprefs[$property]; + } + } + // remove the property, since it is not exist anymore in database + $this->removeUiProp(self::PROP_SORTED_COLUMN); + return false; + } else { + return false; + } + } elseif ($property == self::PROP_COLUMN_ORDER + || $property == self::PROP_COLUMN_VISIB + ) { + if (! PMA_Table::isView($this->db_name, $this->name) + && isset($this->uiprefs[$property]) + ) { + // check if the table has not been modified + if (self::sGetStatusInfo( + $this->db_name, + $this->name, 'Create_time' + ) == $this->uiprefs['CREATE_TIME']) { + return $this->uiprefs[$property]; + } else { + // remove the property, since the table has been modified + $this->removeUiProp(self::PROP_COLUMN_ORDER); + return false; + } + } else { + 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|PMA_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 (! PMA_Table::isView($this->db_name, $this->name) + && ($property == self::PROP_COLUMN_ORDER + || $property == self::PROP_COLUMN_VISIB) + ) { + $curr_create_time = self::sGetStatusInfo( + $this->db_name, + $this->name, + '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 PMA_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 + if (strlen($GLOBALS['cfg']['Server']['pmadb']) + && strlen($GLOBALS['cfg']['Server']['table_uiprefs']) + ) { + return $this->saveUiprefsToDb(); + } + return true; + } + + /** + * Remove a property from UI preferences. + * + * @param string $property the property + * + * @return true|PMA_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 + if (strlen($GLOBALS['cfg']['Server']['pmadb']) + && strlen($GLOBALS['cfg']['Server']['table_uiprefs']) + ) { + 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 (PMA_SQP_isKeyWord($column_name)) { + $return[] = $column_name; + } + } + return $return; + } +} +?> diff --git a/sources/libraries/TableSearch.class.php b/sources/libraries/TableSearch.class.php new file mode 100644 index 0000000..ec273ed --- /dev/null +++ b/sources/libraries/TableSearch.class.php @@ -0,0 +1,1469 @@ +_db = $db; + $this->_table = $table; + $this->_searchType = $searchType; + $this->_columnNames = array(); + $this->_columnNullFlags = array(); + $this->_columnTypes = array(); + $this->_columnCollations = array(); + $this->_geomColumnFlag = false; + $this->_foreigners = array(); + // Loads table's information + $this->_loadTableInfo(); + } + + /** + * Returns Column names array + * + * @return array column names + */ + public function getColumnNames() + { + return $this->_columnNames; + } + + /** + * 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 = $GLOBALS['dbi']->getColumns( + $this->_db, $this->_table, null, true + ); + // Get details about the geometry fucntions + $geom_types = PMA_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 = 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 = PMA_getForeigners($this->_db, $this->_table); + } + + /** + * Sets the table header for displaying a table in query-by-example format. + * + * @return string HTML content, the tags and content for table header + */ + private function _getTableHeader() + { + // Display the Function column only if there is at least one geometry column + $func = ''; + if ($this->_geomColumnFlag) { + $func = '' . __('Function') . ''; + } + + return ' + ' . $func . '' . __('Column') . ' + ' . __('Type') . ' + ' . __('Collation') . ' + ' . __('Operator') . ' + ' . __('Value') . ' + + '; + } + + /** + * 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.png'; + $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_props.png'; + $subtabs['zoom']['link'] = 'tbl_zoom_select.php'; + $subtabs['zoom']['text'] = __('Zoom Search'); + $subtabs['zoom']['id'] = 'zoom_search_id'; + + $subtabs['replace']['icon'] = 'b_find_replace.png'; + $subtabs['replace']['link'] = 'tbl_find_replace.php'; + $subtabs['replace']['text'] = __('Find and Replace'); + $subtabs['replace']['id'] = 'find_replace_id'; + + return $subtabs; + } + + /** + * Provides html elements for search criteria inputbox + * in case the column's type is geometrical + * + * @param int $column_index Column's index + * @param bool $in_fbs Whether we are in 'function based search' + * + * @return string HTML elements. + */ + private function _getGeometricalInputBox($column_index, $in_fbs) + { + $html_output = ''; + + if ($in_fbs) { + $edit_url = 'gis_data_editor.php?' . PMA_URL_getCommon(); + $edit_str = PMA_Util::getIcon('b_edit.png', __('Edit/Insert')); + $html_output .= ''; + $html_output .= PMA_Util::linkOrButton( + $edit_url, $edit_str, array(), false, false, '_blank' + ); + $html_output .= ''; + } + return $html_output; + } + + /** + * Provides html elements for search criteria inputbox + * in case the column is a Foreign Key + * + * @param array $foreignData Foreign keys data + * @param string $column_name Column name + * @param int $column_index Column index + * @param array $titles Selected title + * @param int $foreignMaxLimit Max limit of displaying foreign elements + * @param array $criteriaValues Array of search criteria inputs + * @param string $column_id Column's inputbox's id + * @param bool $in_zoom_search_edit Whether we are in zoom search edit + * + * @return string HTML elements. + */ + private function _getForeignKeyInputBox($foreignData, $column_name, + $column_index, $titles, $foreignMaxLimit, $criteriaValues, $column_id, + $in_zoom_search_edit = false + ) { + $html_output = ''; + if (is_array($foreignData['disp_row'])) { + $html_output .= ''; + + } elseif ($foreignData['foreign_link'] == true) { + $html_output .= ''; + + $html_output .= <<'; + } + return $html_output; + } + + /** + * Provides html elements for search criteria inputbox + * in case the column is of ENUM or SET type + * + * @param int $column_index Column index + * @param array $criteriaValues Array of search criteria inputs + * @param string $column_type Column type + * @param string $column_id Column's inputbox's id + * @param bool $in_zoom_search_edit Whether we are in zoom search edit + * + * @return string HTML elements. + */ + private function _getEnumSetInputBox($column_index, $criteriaValues, + $column_type, $column_id, $in_zoom_search_edit = false + ) { + $html_output = ''; + $value = explode( + ', ', + str_replace("'", '', substr($column_type, 5, -1)) + ); + $cnt_value = count($value); + + /* + * 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 ((strncasecmp($column_type, 'enum', 4) && ! $in_zoom_search_edit) + || (strncasecmp($column_type, 'set', 3) && $in_zoom_search_edit) + ) { + $html_output .= ''; + } + + //Add select options + for ($j = 0; $j < $cnt_value; $j++) { + if (isset($criteriaValues[$column_index]) + && is_array($criteriaValues[$column_index]) + && in_array($value[$j], $criteriaValues[$column_index]) + ) { + $html_output .= ''; + } else { + $html_output .= ''; + } + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Creates the HTML content for: + * 1) Browsing foreign data for a column. + * 2) Creating elements for search criteria input on columns. + * + * @param array $foreignData Foreign keys data + * @param string $column_name Column name + * @param string $column_type Column type + * @param int $column_index Column index + * @param array $titles Selected title + * @param int $foreignMaxLimit Max limit of displaying foreign elements + * @param array $criteriaValues Array of search criteria inputs + * @param bool $in_fbs Whether we are in 'function based search' + * @param bool $in_zoom_search_edit Whether we are in zoom search edit + * + * @return string HTML content for viewing foreign data and elements + * for search criteria input. + */ + private function _getInputbox($foreignData, $column_name, $column_type, + $column_index, $titles, $foreignMaxLimit, $criteriaValues, $in_fbs = false, + $in_zoom_search_edit = false + ) { + $str = ''; + $column_type = (string)$column_type; + $column_id = ($in_zoom_search_edit) ? 'edit_fieldID_' : 'fieldID_'; + + // Get inputbox based on different column types + // (Foreign key, geometrical, enum) + if ($this->_foreigners && isset($this->_foreigners[$column_name])) { + $str .= $this->_getForeignKeyInputBox( + $foreignData, $column_name, $column_index, $titles, + $foreignMaxLimit, $criteriaValues, $column_id + ); + + } elseif (in_array($column_type, PMA_Util::getGISDatatypes())) { + $str .= $this->_getGeometricalInputBox($column_index, $in_fbs); + + } elseif (strncasecmp($column_type, 'enum', 4) == 0 + || (strncasecmp($column_type, 'set', 3) == 0 && $in_zoom_search_edit) + ) { + $str .= $this->_getEnumSetInputBox( + $column_index, $criteriaValues, $column_type, $column_id, + $in_zoom_search_edit = false + ); + + } else { + // other cases + $the_class = 'textfield'; + + if ($column_type == 'date') { + $the_class .= ' datefield'; + } elseif ($column_type == 'datetime' + || substr($column_type, 0, 9) == 'timestamp' + ) { + $the_class .= ' datetimefield'; + } elseif (substr($column_type, 0, 3) == 'bit') { + $the_class .= ' bit'; + } + + $str .= ''; + } + return $str; + } + + /** + * 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 = '\'' + . PMA_Util::sqlAddSlashes($criteriaValues[0]) . '\''; + for ($e = 1; $e < $enum_selected_count; $e++) { + $enum_where .= ', \'' + . PMA_Util::sqlAddSlashes($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 = PMA_Util::getGISFunctions($types, true, false); + // New output type is the output type of the function being applied + $types = $geom_funcs[$geom_func]['type']; + + // If the function takes a single parameter + if ($geom_funcs[$geom_func]['params'] == 1) { + $backquoted_name = $geom_func . '(' . PMA_Util::backquote($names) . ')'; + } else { + // If the function takes two parameters + // create gis data from the criteria input + $gis_data = PMA_Util::createGISData($criteriaValues); + $where = $geom_func . '(' . PMA_Util::backquote($names) + . ',' . $gis_data . ')'; + return $where; + } + + // If the where clause is something like 'IsEmpty(`spatial_col_name`)' + if (isset($geom_unary_functions[$geom_func]) + && trim($criteriaValues) == '' + ) { + $where = $backquoted_name; + + } elseif (in_array($types, PMA_Util::getGISDatatypes()) + && ! empty($criteriaValues) + ) { + // create gis data from the criteria input + $gis_data = PMA_Util::createGISData($criteriaValues); + $where = $backquoted_name . ' ' . $func_type . ' ' . $gis_data; + } + 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 $collations Field collation + * @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, $collations, + $func_type, $unaryFlag, $geom_func = null + ) { + // If geometry function is set + if ($geom_func != null && trim($geom_func) != '') { + return $this->_getGeomWhereClause( + $criteriaValues, $names, $func_type, $types, $geom_func + ); + } + + $backquoted_name = PMA_Util::backquote($names); + $where = ''; + if ($unaryFlag) { + $criteriaValues = ''; + $where = $backquoted_name . ' ' . $func_type; + + } elseif (strncasecmp($types, 'enum', 4) == 0 && ! empty($criteriaValues)) { + $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) + || 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 + ) { + $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 . PMA_Util::sqlAddSlashes(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 . ')'; + } + } + } else { + if ($func_type == 'LIKE %...%' || $func_type == 'LIKE') { + $where = $backquoted_name . ' ' . $func_type . ' ' . $quot + . PMA_Util::sqlAddSlashes($criteriaValues, true) . $quot; + } else { + $where = $backquoted_name . ' ' . $func_type . ' ' . $quot + . PMA_Util::sqlAddSlashes($criteriaValues) . $quot; + } + } + } // end if + + return $where; + } + + /** + * Builds the sql search query from the post parameters + * + * @return string the generated SQL query + */ + public 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( + ', ', + PMA_Util::backquote($_POST['columnsToDisplay']) + ); + } // end if + + $sql_query .= ' FROM ' + . PMA_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 ' + . PMA_Util::backquote($_POST['orderByColumn']) + . ' ' . $_POST['order']; + } // end if + return $sql_query; + } + + /** + * 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']) + ) { + return ''; + } + + // else continue to form the where clause from column criteria values + $fullWhereClause = $charsets = array(); + reset($_POST['criteriaColumnOperators']); + while (list($column_index, $operator) = each( + $_POST['criteriaColumnOperators'] + )) { + list($charsets[$column_index]) = explode( + '_', $_POST['criteriaColumnCollations'][$column_index] + ); + $unaryFlag = $GLOBALS['PMA_Types']->isUnaryOperator($operator); + $tmp_geom_func = isset($geom_func[$column_index]) + ? $geom_func[$column_index] : null; + + $whereClause = $this->_getWhereClause( + $_POST['criteriaValues'][$column_index], + $_POST['criteriaColumnNames'][$column_index], + $_POST['criteriaColumnTypes'][$column_index], + $_POST['criteriaColumnCollations'][$column_index], + $operator, + $unaryFlag, + $tmp_geom_func + ); + + if ($whereClause) { + $fullWhereClause[] = $whereClause; + } + } // end while + + if ($fullWhereClause) { + return ' WHERE ' . implode(' AND ', $fullWhereClause); + } + return ''; + } + + /** + * Generates HTML for a geometrical function column to be displayed in table + * search selection form + * + * @param integer $column_index index of current column in $columnTypes array + * + * @return string the generated HTML + */ + private function _getGeomFuncHtml($column_index) + { + $html_output = ''; + // return if geometrical column is not present + if (! $this->_geomColumnFlag) { + return $html_output; + } + + /** + * Displays 'Function' column if it is present + */ + $html_output .= ''; + $geom_types = PMA_Util::getGISDatatypes(); + // if a geometry column is present + if (in_array($this->_columnTypes[$column_index], $geom_types)) { + $html_output .= ''; + } else { + $html_output .= ' '; + } + $html_output .= ''; + return $html_output; + } + + /** + * Generates formatted HTML for extra search options in table search form + * + * @return string the generated HTML + */ + private function _getOptions() + { + $html_output = ''; + $html_output .= PMA_Util::getDivForSliderEffect( + 'searchoptions', __('Options') + ); + + /** + * Displays columns select list for selecting distinct columns in the search + */ + $html_output .= '
      ' + . '' . __('Select columns (at least one):') . '' + . '' + . '' + . '
      '; + + /** + * Displays input box for custom 'Where' clause to be used in the search + */ + $html_output .= '
      ' + . '' . '' . __('Or') . ' ' + . __('Add search conditions (body of the "where" clause):') . ''; + $html_output .= PMA_Util::showMySQLDocu('Functions'); + $html_output .= ''; + $html_output .= '
      '; + + /** + * Displays option of changing default number of rows displayed per page + */ + $html_output .= '
      ' + . '' . __('Number of rows per page') . '' + . '' + . '
      '; + + /** + * Displays option for ordering search results + * by a column value (Asc or Desc) + */ + $html_output .= '
      ' + . '' . __('Display order:') . '' + . ''; + $choices = array( + 'ASC' => __('Ascending'), + 'DESC' => __('Descending') + ); + $html_output .= PMA_Util::getRadioFields( + 'order', $choices, 'ASC', false, true, "formelement" + ); + unset($choices); + + $html_output .= '

      '; + return $html_output; + } + + /** + * Other search criteria like data label + * (for tbl_zoom_select.php) + * + * @param array $dataLabel Label for points in zoom plot + * + * @return string the generated html + */ + private function _getOptionsZoom($dataLabel) + { + $html_output = ''; + $html_output .= ''; + //Select options for datalabel + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + //Inputbox for changing default maximum rows to plot + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= ''; + $html_output .= '
      '; + return $html_output; + } + + /** + * Provides a column's type, collation, operators list, and crietria 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 contaning column's properties + */ + public function getColumnProperties($search_index, $column_index) + { + $selected_operator = (isset($_POST['criteriaColumnOperators']) + ? $_POST['criteriaColumnOperators'][$search_index] : ''); + $entered_value = (isset($_POST['criteriaValues']) + ? $_POST['criteriaValues'] : ''); + $titles['Browse'] = PMA_Util::getIcon( + 'b_browse.png', __('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 + $func = ''; + //Gets link to browse foreign data(if any) and criteria inputbox + $foreignData = PMA_getForeignData( + $this->_foreigners, $this->_columnNames[$column_index], false, '', '' + ); + $value = $this->_getInputbox( + $foreignData, $this->_columnNames[$column_index], $type, $search_index, + $titles, $GLOBALS['cfg']['ForeignKeyMaxLimit'], $entered_value + ); + return array( + 'type' => $type, + 'collation' => $collation, + 'func' => $func, + 'value' => $value + ); + } + + /** + * Provides the search form's table row in case of Normal Search + * (for tbl_select.php) + * + * @return string the generated table row + */ + private function _getRowsNormal() + { + $odd_row = true; + $html_output = ''; + // for every column present in table + for ( + $column_index = 0; + $column_index < count($this->_columnNames); + $column_index++ + ) { + $html_output .= ''; + $odd_row = !$odd_row; + //If 'Function' column is present + $html_output .= $this->_getGeomFuncHtml($column_index); + //Displays column's name, type, collation and value + $html_output .= '' + . htmlspecialchars($this->_columnNames[$column_index]) . ''; + $properties = $this->getColumnProperties($column_index, $column_index); + $html_output .= '' . $properties['type'] . ''; + $html_output .= '' . $properties['collation'] . ''; + $html_output .= '' . $properties['func'] . ''; + $html_output .= '' . $properties['value'] . ''; + $html_output .= ''; + //Displays hidden fields + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + } // end for + + return $html_output; + } + + /** + * Provides the search form's table row in case of Zoom Search + * (for tbl_zoom_select.php) + * + * @return string the generated table row + */ + private function _getRowsZoom() + { + $odd_row = true; + $html_output = ''; + /** + * Get already set search criteria (if any) + */ + + //Displays column rows for search criteria input + for ($i = 0; $i < 4; $i++) { + //After X-Axis and Y-Axis column rows, display additional criteria + // option + if ($i == 2) { + $html_output .= ''; + $html_output .= __("Additional search criteria"); + $html_output .= ''; + } + $html_output .= ''; + $odd_row = ! $odd_row; + //Select options for column names + $html_output .= ''; + if (isset($_POST['criteriaColumnNames']) + && $_POST['criteriaColumnNames'][$i] != 'pma_null' + ) { + $key = array_search( + $_POST['criteriaColumnNames'][$i], + $this->_columnNames + ); + $properties = $this->getColumnProperties($i, $key); + $type[$i] = $properties['type']; + $collation[$i] = $properties['collation']; + $func[$i] = $properties['func']; + $value[$i] = $properties['value']; + } + //Column type + $html_output .= '' . (isset($type[$i]) ? $type[$i] : '') . ''; + //Column Collation + $html_output .= '' . (isset($collation[$i]) ? $collation[$i] : '') + . ''; + //Select options for column operators + $html_output .= '' . (isset($func[$i]) ? $func[$i] : '') . ''; + //Inputbox for search criteria value + $html_output .= '' . (isset($value[$i]) ? $value[$i] : '') . ''; + $html_output .= ''; + //Displays hidden fields + $html_output .= ''; + $html_output + .= ''; + $html_output .= ''; + }//end for + return $html_output; + } + + /** + * Generates HTML for displaying fields table in search form + * + * @return string the generated HTML + */ + private function _getFieldsTableHtml() + { + $html_output = ''; + $html_output .= '_searchType == 'zoom' ? ' id="tableFieldsId"' : '') . '>'; + $html_output .= $this->_getTableHeader(); + $html_output .= ''; + + if ($this->_searchType == 'zoom') { + $html_output .= $this->_getRowsZoom(); + } else { + $html_output .= $this->_getRowsNormal(); + } + + $html_output .= '
      '; + return $html_output; + } + + /** + * Provides the form tag for table search form + * (normal search or zoom search) + * + * @param string $goto Goto URL + * + * @return string the HTML for form tag + */ + private function _getFormTag($goto) + { + $html_output = ''; + $scriptName = ''; + $formId = ''; + switch ($this->_searchType) { + case 'normal' : + $scriptName = 'tbl_select.php'; + $formId = 'tbl_search_form'; + break; + case 'zoom' : + $scriptName = 'tbl_zoom_select.php'; + $formId = 'zoom_search_form'; + break; + case 'replace' : + $scriptName = 'tbl_find_replace.php'; + $formId = 'find_replace_form'; + break; + } + + $html_output .= '
      '; + + $html_output .= PMA_URL_getHiddenInputs($this->_db, $this->_table); + $html_output .= ''; + $html_output .= ''; + + return $html_output; + } + + /** + * Returns the HTML for secondary levels tabs of the table search page + * + * @return string HTML for secondary levels tabs + */ + public function getSecondaryTabs() + { + $url_params = array(); + $url_params['db'] = $this->_db; + $url_params['table'] = $this->_table; + + $html_output = '
        '; + foreach ($this->_getSubTabs() as $tab) { + $html_output .= PMA_Util::getHtmlTab($tab, $url_params); + } + $html_output .= '
      '; + $html_output .= '
      '; + return $html_output; + } + + /** + * Generates the table search form under table search tab + * + * @param string $goto Goto URL + * @param string $dataLabel Label for points in zoom plot + * + * @return string the generated HTML for table search form + */ + public function getSelectionForm($goto, $dataLabel = null) + { + $html_output = $this->_getFormTag($goto); + + if ($this->_searchType == 'zoom') { + $html_output .= ''; + } else if ($this->_searchType == 'normal') { + $html_output .= ''; + } else if ($this->_searchType == 'replace') { + $html_output .= '
      '; + $html_output .= '
      '; + $html_output .= '' . __('Find and Replace') . ''; + $html_output .= $this->_getSearchAndReplaceHTML(); + $html_output .= '
      '; + $html_output .= '
      '; + } + + /** + * Displays selection form's footer elements + */ + $html_output .= '
      '; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= '
      '; + return $html_output; + } + + /** + * Provides form for displaying point data and also the scatter plot + * (for tbl_zoom_select.php) + * + * @param string $goto Goto URL + * @param array $data Array containing SQL query data + * + * @return string form's html + */ + public function getZoomResultsForm($goto, $data) + { + $html_output = ''; + $titles['Browse'] = PMA_Util::getIcon( + 'b_browse.png', + __('Browse foreign values') + ); + $html_output .= '
      '; + $html_output .= PMA_URL_getHiddenInputs($this->_db, $this->_table); + $html_output .= ''; + $html_output + .= ''; + + $html_output .= '
      '; + $html_output .= '' . __('Browse/Edit the points') . ''; + + //JSON encode the data(query result) + $html_output .= '
      '; + if (isset($_POST['zoom_submit']) && ! empty($data)) { + $html_output .= '
      '; + $html_output .= '
      ' + . __('How to use') . '
      '; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= ''; + $html_output .= '
      '; + } + $html_output .= '
      '; + + //Displays rows in point edit form + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + return $html_output; + } + + /** + * Displays the 'Find and Replace' form + * + * @return string HTML for 'Find and Replace' form + */ + function _getSearchAndReplaceHTML() + { + $htmlOutput = __('Find:') + . ''; + $htmlOutput .= __('Replace with:') + . ''; + + $htmlOutput .= __('Column:') . ''; + return $htmlOutput; + } + + /** + * Returns HTML for prviewing 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 string $charSet character set of the connection + * + * @return string HTML for prviewing strings found and their replacements + */ + function getReplacePreview($columnIndex, $find, $replaceWith, $charSet) + { + $column = $this->_columnNames[$columnIndex]; + $sql_query = "SELECT " + . PMA_Util::backquote($column) . "," + . " REPLACE(" + . PMA_Util::backquote($column) . ", '" . $find . "', '" . $replaceWith + . "')," + . " COUNT(*)" + . " FROM " . PMA_Util::backquote($this->_db) + . "." . PMA_Util::backquote($this->_table) + . " WHERE " . PMA_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 " . PMA_Util::backquote($column) + . " ORDER BY " . PMA_Util::backquote($column) . " ASC"; + + $rs = $GLOBALS['dbi']->query( + $sql_query, null, PMA_DatabaseInterface::QUERY_STORE + ); + + $htmlOutput = '
      '; + $htmlOutput .= PMA_URL_getHiddenInputs($this->_db, $this->_table); + $htmlOutput .= ''; + $htmlOutput .= ''; + $htmlOutput .= ''; + $htmlOutput .= ''; + + $htmlOutput .= '
      '; + $htmlOutput .= '' . __('Find and replace - preview') . ''; + + $htmlOutput .= '' + . '' + . '' + . '' + . '' + . ''; + + $htmlOutput .= ''; + $odd = true; + while ($row = $GLOBALS['dbi']->fetchRow($rs)) { + $val = $row[0]; + $replaced = $row[1]; + $count = $row[2]; + + $htmlOutput .= ''; + $htmlOutput .= ''; + $htmlOutput .= ''; + $htmlOutput .= ''; + $htmlOutput .= ''; + + $odd = ! $odd; + } + $htmlOutput .= ''; + $htmlOutput .= '
      ' . __('Count') . '' . __('Original string') . '' . __('Replaced string') . '
      ' . htmlspecialchars($count) . '' . htmlspecialchars($val) . '' . htmlspecialchars($replaced) . '
      '; + $htmlOutput .= '
      '; + + $htmlOutput .= '
      '; + $htmlOutput .= ''; + $htmlOutput .= '
      '; + + $htmlOutput .= '
      '; + return $htmlOutput; + } + + /** + * 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 string $charSet character set of the connection + * + * @return void + */ + function replace($columnIndex, $find, $replaceWith, $charSet) + { + $column = $this->_columnNames[$columnIndex]; + $sql_query = "UPDATE " . PMA_Util::backquote($this->_db) + . "." . PMA_Util::backquote($this->_table) + . " SET " . PMA_Util::backquote($column) . " =" + . " REPLACE(" + . PMA_Util::backquote($column) . ", '" . $find . "', '" . $replaceWith + . "')" + . " WHERE " . PMA_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 + $GLOBALS['dbi']->query( + $sql_query, null, PMA_DatabaseInterface::QUERY_STORE + ); + $GLOBALS['sql_query'] = $sql_query; + } +} +?> diff --git a/sources/libraries/Theme.class.php b/sources/libraries/Theme.class.php new file mode 100644 index 0000000..a6c8a5f --- /dev/null +++ b/sources/libraries/Theme.class.php @@ -0,0 +1,485 @@ +getPath() . '/info.inc.php')) { + return false; + } + + if ($this->mtime_info === filemtime($this->getPath() . '/info.inc.php')) { + return true; + } + + @include $this->getPath() . '/info.inc.php'; + + // was it set correctly? + if (! isset($theme_name)) { + return false; + } + + $this->mtime_info = filemtime($this->getPath() . '/info.inc.php'); + $this->filesize_info = filesize($this->getPath() . '/info.inc.php'); + + if (isset($theme_full_version)) { + $this->setVersion($theme_full_version); + } elseif (isset($theme_generation, $theme_version)) { + $this->setVersion($theme_generation . '.' . $theme_version); + } + $this->setName($theme_name); + + return true; + } + + /** + * returns theme object loaded from given folder + * or false if theme is invalid + * + * @param string $folder path to theme + * + * @return PMA_Theme|false + * @static + * @access public + */ + static public function load($folder) + { + $theme = new PMA_Theme(); + + $theme->setPath($folder); + + if (! $theme->loadInfo()) { + return false; + } + + $theme->checkImgPath(); + + return $theme; + } + + /** + * checks image path for existance - 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 = $GLOBALS['cfg']['ThemePath'] . '/' + . PMA_Theme_Manager::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 agaisnt $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 + * + * @access public + * @return string image path for this theme + */ + public function getImgPath($file = null) + { + if (is_null($file)) { + return $this->img_path; + } else { + if (is_readable($this->img_path . $file)) { + return $this->img_path . $file; + } else { + return $GLOBALS['cfg']['ThemePath'] . '/' + . PMA_Theme_Manager::FALLBACK_THEME . '/img/' . $file; + } + } + } + + /** + * load css (send to stdout, normally the browser) + * + * @return bool + * @access public + */ + public function loadCss() + { + $success = true; + + 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/" + . PMA_Theme_Manager::FALLBACK_THEME . "/css/$file.css.php"; + + if (is_readable($path)) { + echo "\n/* FILE: $file.css.php */\n"; + include $path; + } else if (is_readable($fallback)) { + echo "\n/* FILE: $file.css.php */\n"; + include $fallback; + } else { + $success = false; + } + } + + include './themes/sprites.css.php'; + + return $success; + } + + /** + * Renders the preview for this theme + * + * @return string + * @access public + */ + public function getPrintPreview() + { + $url_params = array('set_theme' => $this->getId()); + $url = 'index.php'. PMA_URL_getCommon($url_params); + + $retval = ''; + return $retval; + } + + /** + * Remove filter for IE. + * + * @return string CSS code. + */ + function getCssIEClearFilter() + { + return PMA_USR_BROWSER_AGENT == 'IE' + && PMA_USR_BROWSER_VER >= 6 + && PMA_USR_BROWSER_VER <= 8 + ? 'filter: none' + : ''; + } + + /** + * Gets currently configured font size. + * + * @return String with font size. + */ + function getFontSize() + { + $fs = $GLOBALS['PMA_Config']->get('fontsize'); + if (!is_null($fs)) { + return $fs; + } + if (isset($_COOKIE['pma_fontsize'])) { + return $_COOKIE['pma_fontsize']; + } + 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 . ');'; + // IE 6-8 + if (PMA_USR_BROWSER_AGENT == 'IE' + && PMA_USR_BROWSER_VER >= 6 + && PMA_USR_BROWSER_VER <= 8 + ) { + $result[] = 'filter: ' + . 'progid:DXImageTransform.Microsoft.gradient(startColorstr="#' + . $start_color . '", endColorstr="#' . $end_color . '");'; + } + return implode("\n", $result); + } +} +?> diff --git a/sources/libraries/Theme_Manager.class.php b/sources/libraries/Theme_Manager.class.php new file mode 100644 index 0000000..d12689e --- /dev/null +++ b/sources/libraries/Theme_Manager.class.php @@ -0,0 +1,461 @@ +init(); + } + + /** + * 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; + } + + /** + * Returns path to folder containing themes + * + * @access public + * @return string theme path + */ + public function getThemesPath() + { + return $this->_themes_path; + } + + /** + * 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; + } + + /** + * Initialise the class + * + * @access public + * @return boolean|void + */ + public function init() + { + $this->themes = array(); + $this->theme_default = self::FALLBACK_THEME; + $this->active_theme = ''; + + if (! $this->setThemesPath($GLOBALS['cfg']['ThemePath'])) { + return false; + } + + $this->setThemePerServer($GLOBALS['cfg']['ThemePerServer']); + + $this->loadThemes(); + + $this->theme = new PMA_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 + if (! $this->getThemeCookie() + || ! $this->setActiveTheme($this->getThemeCookie()) + ) { + if ($GLOBALS['cfg']['ThemeDefault']) { + // otherwise use default theme + $this->setActiveTheme($this->theme_default); + } else { + // or fallback theme + $this->setActiveTheme(self::FALLBACK_THEME); + } + } + } + + /** + * Checks configuration + * + * @access public + * @return void + */ + public function checkConfig() + { + if ($this->_themes_path != trim($GLOBALS['cfg']['ThemePath']) + || $this->theme_default != $GLOBALS['cfg']['ThemeDefault'] + ) { + $this->init(); + } else { + // at least the theme path needs to be checked every time for new + // themes, as there is no other way at the moment to keep track of + // new or removed themes + $this->loadThemes(); + } + } + + /** + * 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']; + } else { + return $this->cookie_name; + } + } + + /** + * returns name of theme stored in the cookie + * + * @return string theme name from cookie + * @access public + */ + public function getThemeCookie() + { + if (isset($_COOKIE[$this->getThemeCookieName()])) { + return $_COOKIE[$this->getThemeCookieName()]; + } + + 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 ($handleThemes = opendir($this->getThemesPath())) { + // check for themes directory + while (false !== ($PMA_Theme = readdir($handleThemes))) { + // Skip non dirs, . and .. + if ($PMA_Theme == '.' + || $PMA_Theme == '..' + || ! is_dir($this->getThemesPath() . '/' . $PMA_Theme) + ) { + continue; + } + if (array_key_exists($PMA_Theme, $this->themes)) { + continue; + } + $new_theme = PMA_Theme::load( + $this->getThemesPath() . '/' . $PMA_Theme + ); + if ($new_theme) { + $new_theme->setId($PMA_Theme); + $this->themes[$PMA_Theme] = $new_theme; + } + } // end get themes + closedir($handleThemes); + } else { + trigger_error( + 'phpMyAdmin-ERROR: cannot open themes folder: ' + . $this->getThemesPath(), + E_USER_WARNING + ); + return false; + } // end check for themes directory + + 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) + { + if (! array_key_exists($theme, $this->themes)) { + return false; + } + + return true; + } + + /** + * 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; + } + + /** + * enables backward compatibility + * + * @return void + * @access public + */ + public function makeBc() + { + $GLOBALS['theme'] = $this->theme->getId(); + $GLOBALS['pmaThemePath'] = $this->theme->getPath(); + $GLOBALS['pmaThemeImage'] = $this->theme->getImgPath(); + + /** + * load layout file if exists + */ + if (file_exists($this->theme->getLayoutFile())) { + include $this->theme->getLayoutFile(); + } + } + + /** + * 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 PMA_Theme object for fall back theme + * + * @return PMA_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; + } +} +?> diff --git a/sources/libraries/Tracker.class.php b/sources/libraries/Tracker.class.php new file mode 100644 index 0000000..d4dab00 --- /dev/null +++ b/sources/libraries/Tracker.class.php @@ -0,0 +1,1087 @@ +fetchArray(PMA_queryAsControlUser($sql_query)); + + if (isset($row['tracking_active']) && $row['tracking_active'] == 1) { + return true; + } else { + return false; + } + } + + /** + * Returns the comment line for the log. + * + * @return string Comment, contains date and username + */ + static public function getLogComment() + { + $date = date('Y-m-d H:i:s'); + + return "# log " . $date . " " . $GLOBALS['cfg']['Server']['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 + */ + static public function createVersion($dbname, $tablename, $version, + $tracking_set = '', $is_view = false + ) { + global $sql_backquotes, $export_type; + + if ($tracking_set == '') { + $tracking_set = self::$default_tracking_set; + } + + // get Export SQL instance + include_once "libraries/plugin_interface.lib.php"; + $export_sql_plugin = PMA_getPlugin( + "export", + "sql", + 'libraries/plugins/export/', + array( + 'export_type' => $export_type, + 'single_table' => false, + ) + ); + + $sql_backquotes = true; + + $date = 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; $i < count($columns); $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 (self::$add_drop_table == true && $is_view == false) { + $create_sql .= self::getLogComment() + . 'DROP TABLE IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n"; + + } + + if (self::$add_drop_view == true && $is_view == true) { + $create_sql .= self::getLogComment() + . 'DROP VIEW IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n"; + } + + $create_sql .= self::getLogComment() . + $export_sql_plugin->getTableDef($dbname, $tablename, "\n", ""); + + // Save version + + $sql_query = "/*NOTRACK*/\n" . + "INSERT INTO" . self::$pma_table . " (" . + "db_name, " . + "table_name, " . + "version, " . + "date_created, " . + "date_updated, " . + "schema_snapshot, " . + "schema_sql, " . + "data_sql, " . + "tracking " . + ") " . + "values ( + '" . PMA_Util::sqlAddSlashes($dbname) . "', + '" . PMA_Util::sqlAddSlashes($tablename) . "', + '" . PMA_Util::sqlAddSlashes($version) . "', + '" . PMA_Util::sqlAddSlashes($date) . "', + '" . PMA_Util::sqlAddSlashes($date) . "', + '" . PMA_Util::sqlAddSlashes($snapshot) . "', + '" . PMA_Util::sqlAddSlashes($create_sql) . "', + '" . PMA_Util::sqlAddSlashes("\n") . "', + '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set)) + . "' )"; + + $result = PMA_queryAsControlUser($sql_query); + + if ($result) { + // Deactivate previous version + self::deactivateTracking($dbname, $tablename, ($version - 1)); + } + + return $result; + } + + + /** + * Removes all tracking data for a table + * + * @param string $dbname name of database + * @param string $tablename name of table + * + * @static + * + * @return int result of version insertion + */ + static public function deleteTracking($dbname, $tablename) + { + $sql_query = "/*NOTRACK*/\n" + . "DELETE FROM " . self::$pma_table + . " WHERE `db_name` = '" + . PMA_Util::sqlAddSlashes($dbname) . "'" + . " AND `table_name` = '" + . PMA_Util::sqlAddSlashes($tablename) . "'"; + $result = PMA_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 + */ + static public function createDatabaseVersion($dbname, $version, $query, + $tracking_set = 'CREATE DATABASE,ALTER DATABASE,DROP DATABASE' + ) { + $date = date('Y-m-d H:i:s'); + + if ($tracking_set == '') { + $tracking_set = self::$default_tracking_set; + } + + $create_sql = ""; + + if (self::$add_drop_database == true) { + $create_sql .= self::getLogComment() + . 'DROP DATABASE IF EXISTS ' . PMA_Util::backquote($dbname) . ";\n"; + } + + $create_sql .= self::getLogComment() . $query; + + // Save version + $sql_query = "/*NOTRACK*/\n" . + "INSERT INTO" . self::$pma_table . " (" . + "db_name, " . + "table_name, " . + "version, " . + "date_created, " . + "date_updated, " . + "schema_snapshot, " . + "schema_sql, " . + "data_sql, " . + "tracking " . + ") " . + "values ( + '" . PMA_Util::sqlAddSlashes($dbname) . "', + '" . PMA_Util::sqlAddSlashes('') . "', + '" . PMA_Util::sqlAddSlashes($version) . "', + '" . PMA_Util::sqlAddSlashes($date) . "', + '" . PMA_Util::sqlAddSlashes($date) . "', + '" . PMA_Util::sqlAddSlashes('') . "', + '" . PMA_Util::sqlAddSlashes($create_sql) . "', + '" . PMA_Util::sqlAddSlashes("\n") . "', + '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set)) + . "' )"; + + $result = PMA_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 + ) { + + $sql_query = " UPDATE " . self::$pma_table . + " SET `tracking_active` = '" . $new_state . "' " . + " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " . + " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " . + " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' "; + + $result = PMA_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 + */ + static public function changeTrackingData($dbname, $tablename, + $version, $type, $new_data + ) { + if ($type == 'DDL') { + $save_to = 'schema_sql'; + } elseif ($type == 'DML') { + $save_to = 'data_sql'; + } else { + return false; + } + $date = 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'] + . PMA_Util::sqlAddSlashes($data['statement']) . "\n"; + } + } else { + $new_data_processed = $new_data; + } + + $sql_query = " UPDATE " . self::$pma_table . + " SET `" . $save_to . "` = '" . $new_data_processed . "' " . + " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " . + " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " . + " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' "; + + $result = PMA_queryAsControlUser($sql_query); + + return $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 + */ + static public 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 + */ + static public 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) + */ + static public function getVersion($dbname, $tablename, $statement = null) + { + $sql_query = " SELECT MAX(version) FROM " . self::$pma_table . + " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " . + " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' "; + + if ($statement != "") { + if (PMA_DRIZZLE) { + $sql_query .= ' AND tracking & ' + . self::_transformTrackingSet($statement) . ' <> 0'; + } else { + $sql_query .= " AND FIND_IN_SET('" + . $statement . "',tracking) > 0" ; + } + } + $row = $GLOBALS['dbi']->fetchArray(PMA_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. + */ + static public function getTrackedData($dbname, $tablename, $version) + { + if (! isset(self::$pma_table)) { + self::init(); + } + $sql_query = " SELECT * FROM " . self::$pma_table . + " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' "; + if (! empty($tablename)) { + $sql_query .= " AND `table_name` = '" + . PMA_Util::sqlAddSlashes($tablename) ."' "; + } + $sql_query .= " AND `version` = '" . PMA_Util::sqlAddSlashes($version) + . "' " . " ORDER BY `version` DESC LIMIT 1"; + + $mixed = $GLOBALS['dbi']->fetchAssoc(PMA_queryAsControlUser($sql_query)); + + // Parse log + $log_schema_entries = explode('# log ', $mixed['schema_sql']); + $log_data_entries = explode('# log ', $mixed['data_sql']); + + $ddl_date_from = $date = date('Y-m-d H:i:s'); + + $ddlog = array(); + $i = 0; + + // 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 = substr($log_entry, 0, 19); + $username = substr($log_entry, 20, strpos($log_entry, "\n") - 20); + if ($i == 0) { + $ddl_date_from = $date; + } + $statement = rtrim(strstr($log_entry, "\n")); + + $ddlog[] = array( 'date' => $date, + 'username'=> $username, + 'statement' => $statement ); + $i++; + } + } + + $date_from = $ddl_date_from; + $ddl_date_to = $date; + + $dml_date_from = $date_from; + + $dmlog = array(); + $i = 0; + + // 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 = substr($log_entry, 0, 19); + $username = substr($log_entry, 20, strpos($log_entry, "\n") - 20); + if ($i == 0) { + $dml_date_from = $date; + } + $statement = rtrim(strstr($log_entry, "\n")); + + $dmlog[] = array( 'date' => $date, + 'username' => $username, + 'statement' => $statement ); + $i++; + } + } + + $dml_date_to = $date; + + // Define begin and end of date range for both logs + 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'] = self::_transformTrackingSet($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. + * + */ + static public 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); + + $query = str_replace("\n", " ", $query); + $query = str_replace("\r", " ", $query); + + $query = trim($query); + $query = trim($query, ' -'); + + $tokens = explode(" ", $query); + foreach ($tokens as $key => $value) { + $tokens[$key] = strtoupper($value); + } + + // Parse USE statement, need it for SQL dump imports + if (substr($query, 0, 4) == 'USE ') { + $prefix = explode('USE ', $query); + $GLOBALS['db'] = self::getTableName($prefix[1]); + } + + /* + * DDL statements + */ + + $result['type'] = 'DDL'; + + // Parse CREATE VIEW statement + if (in_array('CREATE', $tokens) == true + && in_array('VIEW', $tokens) == true + && in_array('AS', $tokens) == true + ) { + $result['identifier'] = 'CREATE VIEW'; + + $index = array_search('VIEW', $tokens); + + $result['tablename'] = strtolower( + self::getTableName($tokens[$index + 1]) + ); + } + + // Parse ALTER VIEW statement + if (in_array('ALTER', $tokens) == true + && in_array('VIEW', $tokens) == true + && in_array('AS', $tokens) == true + && ! isset($result['identifier']) + ) { + $result['identifier'] = 'ALTER VIEW'; + + $index = array_search('VIEW', $tokens); + + $result['tablename'] = strtolower( + self::getTableName($tokens[$index + 1]) + ); + } + + // Parse DROP VIEW statement + if (! isset($result['identifier']) + && substr($query, 0, 10) == 'DROP VIEW ' + ) { + $result['identifier'] = 'DROP VIEW'; + + $prefix = explode('DROP VIEW ', $query); + $str = strstr($prefix[1], 'IF EXISTS'); + + if ($str == false ) { + $str = $prefix[1]; + } + $result['tablename'] = self::getTableName($str); + } + + // Parse CREATE DATABASE statement + if (! isset($result['identifier']) + && substr($query, 0, 15) == 'CREATE DATABASE' + ) { + $result['identifier'] = 'CREATE DATABASE'; + $str = str_replace('CREATE DATABASE', '', $query); + $str = str_replace('IF NOT EXISTS', '', $str); + + $prefix = explode('DEFAULT ', $str); + + $result['tablename'] = ''; + $GLOBALS['db'] = self::getTableName($prefix[0]); + } + + // Parse ALTER DATABASE statement + if (! isset($result['identifier']) + && substr($query, 0, 14) == 'ALTER DATABASE' + ) { + $result['identifier'] = 'ALTER DATABASE'; + $result['tablename'] = ''; + } + + // Parse DROP DATABASE statement + if (! isset($result['identifier']) + && substr($query, 0, 13) == 'DROP DATABASE' + ) { + $result['identifier'] = 'DROP DATABASE'; + $str = str_replace('DROP DATABASE', '', $query); + $str = str_replace('IF EXISTS', '', $str); + $GLOBALS['db'] = self::getTableName($str); + $result['tablename'] = ''; + } + + // Parse CREATE TABLE statement + if (! isset($result['identifier']) + && substr($query, 0, 12) == 'CREATE TABLE' + ) { + $result['identifier'] = 'CREATE TABLE'; + $query = str_replace('IF NOT EXISTS', '', $query); + $prefix = explode('CREATE TABLE ', $query); + $suffix = explode('(', $prefix[1]); + $result['tablename'] = self::getTableName($suffix[0]); + } + + // Parse ALTER TABLE statement + if (! isset($result['identifier']) + && substr($query, 0, 12) == 'ALTER TABLE ' + ) { + $result['identifier'] = 'ALTER TABLE'; + + $prefix = explode('ALTER TABLE ', $query); + $suffix = explode(' ', $prefix[1]); + $result['tablename'] = self::getTableName($suffix[0]); + } + + // Parse DROP TABLE statement + if (! isset($result['identifier']) + && substr($query, 0, 11) == 'DROP TABLE ' + ) { + $result['identifier'] = 'DROP TABLE'; + + $prefix = explode('DROP TABLE ', $query); + $str = strstr($prefix[1], 'IF EXISTS'); + + if ($str == false ) { + $str = $prefix[1]; + } + $result['tablename'] = self::getTableName($str); + } + + // Parse CREATE INDEX statement + if (! isset($result['identifier']) + && (substr($query, 0, 12) == 'CREATE INDEX' + || substr($query, 0, 19) == 'CREATE UNIQUE INDEX' + || substr($query, 0, 20) == 'CREATE SPATIAL INDEX') + ) { + $result['identifier'] = 'CREATE INDEX'; + $prefix = explode('ON ', $query); + $suffix = explode('(', $prefix[1]); + $result['tablename'] = self::getTableName($suffix[0]); + } + + // Parse DROP INDEX statement + if (! isset($result['identifier']) + && substr($query, 0, 10) == 'DROP INDEX' + ) { + $result['identifier'] = 'DROP INDEX'; + $prefix = explode('ON ', $query); + $result['tablename'] = self::getTableName($prefix[1]); + } + + // Parse RENAME TABLE statement + if (! isset($result['identifier']) + && substr($query, 0, 13) == 'RENAME TABLE ' + ) { + $result['identifier'] = 'RENAME TABLE'; + $prefix = explode('RENAME TABLE ', $query); + $names = explode(' TO ', $prefix[1]); + $result['tablename'] = self::getTableName($names[0]); + $result["tablename_after_rename"] = self::getTableName($names[1]); + } + + /* + * DML statements + */ + + if (! isset($result['identifier'])) { + $result["type"] = 'DML'; + } + // Parse UPDATE statement + if (! isset($result['identifier']) + && substr($query, 0, 6) == 'UPDATE' + ) { + $result['identifier'] = 'UPDATE'; + $prefix = explode('UPDATE ', $query); + $suffix = explode(' ', $prefix[1]); + $result['tablename'] = self::getTableName($suffix[0]); + } + + // Parse INSERT INTO statement + if (! isset($result['identifier']) + && substr($query, 0, 11) == 'INSERT INTO' + ) { + $result['identifier'] = 'INSERT'; + $prefix = explode('INSERT INTO', $query); + $suffix = explode('(', $prefix[1]); + $result['tablename'] = self::getTableName($suffix[0]); + } + + // Parse DELETE statement + if (! isset($result['identifier']) + && substr($query, 0, 6) == 'DELETE' + ) { + $result['identifier'] = 'DELETE'; + $prefix = explode('FROM ', $query); + $suffix = explode(' ', $prefix[1]); + $result['tablename'] = self::getTableName($suffix[0]); + } + + // Parse TRUNCATE statement + if (! isset($result['identifier']) + && substr($query, 0, 8) == 'TRUNCATE' + ) { + $result['identifier'] = 'TRUNCATE'; + $prefix = explode('TRUNCATE', $query); + $result['tablename'] = self::getTableName($prefix[1]); + } + + return $result; + } + + + /** + * Analyzes a given SQL statement and saves tracking data. + * + * @param string $query a SQL query + * + * @static + * + * @return void + */ + static public function handleQuery($query) + { + // If query is marked as untouchable, leave + if (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; + } + // Remove null bytes (preg_replace() is vulnerable in some + // PHP versions) + $dbname = str_replace("\0", "", $dbname); + + // If we found a valid statement + if (isset($result['identifier'])) { + $version = self::getVersion( + $dbname, $result['tablename'], $result['identifier'] + ); + + // If version not exists and auto-creation is enabled + if (self::$version_auto_create == true + && self::isTracked($dbname, $result['tablename']) == false + && $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 (self::isTracked($dbname, $result['tablename']) && $version != -1) { + if ($result['type'] == 'DDL') { + $save_to = 'schema_sql'; + } elseif ($result['type'] == 'DML') { + $save_to = 'data_sql'; + } else { + $save_to = ''; + } + $date = date('Y-m-d H:i:s'); + + // Cut off `dbname`. from query + $query = preg_replace('/`' . $dbname . '`\s?\./', '', $query); + + // Add log information + $query = self::getLogComment() . $query ; + + // Mark it as untouchable + $sql_query = " /*NOTRACK*/\n" + . " UPDATE " . self::$pma_table + . " SET " . PMA_Util::backquote($save_to) + . " = CONCAT( " . PMA_Util::backquote($save_to) . ",'\n" + . PMA_Util::sqlAddSlashes($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` = \'' + . PMA_Util::sqlAddSlashes($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` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " . + " AND `table_name` = '" + . PMA_Util::sqlAddSlashes($result['tablename']) . "' " . + " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' "; + + $result = PMA_queryAsControlUser($sql_query); + } + } + } + + /** + * Transforms tracking set for Drizzle, which has no SET type + * + * Converts int<>string for Drizzle, does nothing for MySQL + * + * @param int|string $tracking_set Set to convert + * + * @return int|string + */ + static private function _transformTrackingSet($tracking_set) + { + if (!PMA_DRIZZLE) { + return $tracking_set; + } + + // init conversion array (key 3 doesn't exist in calculated array) + if (isset(self::$_tracking_set_flags[3])) { + // initialize flags + $set = self::$_tracking_set_flags; + $array = array(); + for ($i = 0; $i < count($set); $i++) { + $flag = 1 << $i; + $array[$flag] = $set[$i]; + $array[$set[$i]] = $flag; + } + self::$_tracking_set_flags = $array; + } + + if (is_numeric($tracking_set)) { + // int > string conversion + $aflags = array(); + // count/2 - conversion table has both int > string + // and string > int values + for ($i = 0; $i < count(self::$_tracking_set_flags)/2; $i++) { + $flag = 1 << $i; + if ($tracking_set & $flag) { + $aflags[] = self::$_tracking_set_flags[$flag]; + } + } + $flags = implode(',', $aflags); + } else { + // string > int conversion + $flags = 0; + foreach (explode(',', $tracking_set) as $strflag) { + if ($strflag == '') { + continue; + } + $flags |= self::$_tracking_set_flags[$strflag]; + } + } + + return $flags; + } +} +?> diff --git a/sources/libraries/Types.class.php b/sources/libraries/Types.class.php new file mode 100644 index 0000000..455c27d --- /dev/null +++ b/sources/libraries/Types.class.php @@ -0,0 +1,986 @@ +getUnaryOperators()); + } + + /** + * Returns list of operators checking for NULL. + * + * @return array + */ + public function getNullOperators() + { + return array( + 'IS NULL', + 'IS NOT NULL', + ); + } + + /** + * ENUM search operators + * + * @return array + */ + public function getEnumOperators() + { + return array( + '=', + '!=', + ); + } + + /** + * TEXT search operators + * + * @return array + */ + public function getTextOperators() + { + return array( + 'LIKE', + 'LIKE %...%', + 'NOT LIKE', + '=', + '!=', + 'REGEXP', + 'REGEXP ^...$', + 'NOT REGEXP', + "= ''", + "!= ''", + 'IN (...)', + 'NOT IN (...)', + 'BETWEEN', + 'NOT BETWEEN', + ); + } + + /** + * Number search operators + * + * @return array + */ + 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 array + */ + 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) + { + 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) + { + return ''; + } + + /** + * Returns array of functions available for a class. + * + * @param string $class The class to get function list. + * + * @return array + * + */ + public function getFunctionsClass($class) + { + return array(); + } + + /** + * Returns array of functions available for a type. + * + * @param string $type The data type to get function list. + * + * @return array + * + */ + public function getFunctions($type) + { + $class = $this->getTypeClass($type); + return $this->getFunctionsClass($class); + } + + /** + * Returns array of all functions available. + * + * @return array + * + */ + 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 array + * + */ + public function getAttributes() + { + return array(); + } + + /** + * Returns array of all column types available. + * + * @return array + * + */ + public function getColumns() + { + // most used types + return array( + 'INT', + 'VARCHAR', + 'TEXT', + 'DATE', + ); + } +} + +/** + * Class holding type definitions for MySQL. + * + * @package PhpMyAdmin + */ +class PMA_Types_MySQL extends PMA_Types +{ + /** + * Returns the data type description. + * + * @param string $type The data type to get a description. + * + * @return string + * + */ + public function getTypeDescription($type) + { + $type = 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'); + } + 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 = 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'; + } + + return ''; + } + + /** + * Returns array of functions available for a class. + * + * @param string $class The class to get function list. + * + * @return array + * + */ + public function getFunctionsClass($class) + { + switch ($class) { + case 'CHAR': + return array( + 'AES_ENCRYPT', + 'BIN', + 'CHAR', + 'COMPRESS', + 'CURRENT_USER', + 'DATABASE', + 'DAYNAME', + 'DES_DECRYPT', + 'DES_ENCRYPT', + 'ENCRYPT', + 'HEX', + 'INET_NTOA', + 'LOAD_FILE', + 'LOWER', + 'LTRIM', + 'MD5', + 'MONTHNAME', + 'OLD_PASSWORD', + 'PASSWORD', + 'QUOTE', + 'REVERSE', + 'RTRIM', + 'SHA1', + 'SOUNDEX', + 'SPACE', + 'TRIM', + 'UNCOMPRESS', + 'UNHEX', + 'UPPER', + 'USER', + 'UUID', + 'VERSION', + ); + + 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', + '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', + ); + // remove functions that are unavailable on current server + if (PMA_MYSQL_INT_VERSION < 50500) { + $ret = array_diff($ret, array('TO_SECONDS')); + } + if (PMA_MYSQL_INT_VERSION < 50120) { + $ret = array_diff($ret, array('UUID_SHORT')); + } + return $ret; + + case 'SPATIAL': + return array( + 'GeomFromText', + 'GeomFromWKB', + + 'GeomCollFromText', + 'LineFromText', + 'MLineFromText', + 'PointFromText', + 'MPointFromText', + 'PolyFromText', + 'MPolyFromText', + + 'GeomCollFromWKB', + 'LineFromWKB', + 'MLineFromWKB', + 'PointFromWKB', + 'MPointFromWKB', + 'PolyFromWKB', + 'MPolyFromWKB', + ); + } + return array(); + } + + /** + * Returns array of all attributes available. + * + * @return array + * + */ + 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 array + * + */ + public function getColumns() + { + $ret = parent::getColumns(); + // 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', + ); + + return $ret; + } +} + +/** + * Class holding type definitions for Drizzle. + * + * @package PhpMyAdmin + */ +class PMA_Types_Drizzle extends PMA_Types +{ + /** + * Returns the data type description. + * + * @param string $type The data type to get a description. + * + * @return string + * + */ + public function getTypeDescription($type) + { + $type = strtoupper($type); + switch ($type) { + case 'INTEGER': + return __('A 4-byte integer, range is -2,147,483,648 to 2,147,483,647'); + case 'BIGINT': + return __('An 8-byte integer, range is -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807'); + 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 'DOUBLE': + return __("A system's default double-precision floating-point number"); + case 'BOOLEAN': + return __('True or false'); + case 'SERIAL': + return __('An alias for BIGINT NOT NULL AUTO_INCREMENT UNIQUE'); + case 'UUID': + return __('Stores a Universally Unique Identifier (UUID)'); + case 'DATE': + return sprintf(__('A date, supported range is %1$s to %2$s'), '0001-01-01', '9999-12-31'); + case 'DATETIME': + return sprintf(__('A date and time combination, supported range is %1$s to %2$s'), '0001-01-01 00:00:0', '9999-12-31 23:59:59'); + case 'TIMESTAMP': + return __("A timestamp, range is '0001-01-01 00:00:00' UTC to '9999-12-31 23:59:59' UTC; TIMESTAMP(6) can store microseconds"); + case 'TIME': + return sprintf(__('A time, range is %1$s to %2$s'), '00:00:00', '23:59:59'); + case 'VARCHAR': + return sprintf(__('A variable-length (%s) string, the effective maximum length is subject to the maximum row size'), '0-16,383'); + 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 'VARBINARY': + return __('A variable-length (0-65,535) string, uses binary collation for all comparisons'); + 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 'ENUM': + return __("An enumeration, chosen from the list of defined values"); + } + 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 = strtoupper($type); + switch ($type) { + case 'INTEGER': + case 'BIGINT': + case 'DECIMAL': + case 'DOUBLE': + case 'BOOLEAN': + case 'SERIAL': + return 'NUMBER'; + + case 'DATE': + case 'DATETIME': + case 'TIMESTAMP': + case 'TIME': + return 'DATE'; + + case 'VARCHAR': + case 'TEXT': + case 'VARBINARY': + case 'BLOB': + case 'ENUM': + return 'CHAR'; + + case 'UUID': + return 'UUID'; + } + return ''; + } + + /** + * Returns array of functions available for a class. + * + * @param string $class The class to get function list. + * + * @return array + * + */ + public function getFunctionsClass($class) + { + switch ($class) { + case 'CHAR': + $ret = array( + 'BIN', + 'CHAR', + 'COMPRESS', + 'CURRENT_USER', + 'DATABASE', + 'DAYNAME', + 'HEX', + 'LOAD_FILE', + 'LOWER', + 'LTRIM', + 'MD5', + 'MONTHNAME', + 'QUOTE', + 'REVERSE', + 'RTRIM', + 'SCHEMA', + 'SPACE', + 'TRIM', + 'UNCOMPRESS', + 'UNHEX', + 'UPPER', + 'USER', + 'UUID', + 'VERSION', + ); + + // check for some functions known to be in modules + $functions = array( + 'MYSQL_PASSWORD', + 'ROT13', + ); + + // add new functions + $sql = "SELECT upper(plugin_name) f + FROM data_dictionary.plugins + WHERE plugin_name IN ('" . implode("','", $functions) . "') + AND plugin_type = 'Function' + AND is_active"; + $drizzle_functions = $GLOBALS['dbi']->fetchResult($sql, 'f', 'f'); + if (count($drizzle_functions) > 0) { + $ret = array_merge($ret, $drizzle_functions); + sort($ret); + } + + return $ret; + + case 'UUID': + return array( + 'UUID', + ); + + case 'DATE': + return array( + 'CURRENT_DATE', + 'CURRENT_TIME', + 'DATE', + 'FROM_DAYS', + 'FROM_UNIXTIME', + 'LAST_DAY', + 'NOW', + 'SYSDATE', + //'TIME', // https://bugs.launchpad.net/drizzle/+bug/804571 + 'TIMESTAMP', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'YEAR', + ); + + case 'NUMBER': + return array( + 'ABS', + 'ACOS', + 'ASCII', + 'ASIN', + 'ATAN', + 'BIT_COUNT', + 'CEILING', + 'CHAR_LENGTH', + 'CONNECTION_ID', + 'COS', + 'COT', + 'CRC32', + 'DAYOFMONTH', + 'DAYOFWEEK', + 'DAYOFYEAR', + 'DEGREES', + 'EXP', + 'FLOOR', + 'HOUR', + 'LENGTH', + 'LN', + 'LOG', + 'LOG2', + 'LOG10', + 'MICROSECOND', + 'MINUTE', + 'MONTH', + 'OCT', + 'ORD', + 'PI', + 'QUARTER', + 'RADIANS', + 'RAND', + 'ROUND', + 'SECOND', + 'SIGN', + 'SIN', + 'SQRT', + 'TAN', + 'TO_DAYS', + 'TIME_TO_SEC', + 'UNCOMPRESSED_LENGTH', + 'UNIX_TIMESTAMP', + //'WEEK', // same as TIME + 'WEEKDAY', + 'WEEKOFYEAR', + 'YEARWEEK', + ); + } + return array(); + } + + /** + * Returns array of all attributes available. + * + * @return array + * + */ + public function getAttributes() + { + return array( + '', + 'on update CURRENT_TIMESTAMP', + ); + } + + /** + * Returns array of all column types available. + * + * @return array + * + */ + public function getColumns() + { + $types_num = array( + 'INTEGER', + 'BIGINT', + '-', + 'DECIMAL', + 'DOUBLE', + '-', + 'BOOLEAN', + 'SERIAL', + 'UUID', + ); + $types_date = array( + 'DATE', + 'DATETIME', + 'TIMESTAMP', + 'TIME', + ); + $types_string = array( + 'VARCHAR', + 'TEXT', + '-', + 'VARBINARY', + 'BLOB', + '-', + 'ENUM', + ); + if (PMA_MYSQL_INT_VERSION >= 20120130) { + $types_string[] = '-'; + $types_string[] = 'IPV6'; + } + + $ret = parent::getColumns(); + // numeric + $ret[_pgettext('numeric types', 'Numeric')] = $types_num; + + // Date/Time + $ret[_pgettext('date and time types', 'Date and time')] = $types_date; + + // Text + $ret[_pgettext('string types', 'String')] = $types_string; + + return $ret; + } +} diff --git a/sources/libraries/Util.class.php b/sources/libraries/Util.class.php new file mode 100644 index 0000000..3fbdf06 --- /dev/null +++ b/sources/libraries/Util.class.php @@ -0,0 +1,4350 @@ +'; + 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, + * which may be an actual file or an icon from a sprite + * + * @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 = '', $attributes = array()) + { + static $sprites; // cached list of available sprites (if any) + if (defined('TESTSUITE')) { + // prevent caching in testsuite + unset($sprites); + } + + $url = ''; + $is_sprite = false; + $alternate = htmlspecialchars($alternate); + + // If it's the first time this function is called + if (! isset($sprites)) { + // Try to load the list of sprites + $sprite_file = $_SESSION['PMA_Theme']->getPath() . '/sprites.lib.php'; + if (is_readable($sprite_file)) { + include_once $sprite_file; + $sprites = PMA_sprites(); + } else { + // No sprites are available for this theme + $sprites = array(); + } + } + + // Check if we have the requested image as a sprite + // and set $url accordingly + $class = str_replace(array('.gif','.png'), '', $image); + if (array_key_exists($class, $sprites)) { + $is_sprite = true; + $url = (defined('PMA_TEST_THEME') ? '../' : '') . 'themes/dot.gif'; + } else { + $url = $GLOBALS['pmaThemeImage'] . $image; + } + + // set class attribute + if ($is_sprite) { + if (isset($attributes['class'])) { + $attributes['class'] = "icon ic_$class " . $attributes['class']; + } else { + $attributes['class'] = "icon ic_$class"; + } + } + + // 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, $url, $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 so a value containing them can + * be used in a sql comparison. + * + * @param string $a_string the string to slash + * @param bool $is_like whether the string will be used in a 'LIKE' clause + * (it then requires two more escaped sequences) or not + * @param bool $crlf whether to treat cr/lfs as escape-worthy entities + * (converts \n to \\n, \r to \\r) + * @param bool $php_code whether this function is used as part of the + * "Create PHP code" dialog + * + * @return string the slashed string + * + * @access public + */ + public static function sqlAddSlashes( + $a_string = '', $is_like = false, $crlf = false, $php_code = false + ) { + if ($is_like) { + $a_string = str_replace('\\', '\\\\\\\\', $a_string); + } else { + $a_string = str_replace('\\', '\\\\', $a_string); + } + + if ($crlf) { + $a_string = strtr( + $a_string, + array("\n" => '\n', "\r" => '\r', "\t" => '\t') + ); + } + + if ($php_code) { + $a_string = str_replace('\'', '\\\'', $a_string); + } else { + $a_string = str_replace('\'', '\'\'', $a_string); + } + + return $a_string; + } // end of the 'sqlAddSlashes()' function + + /** + * 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 (substr($quoted_string, 0, 1) === $quote + && substr($quoted_string, -1, 1) === $quote + ) { + $unquoted_string = 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 $sql_query 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($sql_query, $truncate = false) + { + global $cfg; + if ($truncate + && strlen($sql_query) > $cfg['MaxCharactersInDisplayedSQL'] + ) { + $sql_query = $GLOBALS['PMA_String']->substr( + $sql_query, + 0, + $cfg['MaxCharactersInDisplayedSQL'] + ) . '[...]'; + } + return '
      ' . "\n"
      +            . htmlspecialchars($sql_query) . "\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 + * + * @return string the html link + * + * @access public + */ + public static function showDocLink($link, $target = 'documentation') + { + return '' + . self::getImage('b_help.png', __('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 = '') + { + global $cfg; + + // Fixup for newly used names: + $link = str_replace('_', '-', strtolower($link)); + + if (empty($link)) { + $link = 'index'; + } + $mysql = '5.5'; + $lang = 'en'; + if (defined('PMA_MYSQL_INT_VERSION')) { + if (PMA_MYSQL_INT_VERSION >= 50600) { + $mysql = '5.6'; + } else if (PMA_MYSQL_INT_VERSION >= 50500) { + $mysql = '5.5'; + } else if (PMA_MYSQL_INT_VERSION >= 50100) { + $mysql = '5.1'; + } else { + $mysql = '5.0'; + } + } + $url = 'http://dev.mysql.com/doc/refman/' + . $mysql . '/' . $lang . '/' . $link . '.html'; + if (! empty($anchor)) { + $url .= '#' . $anchor; + } + + return PMA_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.png', __('Documentation')) . ''; + } else { + 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 */ + if (defined('TESTSUITE')) { + /* Provide consistent URL for testsuite */ + return PMA_linkURL('http://docs.phpmyadmin.net/en/latest/' . $url); + } else if (file_exists('doc/html/index.html')) { + if (defined('PMA_SETUP')) { + return '../doc/html/' . $url; + } else { + return './doc/html/' . $url; + } + } else { + /* TODO: Should link to correct branch for released versions */ + return PMA_linkURL('http://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 + * + * @return string the html link + * + * @access public + */ + public static function showDocu($page, $anchor = '') + { + return self::showDocLink(self::getDocuLink($page, $anchor)); + } // 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 = PMA_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.png') + . '' . $message . '' + . ''; + } + + /** + * Displays a MySQL error message in the main panel when $exit is true. + * Returns the error message otherwise. + * + * @param string $error_message the error message + * @param string $the_query the sql query that failed + * @param bool $is_modify_link whether to show a "modify" link or not + * @param string $back_url the "back" link url (full path is not required) + * @param bool $exit EXIT the page? + * + * @return mixed + * + * @global string $table the curent table + * @global string $db the current db + * + * @access public + */ + public static function mysqlDie( + $error_message = '', $the_query = '', + $is_modify_link = true, $back_url = '', $exit = true + ) { + global $table, $db; + + $error_msg = ''; + + if (! $error_message) { + $error_message = $GLOBALS['dbi']->getError(); + } + if (! $the_query && ! empty($GLOBALS['sql_query'])) { + $the_query = $GLOBALS['sql_query']; + } + + // --- Added to solve bug #641765 + if (! function_exists('PMA_SQP_isError') || PMA_SQP_isError()) { + $formatted_sql = htmlspecialchars($the_query); + } elseif (empty($the_query) || (trim($the_query) == '')) { + $formatted_sql = ''; + } else { + $formatted_sql = self::formatSql($the_query, true); + } + // --- + $error_msg .= "\n" . '' . "\n"; + $error_msg .= '

      ' . __('Error') + . '

      ' . "\n"; + // if the config password is wrong, or the MySQL server does not + // respond, do not show the query that would reveal the + // username/password + if (! empty($the_query) && ! strstr($the_query, 'connect')) { + // --- Added to solve bug #641765 + if (function_exists('PMA_SQP_isError') && PMA_SQP_isError()) { + $error_msg .= PMA_SQP_getErrorString() . "\n"; + $error_msg .= '
      ' . "\n"; + } + // --- + // modified to show the help on sql errors + $error_msg .= '

      ' . __('SQL query:') . '' . "\n"; + if (strstr(strtolower($formatted_sql), 'select')) { + // please show me help to the error on select + $error_msg .= self::showMySQLDocu('SELECT'); + } + if ($is_modify_link) { + $_url_params = array( + 'sql_query' => $the_query, + 'show_query' => 1, + ); + if (strlen($table)) { + $_url_params['db'] = $db; + $_url_params['table'] = $table; + $doedit_goto = ''; + } elseif (strlen($db)) { + $_url_params['db'] = $db; + $doedit_goto = ''; + } else { + $doedit_goto = ''; + } + + $error_msg .= $doedit_goto + . self::getIcon('b_edit.png', __('Edit')) + . ''; + } // end if + $error_msg .= '

      ' . "\n" + .'

      ' . "\n" + . $formatted_sql . "\n" + . '

      ' . "\n"; + } // end if + + if (! empty($error_message)) { + $error_message = preg_replace( + "@((\015\012)|(\015)|(\012)){3,}@", + "\n\n", + $error_message + ); + } + // modified to show the help on error-returns + // (now error-messages-server) + $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 wordwrapping, + // we do a couple of replacements + + // Replace all non-single blanks with their HTML-counterpart + $error_message = str_replace(' ', '  ', $error_message); + // Replace TAB-characters with their HTML-counterpart + $error_message = str_replace( + "\t", '    ', $error_message + ); + // Replace linebreaks + $error_message = nl2br($error_message); + + $error_msg .= '' . "\n" + . $error_message . "\n" + . '
      ' . "\n"; + $error_msg .= '
      '; + + $_SESSION['Import_message']['message'] = $error_msg; + + if ($exit) { + /** + * If in an Ajax request + * - avoid displaying a Back link + * - use PMA_Response() to transmit the message and exit + */ + if (isset($GLOBALS['is_ajax_request']) + && $GLOBALS['is_ajax_request'] == true + ) { + $response = PMA_Response::getInstance(); + $response->isSuccess(false); + $response->addJSON('message', $error_msg); + exit; + } + if (! empty($back_url)) { + if (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"; + } + echo $error_msg; + exit; + } else { + return $error_msg; + } + } // end of the 'mysqlDie()' function + + /** + * 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, false, null, $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) { + // check for correct row count + 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 left frame; + // but we can check row count if this is a view or the + // information_schema database + // since PMA_Table::countRecords() returns a limited row count + // in this case. + + // set this because PMA_Table::countRecords() can use it + $tbl_is_view = $table['TABLE_TYPE'] == 'VIEW'; + + if ($tbl_is_view || $GLOBALS['dbi']->isSystemSchema($db)) { + $table['Rows'] = PMA_Table::countRecords( + $db, + $table['Name'], + false, + true + ); + } + } + + // in $group we save the reference to the place in $table_groups + // where to store the table info + if ($GLOBALS['cfg']['NavigationTreeEnableGrouping'] + && $sep && 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; + unset($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) { + global $PMA_SQPdata_forbidden_word; + if (! in_array(strtoupper($a_name), $PMA_SQPdata_forbidden_word)) { + return $a_name; + } + } + + // '0' is also empty for php :-( + if (strlen($a_name) && $a_name !== '*') { + return '`' . str_replace('`', '``', $a_name) . '`'; + } else { + return $a_name; + } + } // end of the 'backquote()' function + + /** + * Adds quotes on both sides of a database, table or field name. + * in compatibility mode + * + * 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 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) { + global $PMA_SQPdata_forbidden_word; + if (! in_array(strtoupper($a_name), $PMA_SQPdata_forbidden_word)) { + return $a_name; + } + } + + // @todo add more compatibility cases (ORACLE for example) + switch ($compatibility) { + case 'MSSQL': + $quote = '"'; + break; + default: + (isset($GLOBALS['sql_backquotes'])) ? $quote = "`" : $quote = ''; + break; + } + + // '0' is also empty for php :-( + if (strlen($a_name) && $a_name !== '*') { + return $quote . $a_name . $quote; + } else { + return $a_name; + } + } // end of the 'backquoteCompat()' function + + /** + * Defines the value depending on the user OS. + * + * @return string the value to use + * + * @access public + */ + public static function whichCrlf() + { + // The 'PMA_USR_OS' constant is defined in "libraries/Config.class.php" + // Win case + if (PMA_USR_OS == 'Win') { + $the_crlf = "\r\n"; + } else { + // Others + $the_crlf = "\n"; + } + + return $the_crlf; + } // end of the 'whichCrlf()' function + + /** + * Prepare the message and the query + * usually the message is the result of the query executed + * + * @param string $message the message to display + * @param string $sql_query the query to display + * @param string $type the type (level) of the message + * @param boolean $is_view is this a message after a VIEW operation? + * + * @return string + * + * @access public + */ + public static function getMessage( + $message, $sql_query = null, $type = 'notice', $is_view = false + ) { + 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 = ''; + } + } + + if (isset($GLOBALS['using_bookmark_message'])) { + $retval .= $GLOBALS['using_bookmark_message']->getDisplay(); + unset($GLOBALS['using_bookmark_message']); + } + + // In an Ajax request, $GLOBALS['cell_align_left'] may not be defined. Hence, + // check for it's presence before using it + $retval .= '
      ' . "\n"; + + if ($message instanceof PMA_Message) { + if (isset($GLOBALS['special_message'])) { + $message->addMessage($GLOBALS['special_message']); + unset($GLOBALS['special_message']); + } + $retval .= $message->getDisplay(); + } else { + $retval .= '
      '; + $retval .= PMA_sanitize($message); + if (isset($GLOBALS['special_message'])) { + $retval .= PMA_sanitize($GLOBALS['special_message']); + unset($GLOBALS['special_message']); + } + $retval .= '
      '; + } + + if ($cfg['ShowSQL'] == true && ! empty($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($sql_query)); + $query_base = preg_replace( + '/((\015\012)|(\015)|(\012))/', $new_line, $query_base + ); + } else { + $query_base = $sql_query; + } + + $query_too_big = false; + + if (strlen($query_base) > $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; + $shortened_query_base = nl2br( + htmlspecialchars( + substr($sql_query, 0, $cfg['MaxCharactersInDisplayedSQL']) + . '[...]' + ) + ); + } elseif (! empty($GLOBALS['parsed_sql']) + && $query_base == $GLOBALS['parsed_sql']['raw'] + ) { + // (here, use "! empty" because when deleting a bookmark, + // $GLOBALS['parsed_sql'] is set but empty + $parsed_sql = $GLOBALS['parsed_sql']; + } else { + // Parse SQL if needed + $parsed_sql = PMA_SQP_parse($query_base); + } + + // Analyze it + if (isset($parsed_sql) && ! PMA_SQP_isError()) { + $analyzed_display_query = PMA_SQP_analyze($parsed_sql); + + // Same as below (append LIMIT), append the remembered ORDER BY + if ($GLOBALS['cfg']['RememberSorting'] + && isset($analyzed_display_query[0]['queryflags']['select_from']) + && isset($GLOBALS['sql_order_to_append']) + ) { + $query_base = $analyzed_display_query[0]['section_before_limit'] + . "\n" . $GLOBALS['sql_order_to_append'] + . $analyzed_display_query[0]['limit_clause'] . ' ' + . $analyzed_display_query[0]['section_after_limit']; + + // Need to reparse query + $parsed_sql = PMA_SQP_parse($query_base); + // update the $analyzed_display_query + $analyzed_display_query[0]['section_before_limit'] + .= $GLOBALS['sql_order_to_append']; + $analyzed_display_query[0]['order_by_clause'] + = $GLOBALS['sorted_col']; + } + + // Here we append the LIMIT added for navigation, to + // enable its display. Adding it higher in the code + // to $sql_query would create a problem when + // using the Refresh or Edit links. + + // Only append it on SELECTs. + + /** + * @todo what would be the best to do when someone hits Refresh: + * use the current LIMITs ? + */ + + if (isset($analyzed_display_query[0]['queryflags']['select_from']) + && ! empty($GLOBALS['sql_limit_to_append']) + ) { + $query_base = $analyzed_display_query[0]['section_before_limit'] + . "\n" . $GLOBALS['sql_limit_to_append'] + . $analyzed_display_query[0]['section_after_limit']; + // Need to reparse query + $parsed_sql = PMA_SQP_parse($query_base); + } + } + + if (! empty($GLOBALS['show_as_php'])) { + $query_base = '$sql = "' . $query_base; + } elseif (! empty($GLOBALS['validatequery'])) { + try { + $query_base = PMA_validateSQL($query_base); + } catch (Exception $e) { + $retval .= PMA_Message::error( + __('Failed to connect to SQL validator!') + )->getDisplay(); + } + } elseif (isset($query_base)) { + $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'])) { + $url_params['db'] = $GLOBALS['db']; + if (strlen($GLOBALS['table'])) { + $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; + // Detect if we are validating as well + // To preserve the validate uRL data + if (! empty($GLOBALS['validatequery'])) { + $explain_params['validatequery'] = 1; + } + if ($is_select) { + $explain_params['sql_query'] = 'EXPLAIN ' . $sql_query; + $_message = __('Explain SQL'); + } elseif ( + preg_match( + '@^EXPLAIN[[:space:]]+SELECT[[:space:]]+@i', $sql_query + ) + ) { + $explain_params['sql_query'] = substr($sql_query, 8); + $_message = __('Skip Explain SQL'); + } + if (isset($explain_params['sql_query'])) { + $explain_link = 'import.php' + . PMA_URL_getCommon($explain_params); + $explain_link = ' [' + . self::linkOrButton($explain_link, $_message) . ']'; + } + } //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'])) { + if ($cfg['EditInWindow'] == true) { + $onclick = 'PMA_querywindow.focus(\'' + . PMA_jsFormat($sql_query, false) . '\'); return false;'; + } else { + $onclick = ''; + } + + $edit_link .= PMA_URL_getCommon($url_params) . '#querybox'; + $edit_link = ' [' + . self::linkOrButton( + $edit_link, __('Edit'), + array('onclick' => $onclick, 'class' => 'disableAjax') + ) + . ']'; + } 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) { + $php_params = $url_params; + + if (! empty($GLOBALS['show_as_php'])) { + $_message = __('Without PHP Code'); + } else { + $php_params['show_as_php'] = 1; + $_message = __('Create PHP Code'); + } + + $php_link = 'import.php' . PMA_URL_getCommon($php_params); + $php_link = ' [' . self::linkOrButton($php_link, $_message) . ']'; + + if (isset($GLOBALS['show_as_php'])) { + + $runquery_link = 'import.php' + . PMA_URL_getCommon($url_params); + + $php_link .= ' [' + . self::linkOrButton($runquery_link, __('Submit Query')) + . ']'; + } + } 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' . PMA_URL_getCommon($url_params); + $refresh_link = ' [' + . self::linkOrButton($refresh_link, __('Refresh')) . ']'; + } else { + $refresh_link = ''; + } //refresh + + if (! empty($cfg['SQLValidator']['use']) + && ! empty($cfg['SQLQuery']['Validate']) + ) { + $validate_params = $url_params; + if (! empty($GLOBALS['validatequery'])) { + $validate_message = __('Skip Validate SQL'); + } else { + $validate_params['validatequery'] = 1; + $validate_message = __('Validate SQL'); + } + + $validate_link = 'import.php' + . PMA_URL_getCommon($validate_params); + $validate_link = ' [' + . self::linkOrButton($validate_link, $validate_message) . ']'; + } else { + $validate_link = ''; + } //validator + + if (! empty($GLOBALS['validatequery'])) { + $retval .= '
      '; + } else { + $retval .= '
      '; + } + if ($query_too_big) { + $retval .= $shortened_query_base; + } else { + $retval .= $query_base; + } + + //Clean up the end of the PHP + if (! empty($GLOBALS['show_as_php'])) { + $retval .= '";'; + } + $retval .= '
      '; + + $retval .= '
      '; + $retval .= '
      '; + $retval .= PMA_URL_getHiddenInputs( + $GLOBALS['db'], $GLOBALS['table'] + ); + $retval .= ''; + + // avoid displaying a Profiling checkbox that could + // be checked, which would reexecute an INSERT, for example + if (! empty($refresh_link) && self::profilingSupported()) { + $retval .= ''; + $retval .= self::getCheckbox( + 'profiling', __('Profiling'), isset($_SESSION['profiling']), true + ); + } + $retval .= '
      '; + + /** + * TODO: Should we have $cfg['SQLQuery']['InlineEdit']? + */ + if (! empty($cfg['SQLQuery']['Edit']) && ! $query_too_big) { + $inline_edit_link = ' [' + . self::linkOrButton( + '#', + _pgettext('Inline edit query', 'Inline'), + array('class' => 'inline_edit_sql') + ) + . ']'; + } else { + $inline_edit_link = ''; + } + $retval .= $inline_edit_link . $edit_link . $explain_link . $php_link + . $refresh_link . $validate_link; + $retval .= '
      '; + } + + $retval .= '
      '; + if ($GLOBALS['is_ajax_request'] === false) { + $retval .= '
      '; + } + + return $retval; + } // end of the 'getMessage()' function + + /** + * Verifies if current MySQL server supports profiling + * + * @access public + * + * @return boolean whether profiling is supported + */ + public static function profilingSupported() + { + if (!self::cacheExists('profiling_supported', true)) { + // 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 (defined('PMA_MYSQL_INT_VERSION') + && (PMA_MYSQL_INT_VERSION >= 50037) + && $GLOBALS['dbi']->fetchValue("SHOW VARIABLES LIKE 'profiling'") + ) { + self::cacheSet('profiling_supported', true, true); + } else { + self::cacheSet('profiling_supported', false, true); + } + } + + return self::cacheGet('profiling_supported', true); + } + + /** + * Formats $value to byte view + * + * @param double $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 = self::pow(10, $comma); + $li = self::pow(10, $limes); + $unit = $byteUnits[0]; + + for ($d = 6, $ex = 15; $d >= 1; $d--, $ex-=3) { + if (isset($byteUnits[$d]) && ($value >= $li * self::pow(10, $ex))) { + // use 1024.0 to avoid integer overflow on 64-bit machines + $value = round($value / (self::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 + + /** + * Changes thousands and decimal separators to locale specific values. + * + * @param string $value the value + * + * @return string + */ + public static function localizeNumber($value) + { + return str_replace( + array(',', '.'), + array( + /* l10n: Thousands separator */ + __(','), + /* l10n: Decimal separator */ + __('.'), + ), + $value + ); + } + + /** + * Formats $value to the given length and appends SI prefixes + * with a $length of 0 no truncation occurs, number is only formated + * 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); + if (($originalValue != 0) && (floatval($value) == 0)) { + $value = ' <' . (1 / self::pow(10, $digits_right)); + } + return self::localizeNumber($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' + ); + + // check for negative value to retain sign + if ($value < 0) { + $sign = '-'; + $value = abs($value); + } else { + $sign = ''; + } + + $dh = self::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 / self::pow(1000, $d, 'pow'))+1); + if ($digits_left > $cur_digits) { + $d -= floor(($digits_left - $cur_digits)/3); + } + + if ($d < 0 && $only_down) { + $d = 0; + } + + $value = round($value / (self::pow(1000, $d, 'pow') / $dh)) /$dh; + $unit = $units[$d]; + + // If we dont want any zeros after the comma just add the thousand separator + if ($noTrailingZero) { + $value = self::localizeNumber( + preg_replace('/(?<=\d)(?=(\d{3})+(?!\d))/', ',', $value) + ); + } else { + //number_format is not multibyte safe, str_replace is safe + $value = self::localizeNumber(number_format($value, $digits_right)); + } + + if ($originalValue != 0 && floatval($value) == 0) { + return ' <' . (1 / self::pow(10, $digits_right)) . ' ' . $unit; + } + + return $sign . $value . ' ' . $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 = substr($formatted_size, 0, -2) * self::pow(1024, 3); + } elseif (preg_match('/^[0-9]+MB$/', $formatted_size)) { + $return_value = substr($formatted_size, 0, -2) * self::pow(1024, 2); + } elseif (preg_match('/^[0-9]+K$/', $formatted_size)) { + $return_value = substr($formatted_size, 0, -1) * self::pow(1024, 1); + } + return $return_value; + }// end of the 'extractValueFromFormattedSize' function + + /** + * Writes localised date + * + * @param string $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 http://www.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 + ); + + return strftime($date, $timestamp); + } // 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($tab, $url_params = array()) + { + // default values + $defaults = array( + 'text' => '', + 'class' => '', + 'active' => null, + 'link' => '', + 'sep' => '?', + 'attr' => '', + 'args' => '', + 'warning' => '', + 'fragment' => '', + 'id' => '', + ); + + $tab = array_merge($defaults, $tab); + + // determine additionnal style-class + if (empty($tab['class'])) { + if (! empty($tab['active']) + || PMA_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'; + } + } + + // If there are any tab specific URL parameters, merge those with + // the general URL parameters + if (! empty($tab['url_params']) && is_array($tab['url_params'])) { + $url_params = array_merge($url_params, $tab['url_params']); + } + + // build the link + if (! empty($tab['link'])) { + $tab['link'] = htmlentities($tab['link']); + $tab['link'] = $tab['link'] . PMA_URL_getCommon($url_params); + if (! empty($tab['args'])) { + foreach ($tab['args'] as $param => $value) { + $tab['link'] .= PMA_URL_getArgSeparator('html') + . urlencode($param) . '=' . urlencode($value); + } + } + } + + 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 + $id_string = ( empty($tab['id']) ? '' : ' id="'.$tab['id'].'" ' ); + $out = ''; + + if (! empty($tab['link'])) { + $out .= '' + . $tab['text'] . ''; + } else { + $out .= '' . $tab['text'] . ''; + } + + $out .= ''; + return $out; + } // end of the 'getHtmlTab()' function + + /** + * returns html-code for a tab navigation + * + * @param array $tabs one element per tab + * @param string $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($tabs, $url_params, $menu_id, + $resizable = false + ) { + $class = ''; + if ($resizable) { + $class = ' class="resizable-menu"'; + } + + $tab_navigation = '' . "\n"; + + return $tab_navigation; + } + + /** + * Displays a link, or a button if the link's URL is too large, to + * accommodate some browsers' limitations + * + * @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 boolean $new_form we set this to false when we are already in + * a form, to avoid generating nested forms + * @param boolean $strip_img whether to strip the image + * @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(), + $new_form = true, $strip_img = false, $target = '' + ) { + $url_length = strlen($url); + // with this we should be able to catch case of image upload + // into a (MEDIUM) BLOB; not worth generating even a form for these + if ($url_length > $GLOBALS['cfg']['LinkLengthLimit'] * 100) { + return ''; + } + + if (! is_array($tag_params)) { + $tmp = $tag_params; + $tag_params = array(); + if (! empty($tmp)) { + $tag_params['onclick'] = 'return confirmLink(this, \'' + . PMA_escapeJsString($tmp) . '\')'; + } + unset($tmp); + } + if (! empty($target)) { + $tag_params['target'] = htmlentities($target); + } + + $tag_params_strings = array(); + foreach ($tag_params as $par_name => $par_value) { + // htmlspecialchars() only on non javascript + $par_value = substr($par_name, 0, 2) == 'on' + ? $par_value + : htmlspecialchars($par_value); + $tag_params_strings[] = $par_name . '="' . $par_value . '"'; + } + + $displayed_message = ''; + // Add text if not already added + if (stristr($message, ''; + } + + // 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) { + list($eachvar, $eachval) = explode('=', $query_pair); + if (strlen($eachval) > $suhosin_get_MaxValueLength) { + $in_suhosin_limits = false; + break; + } + } + } + } + + if (($url_length <= $GLOBALS['cfg']['LinkLengthLimit']) + && $in_suhosin_limits + ) { + // no whitespace within an else Safari will make it part of the link + $ret = "\n" . '' + . $message . $displayed_message . '' . "\n"; + } else { + // no spaces (linebreaks) at all + // or after the hidden fields + // IE will display them all + + // add class=link to submit button + if (empty($tag_params['class'])) { + $tag_params['class'] = 'link'; + } + + if (! isset($query_parts)) { + $query_parts = self::splitURLQuery($url); + } + $url_parts = parse_url($url); + + if ($new_form) { + $ret = ''; + } + } // end if... else... + + return $ret; + } // 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 = PMA_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); + + return explode($separator, $url_parts['query']); + } + + /** + * 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 + ); + } + + /** + * Takes a string and outputs each character on a line for itself. Used + * mainly for horizontalflipped display mode. + * Takes care of special html-characters. + * Fulfills https://sourceforge.net/p/phpmyadmin/feature-requests/164/ + * + * @param string $string The string + * @param string $Separator The Separator (defaults to "
      \n") + * + * @access public + * @todo add a multibyte safe function $GLOBALS['PMA_String']->split() + * + * @return string The flipped string + */ + public static function flipstring($string, $Separator = "
      \n") + { + $format_string = ''; + $charbuff = false; + + for ($i = 0, $str_len = strlen($string); $i < $str_len; $i++) { + $char = $string{$i}; + $append = false; + + if ($char == '&') { + $format_string .= $charbuff; + $charbuff = $char; + } elseif ($char == ';' && ! empty($charbuff)) { + $format_string .= $charbuff . $char; + $charbuff = false; + $append = true; + } elseif (! empty($charbuff)) { + $charbuff .= $char; + } else { + $format_string .= $char; + $append = true; + } + + // do not add separator after the last character + if ($append && ($i != $str_len - 1)) { + $format_string .= $Separator; + } + } + + return $format_string; + } + + /** + * Function added to avoid path disclosures. + * Called by each script that needs parameters, it displays + * an error message and, by default, stops the execution. + * + * Not sure we could use a strMissingParameter message here, + * would have to check if the error message file is always available + * + * @param array $params The names of the parameters needed by the calling script + * @param bool $request Whether to include this list in checking for + * special params + * + * @return void + * + * @global boolean $checked_special flag whether any special variable was required + * + * @access public + */ + public static function checkParameters($params, $request = true) + { + global $checked_special; + + if (! isset($checked_special)) { + $checked_special = false; + } + + $reported_script_name = basename($GLOBALS['PMA_PHP_SELF']); + $found_error = false; + $error_message = ''; + + foreach ($params as $param) { + if ($request && ($param != 'db') && ($param != 'table')) { + $checked_special = true; + } + + if (! isset($GLOBALS[$param])) { + $error_message .= $reported_script_name + . ': ' . __('Missing parameter:') . ' ' + . $param + . self::showDocu('faq', 'faqmissingparameters') + . '
      '; + $found_error = true; + } + } + if ($found_error) { + PMA_fatalError($error_message, null, false); + } + } // 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 + * + * @access public + * + * @return array the calculated condition and whether condition is unique + */ + public static function getUniqueCondition( + $handle, $fields_cnt, $fields_meta, $row, $force_unique = false + ) { + $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) { + + $condition = ''; + $con_key = ''; + $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)) { + $meta->orgname = $meta->name; + + if (isset($GLOBALS['analyzed_sql'][0]['select_expr']) + && is_array($GLOBALS['analyzed_sql'][0]['select_expr']) + ) { + foreach ( + $GLOBALS['analyzed_sql'][0]['select_expr'] as $select_expr + ) { + // need (string) === (string) + // '' !== 0 but '' == 0 + if ((string)$select_expr['alias'] === (string)$meta->name) { + $meta->orgname = $select_expr['column']; + break; + } // end if + } // end foreach + } + } + + // 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) + && ! PMA_Table::isView($GLOBALS['db'], $meta->table) + ) { + $meta->table = $meta->orgtable; + } + + // 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 (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)'; + } else if ($fields_cnt == 1) { + // when this blob is the only field present + // try settling with length comparison + $condition = ' CHAR_LENGTH(' . $con_key . ') '; + $con_val = ' = ' . 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 (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 = '= \'' + . self::sqlAddSlashes($row[$i], false, true) . '\''; + } + } + + 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 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 $image_name name of 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, $image_name, $text, $image, $value = '' + ) { + if ($value == '') { + $value = $text; + } + + if ($GLOBALS['cfg']['ActionLinksMode'] == 'text') { + return ' ' . "\n"; + } + + /* Opera has trouble with */ + /* IE (before version 9) has trouble with ' . "\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 + + /** + * 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 array $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, $_url_params, $script, $frame, $max_count, $name = 'pos', + $classes = array() + ) { + + $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) { + if (self::showIcons('TableNavigationLinksMode')) { + $caption1 = '<<'; + $caption2 = ' < '; + $title1 = ' title="' . _pgettext('First page', 'Begin') . '"'; + $title2 = ' title="' + . _pgettext('Previous page', 'Previous') . '"'; + } else { + $caption1 = _pgettext('First page', 'Begin') . ' <<'; + $caption2 = _pgettext('Previous page', 'Previous') . ' <'; + $title1 = ''; + $title2 = ''; + } // end if... else... + + $_url_params[$name] = 0; + $list_navigator_html .= '' . $caption1 + . ''; + + $_url_params[$name] = $pos - $max_count; + $list_navigator_html .= '' . $caption2 + . ''; + } + + $list_navigator_html .= '
      '; + + $list_navigator_html .= PMA_URL_getHiddenInputs($_url_params); + $list_navigator_html .= self::pageselector( + $name, + $max_count, + floor(($pos + 1) / $max_count) + 1, + ceil($count / $max_count) + ); + $list_navigator_html .= '
      '; + + if ($pos + $max_count < $count) { + if ( self::showIcons('TableNavigationLinksMode')) { + $caption3 = ' > '; + $caption4 = '>>'; + $title3 = ' title="' . _pgettext('Next page', 'Next') . '"'; + $title4 = ' title="' . _pgettext('Last page', 'End') . '"'; + } else { + $caption3 = '> ' . _pgettext('Next page', 'Next'); + $caption4 = '>> ' . _pgettext('Last page', 'End'); + $title3 = ''; + $title4 = ''; + } // end if... else... + + $_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 (substr($dir, -1) != '/') { + $dir .= '/'; + } + + return str_replace('%u', $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)) { + if (! strlen($GLOBALS['db'])) { + 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') && (PMA_MYSQL_INT_VERSION < $minimum_version)) { + $ext_but_html .= self::showHint( + sprintf( + __('The %s functionality is affected by a known bug, see %s'), + $functionality, + PMA_linkURL('http://bugs.mysql.com/') . $bugref + ) + ); + } + return $ext_but_html; + } + + /** + * Returns a HTML checkbox + * + * @param string $html_field_name the checkbox HTML field + * @param string $label label for checkbox + * @param boolean $checked is it initially checked? + * @param boolean $onclick should it submit the form on click? + * + * @return string HTML for the checkbox + */ + public static function getCheckbox($html_field_name, $label, $checked, $onclick) + { + return ''; + } + + /** + * 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 + * + * @return string set of html radio fiels + */ + public static function getRadioFields( + $html_field_name, $choices, $checked_choice = '', + $line_break = true, $escape_label = true, $class = '' + ) { + $radio_html = ''; + + foreach ($choices as $choice_value => $choice_label) { + + if (! empty($class)) { + $radio_html .= '
      '; + } + + $html_field_id = $html_field_name . '_' . $choice_value; + $radio_html .= '' + . ($escape_label + ? htmlspecialchars($choice_label) + : $choice_label) + . ''; + + if ($line_break) { + $radio_html .= '
      '; + } + + if (! empty($class)) { + $radio_html .= '
      '; + } + $radio_html .= "\n"; + } + + 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 + * + * @return string html content + * + * @todo support titles + */ + public static function getDropdown( + $select_name, $choices, $active_choice, $id, $class = '' + ) { + $result = ''; + + foreach ($choices as $one_choice_value => $one_choice_label) { + $result .= '
      '; +} + +/** + * Closes form tabs + * + * @return void + */ +function PMA_displayTabsBottom() +{ + echo "
      \n"; +} + +/** + * Displays bottom part of the form + * + * @return void + */ +function PMA_displayFormBottom() +{ + echo "\n"; +} + +/** + * 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 + */ +function PMA_addJsValidate($field_id, $validators, &$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[] = PMA_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 void + */ +function PMA_displayJavascript($js_array) +{ + if (empty($js_array)) { + return; + } + echo '' . "\n"; +} + +/** + * Displays error list + * + * @param string $name name of item with errors + * @param array $error_list list of errors to show + * + * @return void + */ +function PMA_displayErrors($name, $error_list) +{ + echo '
      '; + echo '
      ' . htmlspecialchars($name) . '
      '; + foreach ($error_list as $error) { + echo '
      ' . htmlspecialchars($error) . '
      '; + } + echo '
      '; +} +?> diff --git a/sources/libraries/config/Validator.class.php b/sources/libraries/config/Validator.class.php new file mode 100644 index 0000000..e6a9588 --- /dev/null +++ b/sources/libraries/config/Validator.class.php @@ -0,0 +1,598 @@ +getDbEntry('_validators', array()); + if (!defined('PMA_SETUP')) { + // not in setup script: load additional validators for user + // preferences we need original config values not overwritten + // by user preferences, creating a new PMA_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; $i < count($uv); $i++) { + if (substr($uv[$i], 0, 6) == 'value:') { + $uv[$i] = PMA_arrayRead( + 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 documen + * 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, &$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 = 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 = "PMA_Validator::" . $vname; + $args = array_merge(array($vid, &$arguments), $vdef); + $r = call_user_func_array($vname, $args); + + // merge results + if (is_array($r)) { + 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; + $new_result[$k2] = $v; + } + return empty($new_result) ? true : $new_result; + } + + /** + * Empty error handler, used to temporarily restore PHP internal error handler + * + * @return bool + */ + public static function nullErrorHandler() + { + return false; + } + + /** + * Ensures that $php_errormsg variable will be registered in case of an error + * and enables output buffering (when $start = true). + * Called with $start = false disables output buffering end restores + * html_errors and track_errors. + * + * @param boolean $start Whether to start buffering + * + * @return void + */ + public static function testPHPErrorMsg($start = true) + { + static $old_html_errors, $old_track_errors, $old_error_reporting; + static $old_display_errors; + if ($start) { + $old_html_errors = ini_get('html_errors'); + $old_track_errors = ini_get('track_errors'); + $old_display_errors = ini_get('display_errors'); + $old_error_reporting = error_reporting(E_ALL); + ini_set('html_errors', false); + ini_set('track_errors', true); + ini_set('display_errors', true); + set_error_handler(array("PMA_Validator", "nullErrorHandler")); + ob_start(); + } else { + ob_end_clean(); + restore_error_handler(); + error_reporting($old_error_reporting); + ini_set('html_errors', $old_html_errors); + ini_set('track_errors', $old_track_errors); + ini_set('display_errors', $old_display_errors); + } + } + + /** + * Test database connection + * + * @param string $extension 'drizzle', 'mysql' or 'mysqli' + * @param string $connect_type 'tcp' or 'socket' + * @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( + $extension, + $connect_type, + $host, + $port, + $socket, + $user, + $pass = null, + $error_key = 'Server' + ) { + // static::testPHPErrorMsg(); + $socket = empty($socket) || $connect_type == 'tcp' ? null : $socket; + $port = empty($port) || $connect_type == 'socket' ? null : ':' . $port; + $error = null; + if ($extension == 'drizzle') { + while (1) { + $drizzle = @drizzle_create(); + if (! $drizzle) { + $error = __('Could not initialize Drizzle connection library'); + break; + } + $conn = $socket + ? @drizzle_con_add_uds($socket, $user, $pass, null, 0) + : @drizzle_con_add_tcp( + $drizzle, $host, $port, $user, $pass, null, 0 + ); + if (! $conn) { + $error = __('Could not connect to Drizzle server'); + drizzle_free($drizzle); + break; + } + // connection object is set up but we have to send some query + // to actually connect + $res = @drizzle_query($conn, 'SELECT 1'); + if (! $res) { + $error = __('Could not connect to Drizzle server'); + } else { + drizzle_result_free($res); + } + drizzle_con_free($conn); + drizzle_free($drizzle); + break; + } + } else if ($extension == 'mysql') { + $conn = @mysql_connect($host . $socket . $port, $user, $pass); + if (! $conn) { + $error = __('Could not connect to MySQL server'); + } else { + mysql_close($conn); + } + } else { + $conn = @mysqli_connect($host, $user, $pass, null, $port, $socket); + if (! $conn) { + $error = __('Could not connect to MySQL server'); + } else { + mysqli_close($conn); + } + } + // static::testPHPErrorMsg(false); + if (isset($php_errormsg)) { + $error .= " - $php_errormsg"; + } + return is_null($error) ? true : array($error_key => $error); + } + + /** + * Validate server config + * + * @param string $path path to config, not used + * @param array $values config values + * + * @return array + */ + public static function validateServer($path, $values) + { + $result = array( + 'Server' => '', + 'Servers/1/user' => '', + 'Servers/1/SignonSession' => '', + 'Servers/1/SignonURL' => '' + ); + $error = false; + if ($values['Servers/1/auth_type'] == 'config' + && empty($values['Servers/1/user']) + ) { + $result['Servers/1/user'] + = __('Empty username while using config 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 signon 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 signon authentication method'); + $error = true; + } + + if (! $error && $values['Servers/1/auth_type'] == 'config') { + $password = $values['Servers/1/nopassword'] ? null + : $values['Servers/1/password']; + $test = static::testDBConnection( + $values['Servers/1/extension'], + $values['Servers/1/connect_type'], + $values['Servers/1/host'], + $values['Servers/1/port'], + $values['Servers/1/socket'], + $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 + * @param array $values config values + * + * @return array + */ + public static function validatePMAStorage($path, $values) + { + $result = array( + 'Server_pmadb' => '', + 'Servers/1/controluser' => '', + 'Servers/1/controlpass' => '' + ); + $error = false; + + if ($values['Servers/1/pmadb'] == '') { + return $result; + } + + $result = array(); + if ($values['Servers/1/controluser'] == '') { + $result['Servers/1/controluser'] + = __('Empty phpMyAdmin control user while using pmadb'); + $error = true; + } + if ($values['Servers/1/controlpass'] == '') { + $result['Servers/1/controlpass'] + = __('Empty phpMyAdmin control user password while using pmadb'); + $error = true; + } + if (! $error) { + $test = static::testDBConnection( + $values['Servers/1/extension'], $values['Servers/1/connect_type'], + $values['Servers/1/host'], $values['Servers/1/port'], + $values['Servers/1/socket'], $values['Servers/1/controluser'], + $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, $values) + { + $result = array($path => ''); + + if ($values[$path] == '') { + return $result; + } + + static::testPHPErrorMsg(); + + $matches = array(); + // in libraries/List_Database.class.php _checkHideDatabase(), + // a '/' is used as the delimiter for hide_db + preg_match('/' . $values[$path] . '/', '', $matches); + + static::testPHPErrorMsg(false); + + if (isset($php_errormsg)) { + $error = preg_replace('/^preg_match\(\): /', '', $php_errormsg); + 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, $values) + { + $result = array($path => array()); + + if (empty($values[$path])) { + return $result; + } + + if (is_array($values[$path])) { + // value already processed by FormDisplay::save + $lines = array(); + foreach ($values[$path] as $ip => $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 key: + * $GLOBALS["strConfig$error_lang_key"] + * + * @return string empty string if test is successful + */ + public static function validateNumber( + $path, + $values, + $allow_neg, + $allow_zero, + $max_value, + $error_string + ) { + if ($values[$path] === '') { + return ''; + } + + if (intval($values[$path]) != $values[$path] + || (! $allow_neg && $values[$path] < 0) + || (! $allow_zero && $values[$path] == 0) + || $values[$path] > $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, $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, $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, $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 regullar expression to match + * + * @return array + */ + public static function validateByRegex($path, $values, $regex) + { + $result = preg_match($regex, $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, $values, $max_value) + { + $result = $values[$path] <= $max_value; + return array($path => ($result ? '' + : sprintf(__('Value must be equal or lower than %s'), $max_value))); + } +} +?> diff --git a/sources/libraries/config/config_functions.lib.php b/sources/libraries/config/config_functions.lib.php new file mode 100644 index 0000000..ad1053e --- /dev/null +++ b/sources/libraries/config/config_functions.lib.php @@ -0,0 +1,55 @@ + diff --git a/sources/libraries/config/messages.inc.php b/sources/libraries/config/messages.inc.php new file mode 100644 index 0000000..fd281f3 --- /dev/null +++ b/sources/libraries/config/messages.inc.php @@ -0,0 +1,548 @@ +CREATE TABLE
      options:'); +$strConfigExport_sql_dates_name = __('Creation/Update/Check dates'); +$strConfigExport_sql_delayed_name = __('Use delayed inserts'); +$strConfigExport_sql_disable_fk_name = __('Disable foreign key checks'); +$strConfigExport_sql_views_as_tables_name = __('Export views as tables'); +$strConfigExport_sql_drop_database_name = sprintf(__('Add %s'), 'DROP DATABASE'); +$strConfigExport_sql_drop_table_name = sprintf(__('Add %s'), 'DROP TABLE / VIEW / PROCEDURE / FUNCTION / EVENT'); +$strConfigExport_sql_hex_for_blob_name = __('Use hexadecimal for BLOB'); +$strConfigExport_sql_if_not_exists_name = sprintf(__('Add %s'), 'IF NOT EXISTS'); +$strConfigExport_sql_ignore_name = __('Use ignore inserts'); +$strConfigExport_sql_include_comments_name = __('Comments'); +$strConfigExport_sql_insert_syntax_name = __('Syntax to use when inserting data'); +$strConfigExport_sql_max_query_size_name = __('Maximal length of created query'); +$strConfigExport_sql_mime_name = __('MIME type'); +$strConfigExport_sql_procedure_function_name = sprintf(__('Add %s'), 'CREATE PROCEDURE / FUNCTION / EVENT'); +$strConfigExport_sql_relation_name = __('Relations'); +$strConfigExport_sql_structure_or_data_name = __('Dump table'); +$strConfigExport_sql_type_name = __('Export type'); +$strConfigExport_sql_use_transaction_name = __('Enclose export in a transaction'); +$strConfigExport_sql_utc_time_name = __('Export time in UTC'); +$strConfigExport_texytext_columns_name = __('Put columns names in the first row'); +$strConfigExport_texytext_null_name = __('Replace NULL with'); +$strConfigExport_texytext_structure_or_data_name = __('Dump table'); +$strConfigExport_xls_columns_name = __('Put columns names in the first row'); +$strConfigExport_xls_null_name = __('Replace NULL with'); +$strConfigExport_xlsx_columns_name = __('Put columns names in the first row'); +$strConfigExport_xlsx_null_name = __('Replace NULL with'); +$strConfigForceSSL_desc = __('Force secured connection while using phpMyAdmin'); +$strConfigForceSSL_name = __('Force SSL connection'); +$strConfigForeignKeyDropdownOrder_desc = __('Sort order for items in a foreign-key dropdown box; [kbd]content[/kbd] is the referenced data, [kbd]id[/kbd] is the key value'); +$strConfigForeignKeyDropdownOrder_name = __('Foreign key dropdown order'); +$strConfigForeignKeyMaxLimit_desc = __('A dropdown will be used if fewer items are present'); +$strConfigForeignKeyMaxLimit_name = __('Foreign key limit'); +$strConfigForm_Browse = __('Browse mode'); +$strConfigForm_Browse_desc = __('Customize browse mode'); +$strConfigForm_CodeGen = 'CodeGen'; +$strConfigForm_CodeGen_desc = __('Customize default options'); +$strConfigForm_Csv = __('CSV'); +$strConfigForm_Csv_desc = __('Customize default options'); +$strConfigForm_Developer = __('Developer'); +$strConfigForm_Developer_desc = __('Settings for phpMyAdmin developers'); +$strConfigForm_Edit = __('Edit mode'); +$strConfigForm_Edit_desc = __('Customize edit mode'); +$strConfigForm_Export = __('Export'); +$strConfigForm_Export_defaults = __('Export defaults'); +$strConfigForm_Export_defaults_desc = __('Customize default export options'); +$strConfigForm_Features = __('Features'); +$strConfigForm_General = __('General'); +$strConfigForm_General_desc = __('Set some commonly used options'); +$strConfigForm_Import = __('Import'); +$strConfigForm_Import_defaults = __('Import defaults'); +$strConfigForm_Import_defaults_desc = __('Customize default common import options'); +$strConfigForm_Import_export = __('Import / export'); +$strConfigForm_Import_export_desc = __('Set import and export directories and compression options'); +$strConfigForm_Latex = __('LaTeX'); +$strConfigForm_Latex_desc = __('Customize default options'); +$strConfigForm_Navi_databases = __('Databases'); +$strConfigForm_Navi_databases_desc = __('Databases display options'); +$strConfigForm_Navi_panel = __('Navigation panel'); +$strConfigForm_Navi_panel_desc = __('Customize appearance of the navigation panel'); +$strConfigForm_Navi_servers = __('Servers'); +$strConfigForm_Navi_servers_desc = __('Servers display options'); +$strConfigForm_Navi_tables = __('Tables'); +$strConfigForm_Navi_tables_desc = __('Tables display options'); +$strConfigForm_Main_panel = __('Main panel'); +$strConfigForm_Microsoft_Office = __('Microsoft Office'); +$strConfigForm_Microsoft_Office_desc = __('Customize default options'); +$strConfigForm_Open_Document = 'OpenDocument'; +$strConfigForm_Open_Document_desc = __('Customize default options'); +$strConfigForm_Other_core_settings = __('Other core settings'); +$strConfigForm_Other_core_settings_desc = __('Settings that didn\'t fit anywhere else'); +$strConfigForm_Page_titles = __('Page titles'); +$strConfigForm_Page_titles_desc = __('Specify browser\'s title bar text. Refer to [doc@cfg_TitleTable]documentation[/doc] for magic strings that can be used to get special values.'); +$strConfigForm_Query_window = __('Query window'); +$strConfigForm_Query_window_desc = __('Customize query window options'); +$strConfigForm_Security = __('Security'); +$strConfigForm_Security_desc = __('Please note that phpMyAdmin is just a user interface and its features do not limit MySQL'); +$strConfigForm_Server = __('Basic settings'); +$strConfigForm_Server_auth = __('Authentication'); +$strConfigForm_Server_auth_desc = __('Authentication settings'); +$strConfigForm_Server_config = __('Server configuration'); +$strConfigForm_Server_config_desc = __('Advanced server configuration, do not change these options unless you know what they are for'); +$strConfigForm_Server_desc = __('Enter server connection parameters'); +$strConfigForm_Server_pmadb = __('Configuration storage'); +$strConfigForm_Server_pmadb_desc = __('Configure phpMyAdmin configuration storage to gain access to additional features, see [doc@linked-tables]phpMyAdmin configuration storage[/doc] in documentation'); +$strConfigForm_Server_tracking = __('Changes tracking'); +$strConfigForm_Server_tracking_desc = __('Tracking of changes made in database. Requires the phpMyAdmin configuration storage.'); +$strConfigFormset_Export = __('Customize export options'); +$strConfigFormset_Features = __('Features'); +$strConfigFormset_Import = __('Customize import defaults'); +$strConfigFormset_Navi_panel = __('Customize navigation panel'); +$strConfigFormset_Main_panel = __('Customize main panel'); +$strConfigFormset_Sql_queries = __('SQL queries'); +$strConfigForm_Sql = __('SQL'); +$strConfigForm_Sql_box = __('SQL Query box'); +$strConfigForm_Sql_box_desc = __('Customize links shown in SQL Query boxes'); +$strConfigForm_Sql_desc = __('Customize default options'); +$strConfigForm_Sql_queries = __('SQL queries'); +$strConfigForm_Sql_queries_desc = __('SQL queries settings'); +$strConfigForm_Sql_validator = __('SQL Validator'); +$strConfigForm_Sql_validator_desc = __('If you wish to use the SQL Validator service, you should be aware that [strong]all SQL statements are stored anonymously for statistical purposes[/strong].[br][em][a@http://sqlvalidator.mimer.com/]Mimer SQL Validator[/a], Copyright 2002 Upright Database Technology. All rights reserved.[/em]'); +$strConfigForm_Startup = __('Startup'); +$strConfigForm_Startup_desc = __('Customize startup page'); +$strConfigForm_DbStructure = __('Database structure'); +$strConfigForm_DbStructure_desc = __('Choose which details to show in the database structure (list of tables)'); +$strConfigForm_TableStructure = __('Table structure'); +$strConfigForm_TableStructure_desc = __('Settings for the table structure (list of columns)'); +$strConfigForm_Tabs = __('Tabs'); +$strConfigForm_Tabs_desc = __('Choose how you want tabs to work'); +$strConfigForm_DisplayRelationalSchema = __('Display relational schema'); +$strConfigForm_DisplayRelationalSchema_desc = ''; +$strConfigPDFDefaultPageSize_name = __('Paper size'); +$strConfigPDFDefaultPageSize_desc = ''; +$strConfigForm_Text_fields = __('Text fields'); +$strConfigForm_Text_fields_desc = __('Customize text input fields'); +$strConfigForm_Texy = __('Texy! text'); +$strConfigForm_Texy_desc = __('Customize default options'); +$strConfigForm_Warnings = __('Warnings'); +$strConfigForm_Warnings_desc = __('Disable some of the warnings shown by phpMyAdmin'); +$strConfigGZipDump_desc = __('Enable [a@http://en.wikipedia.org/wiki/Gzip]gzip[/a] compression for import and export operations'); +$strConfigGZipDump_name = __('GZip'); +$strConfigIconvExtraParams_name = __('Extra parameters for iconv'); +$strConfigIgnoreMultiSubmitErrors_desc = __('If enabled, phpMyAdmin continues computing multiple-statement queries even if one of the queries failed'); +$strConfigIgnoreMultiSubmitErrors_name = __('Ignore multiple statement errors'); +$strConfigImport_allow_interrupt_desc = __('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.'); +$strConfigImport_allow_interrupt_name = __('Partial import: allow interrupt'); +$strConfigImport_charset_name = __('Character set of the file'); +$strConfigImport_csv_col_names_name = __('Lines terminated with'); +$strConfigImport_csv_enclosed_name = __('Columns enclosed with'); +$strConfigImport_csv_escaped_name = __('Columns escaped with'); +$strConfigImport_csv_ignore_name = __('Do not abort on INSERT error'); +$strConfigImport_csv_replace_name = __('Replace table data with file'); +$strConfigImport_csv_terminated_name = __('Columns terminated with'); +$strConfigImport_format_desc = __('Default format; be aware that this list depends on location (database, table) and only SQL is always available'); +$strConfigImport_format_name = __('Format of imported file'); +$strConfigImport_ldi_enclosed_name = __('Columns enclosed with'); +$strConfigImport_ldi_escaped_name = __('Columns escaped with'); +$strConfigImport_ldi_ignore_name = __('Do not abort on INSERT error'); +$strConfigImport_ldi_local_option_name = __('Use LOCAL keyword'); +$strConfigImport_ldi_replace_name = __('Replace table data with file'); +$strConfigImport_ldi_terminated_name = __('Columns terminated with'); +$strConfigImport_ods_col_names_name = __('Column names in first row'); +$strConfigImport_ods_empty_rows_name = __('Do not import empty rows'); +$strConfigImport_ods_recognize_currency_name = __('Import currencies ($5.00 to 5.00)'); +$strConfigImport_ods_recognize_percentages_name = __('Import percentages as proper decimals (12.00% to .12)'); +$strConfigImport_skip_queries_desc = __('Number of queries to skip from start'); +$strConfigImport_skip_queries_name = __('Partial import: skip queries'); +$strConfigImport_sql_compatibility_name = __('SQL compatibility mode'); +$strConfigImport_sql_no_auto_value_on_zero_name = __('Do not use AUTO_INCREMENT for zero values'); +$strConfigImport_xls_col_names_name = __('Column names in first row'); +$strConfigImport_xlsx_col_names_name = __('Column names in first row'); +$strConfigInitialSlidersState_name = __('Initial state for sliders'); +$strConfigInsertRows_desc = __('How many rows can be inserted at one time'); +$strConfigInsertRows_name = __('Number of inserted rows'); +$strConfigLimitChars_desc = __('Maximum number of characters shown in any non-numeric column on browse view'); +$strConfigLimitChars_name = __('Limit column characters'); +$strConfigLoginCookieDeleteAll_desc = __('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.'); +$strConfigLoginCookieDeleteAll_name = __('Delete all cookies on logout'); +$strConfigLoginCookieRecall_desc = __('Define whether the previous login should be recalled or not in cookie authentication mode'); +$strConfigLoginCookieRecall_name = __('Recall user name'); +$strConfigLoginCookieStore_desc = __('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.'); +$strConfigLoginCookieStore_name = __('Login cookie store'); +$strConfigLoginCookieValidity_desc = __('Define how long (in seconds) a login cookie is valid'); +$strConfigLoginCookieValidity_name = __('Login cookie validity'); +$strConfigLongtextDoubleTextarea_desc = __('Double size of textarea for LONGTEXT columns'); +$strConfigLongtextDoubleTextarea_name = __('Bigger textarea for LONGTEXT'); +$strConfigMaxCharactersInDisplayedSQL_desc = __('Maximum number of characters used when a SQL query is displayed'); +$strConfigMaxCharactersInDisplayedSQL_name = __('Maximum displayed SQL length'); +$strConfigMaxDbList_cmt = __('Users cannot set a higher value'); +$strConfigMaxDbList_desc = __('Maximum number of databases displayed in database list'); +$strConfigMaxDbList_name = __('Maximum databases'); +$strConfigMaxNavigationItems_desc = __('The number of items that can be displayed on each page of the navigation tree'); +$strConfigMaxNavigationItems_name = __('Maximum items in branch'); +$strConfigMaxRows_desc = __('Number of rows displayed when browsing a result set. If the result set contains more rows, "Previous" and "Next" links will be shown.'); +$strConfigMaxRows_name = __('Maximum number of rows to display'); +$strConfigMaxTableList_cmt = __('Users cannot set a higher value'); +$strConfigMaxTableList_desc = __('Maximum number of tables displayed in table list'); +$strConfigMaxTableList_name = __('Maximum tables'); +$strConfigMcryptDisableWarning_desc = __('Disable the default warning that is displayed if mcrypt is missing for cookie authentication'); +$strConfigMcryptDisableWarning_name = __('mcrypt warning'); +$strConfigMemoryLimit_desc = __('The number of bytes a script is allowed to allocate, eg. [kbd]32M[/kbd] ([kbd]0[/kbd] for no limit)'); +$strConfigMemoryLimit_name = __('Memory limit'); +$strConfigNavigationDisplayLogo_desc = __('Show logo in navigation panel'); +$strConfigNavigationDisplayLogo_name = __('Display logo'); +$strConfigNavigationLogoLink_desc = __('URL where logo in the navigation panel will point to'); +$strConfigNavigationLogoLink_name = __('Logo link URL'); +$strConfigNavigationLogoLinkWindow_desc = __('Open the linked page in the main window ([kbd]main[/kbd]) or in a new one ([kbd]new[/kbd])'); +$strConfigNavigationLogoLinkWindow_name = __('Logo link target'); +$strConfigNavigationDisplayServers_desc = __('Display server choice at the top of the navigation panel'); +$strConfigNavigationDisplayServers_name = __('Display servers selection'); +$strConfigNavigationTreeDefaultTabTable_name = __('Target for quick access icon'); +$strConfigNavigationTreeDisplayItemFilterMinimum_desc = __('Defines the minimum number of items (tables, views, routines and events) to display a filter box.'); +$strConfigNavigationTreeDisplayItemFilterMinimum_name = __('Minimum number of items to display the filter box'); +$strConfigNavigationTreeDisplayDbFilterMinimum_name = __('Minimum number of databases to display the database filter box'); +$strConfigNavigationTreeEnableGrouping_desc = __('Group items in the navigation tree (determined by the separator defined below)'); +$strConfigNavigationTreeEnableGrouping_name = __('Group items in the tree'); +$strConfigNavigationTreeDbSeparator_desc = __('String that separates databases into different tree levels'); +$strConfigNavigationTreeDbSeparator_name = __('Database tree separator'); +$strConfigNavigationTreeTableSeparator_desc = __('String that separates tables into different tree levels'); +$strConfigNavigationTreeTableSeparator_name = __('Table tree separator'); +$strConfigNavigationTreeTableLevel_name = __('Maximum table tree depth'); +$strConfigNavigationTreePointerEnable_desc = __('Highlight server under the mouse cursor'); +$strConfigNavigationTreePointerEnable_name = __('Enable highlighting'); +$strConfigNumRecentTables_desc = __('Maximum number of recently used tables; set 0 to disable'); +$strConfigNumRecentTables_name = __('Recently used tables'); +$strConfigRowActionLinks_desc = __('These are Edit, Copy and Delete links'); +$strConfigRowActionLinks_name = __('Where to show the table row links'); +$strConfigNaturalOrder_desc = __('Use natural order for sorting table and database names'); +$strConfigNaturalOrder_name = __('Natural order'); +$strConfigTableNavigationLinksMode_desc = __('Use only icons, only text or both'); +$strConfigTableNavigationLinksMode_name = __('Table navigation bar'); +$strConfigOBGzip_desc = __('use GZip output buffering for increased speed in HTTP transfers'); +$strConfigOBGzip_name = __('GZip output buffering'); +$strConfigOrder_desc = __('[kbd]SMART[/kbd] - i.e. descending order for columns of type TIME, DATE, DATETIME and TIMESTAMP, ascending order otherwise'); +$strConfigOrder_name = __('Default sorting order'); +$strConfigPersistentConnections_desc = __('Use persistent connections to MySQL databases'); +$strConfigPersistentConnections_name = __('Persistent connections'); +$strConfigPmaNoRelation_DisableWarning_desc = __('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'); +$strConfigPmaNoRelation_DisableWarning_name = __('Missing phpMyAdmin configuration storage tables'); +$strConfigServerLibraryDifference_DisableWarning_desc = __('Disable the default warning that is displayed if a difference between the MySQL library and server is detected'); +$strConfigServerLibraryDifference_DisableWarning_name = __('Server/library difference warning'); +$strConfigReservedWordDisableWarning_desc = __('Disable the default warning that is displayed on the Structure page if column names in a table are reserved MySQL words'); +$strConfigReservedWordDisableWarning_name = __('MySQL reserved word warning'); +$strConfigTabsMode_desc = __('Use only icons, only text or both'); +$strConfigTabsMode_name = __('How to display the menu tabs'); +$strConfigActionLinksMode_desc = __('Use only icons, only text or both'); +$strConfigActionLinksMode_name = __('How to display various action links'); +$strConfigProtectBinary_desc = __('Disallow BLOB and BINARY columns from editing'); +$strConfigProtectBinary_name = __('Protect binary columns'); +$strConfigQueryHistoryDB_desc = __('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).'); +$strConfigQueryHistoryDB_name = __('Permanent query history'); +$strConfigQueryHistoryMax_cmt = __('Users cannot set a higher value'); +$strConfigQueryHistoryMax_desc = __('How many queries are kept in history'); +$strConfigQueryHistoryMax_name = __('Query history length'); +$strConfigQueryWindowDefTab_desc = __('Tab displayed when opening a new query window'); +$strConfigQueryWindowDefTab_name = __('Default query window tab'); +$strConfigQueryWindowHeight_desc = __('Query window height (in pixels)'); +$strConfigQueryWindowHeight_name = __('Query window height'); +$strConfigQueryWindowWidth_desc = __('Query window width (in pixels)'); +$strConfigQueryWindowWidth_name = __('Query window width'); +$strConfigRecodingEngine_desc = __('Select which functions will be used for character set conversion'); +$strConfigRecodingEngine_name = __('Recoding engine'); +$strConfigRememberSorting_desc = __('When browsing tables, the sorting of each table is remembered'); +$strConfigRememberSorting_name = __('Remember table\'s sorting'); +$strConfigRepeatCells_desc = __('Repeat the headers every X cells, [kbd]0[/kbd] deactivates this feature'); +$strConfigRepeatCells_name = __('Repeat headers'); +$strConfigRestoreDefaultValue = __('Restore default value'); +$strConfigGridEditing_name = __('Grid editing: trigger action'); +$strConfigSaveCellsAtOnce_name = __('Grid editing: save all edited cells at once'); +$strConfigSaveDir_desc = __('Directory where exports can be saved on server'); +$strConfigSaveDir_name = __('Save directory'); +$strConfigServers_AllowDeny_order_desc = __('Leave blank if not used'); +$strConfigServers_AllowDeny_order_name = __('Host authorization order'); +$strConfigServers_AllowDeny_rules_desc = __('Leave blank for defaults'); +$strConfigServers_AllowDeny_rules_name = __('Host authorization rules'); +$strConfigServers_AllowNoPassword_name = __('Allow logins without a password'); +$strConfigServers_AllowRoot_name = __('Allow root login'); +$strConfigServers_auth_http_realm_desc = __('HTTP Basic Auth Realm name to display when doing HTTP Auth'); +$strConfigServers_auth_http_realm_name = __('HTTP Realm'); +$strConfigServers_auth_swekey_config_desc = __('The path for the config file for [a@http://swekey.com]SweKey hardware authentication[/a] (not located in your document root; suggested: /etc/swekey.conf)'); +$strConfigServers_auth_swekey_config_name = __('SweKey config file'); +$strConfigServers_auth_type_desc = __('Authentication method to use'); +$strConfigServers_auth_type_name = __('Authentication type'); +$strConfigServers_bookmarktable_desc = __('Leave blank for no [a@http://wiki.phpmyadmin.net/pma/bookmark]bookmark[/a] support, suggested: [kbd]pma__bookmark[/kbd]'); +$strConfigServers_bookmarktable_name = __('Bookmark table'); +$strConfigServers_column_info_desc = __('Leave blank for no column comments/mime types, suggested: [kbd]pma__column_info[/kbd]'); +$strConfigServers_column_info_name = __('Column information table'); +$strConfigServers_compress_desc = __('Compress connection to MySQL server'); +$strConfigServers_compress_name = __('Compress connection'); +$strConfigServers_connect_type_desc = __('How to connect to server, keep [kbd]tcp[/kbd] if unsure'); +$strConfigServers_connect_type_name = __('Connection type'); +$strConfigServers_controlpass_name = __('Control user password'); +$strConfigServers_controluser_desc = __('A special MySQL user configured with limited permissions, more information available on [a@http://wiki.phpmyadmin.net/pma/controluser]wiki[/a]'); +$strConfigServers_controluser_name = __('Control user'); +$strConfigServers_controlhost_desc = __('An alternate host to hold the configuration storage; leave blank to use the already defined host'); +$strConfigServers_controlhost_name = __('Control host'); +$strConfigServers_controlport_desc = __('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'); +$strConfigServers_controlport_name = __('Control port'); +$strConfigServers_designer_coords_desc = __('Leave blank for no Designer support, suggested: [kbd]pma__designer_coords[/kbd]'); +$strConfigServers_designer_coords_name = __('Designer table'); +$strConfigServers_extension_desc = __('What PHP extension to use; you should use mysqli if supported'); +$strConfigServers_extension_name = __('PHP extension to use'); +$strConfigServers_hide_db_desc = __('Hide databases matching regular expression (PCRE)'); +$strConfigServers_hide_db_name = __('Hide databases'); +$strConfigServers_history_desc = __('Leave blank for no SQL query history support, suggested: [kbd]pma__history[/kbd]'); +$strConfigServers_history_name = __('SQL query history table'); +$strConfigServers_host_desc = __('Hostname where MySQL server is running'); +$strConfigServers_host_name = __('Server hostname'); +$strConfigServers_LogoutURL_name = __('Logout URL'); +$strConfigServers_MaxTableUiprefs_desc = __('Limits number of table preferences which are stored in database, the oldest records are automatically removed'); +$strConfigServers_MaxTableUiprefs_name = __('Maximal number of table preferences to store'); +$strConfigServers_nopassword_desc = __('Try to connect without password'); +$strConfigServers_nopassword_name = __('Connect without password'); +$strConfigServers_only_db_desc = __('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].'); +$strConfigServers_only_db_name = __('Show only listed databases'); +$strConfigServers_password_desc = __('Leave empty if not using config auth'); +$strConfigServers_password_name = __('Password for config auth'); +$strConfigServers_pdf_pages_desc = __('Leave blank for no PDF schema support, suggested: [kbd]pma__pdf_pages[/kbd]'); +$strConfigServers_pdf_pages_name = __('PDF schema: pages table'); +$strConfigServers_pmadb_desc = __('Database used for relations, bookmarks, and PDF features. See [a@http://wiki.phpmyadmin.net/pma/pmadb]pmadb[/a] for complete information. Leave blank for no support. Suggested: [kbd]phpmyadmin[/kbd]'); +$strConfigServers_pmadb_name = __('Database name'); +$strConfigServers_port_desc = __('Port on which MySQL server is listening, leave empty for default'); +$strConfigServers_port_name = __('Server port'); +$strConfigServers_recent_desc = __('Leave blank for no "persistent" recently used tables across sessions, suggested: [kbd]pma__recent[/kbd]'); +$strConfigServers_recent_name = __('Recently used table'); +$strConfigServers_relation_desc = __('Leave blank for no [a@http://wiki.phpmyadmin.net/pma/relation]relation-links[/a] support, suggested: [kbd]pma__relation[/kbd]'); +$strConfigServers_relation_name = __('Relation table'); +$strConfigServers_SignonSession_desc = __('See [a@http://wiki.phpmyadmin.net/pma/auth_types#signon]authentication types[/a] for an example'); +$strConfigServers_SignonSession_name = __('Signon session name'); +$strConfigServers_SignonURL_name = __('Signon URL'); +$strConfigServers_socket_desc = __('Socket on which MySQL server is listening, leave empty for default'); +$strConfigServers_socket_name = __('Server socket'); +$strConfigServers_ssl_desc = __('Enable SSL for connection to MySQL server'); +$strConfigServers_ssl_name = __('Use SSL'); +$strConfigServers_table_coords_desc = __('Leave blank for no PDF schema support, suggested: [kbd]pma__table_coords[/kbd]'); +$strConfigServers_table_coords_name = __('PDF schema: table coordinates'); +$strConfigServers_table_info_desc = __('Table to describe the display columns, leave blank for no support; suggested: [kbd]pma__table_info[/kbd]'); +$strConfigServers_table_info_name = __('Display columns table'); +$strConfigServers_table_uiprefs_desc = __('Leave blank for no "persistent" tables\' UI preferences across sessions, suggested: [kbd]pma__table_uiprefs[/kbd]'); +$strConfigServers_table_uiprefs_name = __('UI preferences table'); +$strConfigServers_tracking_add_drop_database_desc = __('Whether a DROP DATABASE IF EXISTS statement will be added as first line to the log when creating a database.'); +$strConfigServers_tracking_add_drop_database_name = __('Add DROP DATABASE'); +$strConfigServers_tracking_add_drop_table_desc = __('Whether a DROP TABLE IF EXISTS statement will be added as first line to the log when creating a table.'); +$strConfigServers_tracking_add_drop_table_name = __('Add DROP TABLE'); +$strConfigServers_tracking_add_drop_view_desc = __('Whether a DROP VIEW IF EXISTS statement will be added as first line to the log when creating a view.'); +$strConfigServers_tracking_add_drop_view_name = __('Add DROP VIEW'); +$strConfigServers_tracking_default_statements_desc = __('Defines the list of statements the auto-creation uses for new versions.'); +$strConfigServers_tracking_default_statements_name = __('Statements to track'); +$strConfigServers_tracking_desc = __('Leave blank for no SQL query tracking support, suggested: [kbd]pma__tracking[/kbd]'); +$strConfigServers_tracking_name = __('SQL query tracking table'); +$strConfigServers_tracking_version_auto_create_desc = __('Whether the tracking mechanism creates versions for tables and views automatically.'); +$strConfigServers_tracking_version_auto_create_name = __('Automatically create versions'); +$strConfigServers_userconfig_desc = __('Leave blank for no user preferences storage in database, suggested: [kbd]pma__userconfig[/kbd]'); +$strConfigServers_userconfig_name = __('User preferences storage table'); +$strConfigServers_users_desc = __('Leave blank to disable configurable menus feature, suggested: [kbd]pma__users[/kbd]'); +$strConfigServers_users_name = __('Users table'); +$strConfigServers_usergroups_desc = __('Leave blank to disable configurable menus feature, suggested: [kbd]pma__usergroups[/kbd]'); +$strConfigServers_usergroups_name = __('User groups table'); +$strConfigServers_navigationhiding_desc = __('Leave blank to disable the feature to hide and show navigation items, suggested: [kbd]pma__navigationhiding[/kbd]'); +$strConfigServers_navigationhiding_name = __('Hidden navigation items table'); +$strConfigServers_user_desc = __('Leave empty if not using config auth'); +$strConfigServers_user_name = __('User for config auth'); +$strConfigServers_verbose_desc = __('A user-friendly description of this server. Leave blank to display the hostname instead.'); +$strConfigServers_verbose_name = __('Verbose name of this server'); +$strConfigShowAll_desc = __('Whether a user should be displayed a "show all (rows)" button'); +$strConfigShowAll_name = __('Allow to display all the rows'); +$strConfigShowChgPassword_desc = __('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'); +$strConfigShowChgPassword_name = __('Show password change form'); +$strConfigShowCreateDb_name = __('Show create database form'); +$strConfigShowDbStructureCreation_desc = __('Show or hide a column displaying the Creation timestamp for all tables'); +$strConfigShowDbStructureCreation_name = __('Show Creation timestamp'); +$strConfigShowDbStructureLastUpdate_desc = __('Show or hide a column displaying the Last update timestamp for all tables'); +$strConfigShowDbStructureLastUpdate_name = __('Show Last update timestamp'); +$strConfigShowDbStructureLastCheck_desc = __('Show or hide a column displaying the Last check timestamp for all tables'); +$strConfigShowDbStructureLastCheck_name = __('Show Last check timestamp'); +$strConfigShowDisplayDirection_desc = __('Defines whether or not type display direction option is shown when browsing a table'); +$strConfigShowDisplayDirection_name = __('Show display direction'); +$strConfigShowFieldTypesInDataEditView_desc = __('Defines whether or not type fields should be initially displayed in edit/insert mode'); +$strConfigShowFieldTypesInDataEditView_name = __('Show field types'); +$strConfigShowFunctionFields_desc = __('Display the function fields in edit/insert mode'); +$strConfigShowFunctionFields_name = __('Show function fields'); +$strConfigShowHint_desc = __('Whether to show hint or not'); +$strConfigShowHint_name = __('Show hint'); +$strConfigShowPhpInfo_desc = __('Shows link to [a@http://php.net/manual/function.phpinfo.php]phpinfo()[/a] output'); +$strConfigShowPhpInfo_name = __('Show phpinfo() link'); +$strConfigShowServerInfo_name = __('Show detailed MySQL server information'); +$strConfigShowSQL_desc = __('Defines whether SQL queries generated by phpMyAdmin should be displayed'); +$strConfigShowSQL_name = __('Show SQL queries'); +$strConfigRetainQueryBox_desc = __('Defines whether the query box should stay on-screen after its submission'); +$strConfigRetainQueryBox_name = __('Retain query box'); +$strConfigShowStats_desc = __('Allow to display database and table statistics (eg. space usage)'); +$strConfigShowStats_name = __('Show statistics'); +$strConfigSkipLockedTables_desc = __('Mark used tables and make it possible to show databases with locked tables'); +$strConfigSkipLockedTables_name = __('Skip locked tables'); +$strConfigSQLQuery_Edit_name = __('Edit'); +$strConfigSQLQuery_Explain_name = __('Explain SQL'); +$strConfigSQLQuery_Refresh_name = __('Refresh'); +$strConfigSQLQuery_ShowAsPHP_name = __('Create PHP Code'); +$strConfigSQLQuery_Validate_desc = __('Requires SQL Validator to be enabled'); +$strConfigSQLQuery_Validate_name = __('Validate SQL'); +$strConfigSQLValidator_password_name = __('Password'); +$strConfigSQLValidator_use_desc = __('[strong]Warning:[/strong] requires PHP SOAP extension or PEAR SOAP to be installed'); +$strConfigSQLValidator_use_name = __('Enable SQL Validator'); +$strConfigSQLValidator_username_desc = __('If you have a custom username, specify it here (defaults to [kbd]anonymous[/kbd])'); +$strConfigSQLValidator_username_name = __('Username'); +$strConfigSuhosinDisableWarning_desc = __('A warning is displayed on the main page if Suhosin is detected'); +$strConfigSuhosinDisableWarning_name = __('Suhosin warning'); +$strConfigTextareaCols_desc = __('Textarea size (columns) in edit mode, this value will be emphasized for SQL query textareas (*2) and for query window (*1.25)'); +$strConfigTextareaCols_name = __('Textarea columns'); +$strConfigTextareaRows_desc = __('Textarea size (rows) in edit mode, this value will be emphasized for SQL query textareas (*2) and for query window (*1.25)'); +$strConfigTextareaRows_name = __('Textarea rows'); +$strConfigTitleDatabase_desc = __('Title of browser window when a database is selected'); +$strConfigTitleDatabase_name = __('Database'); +$strConfigTitleDefault_desc = __('Title of browser window when nothing is selected'); +$strConfigTitleDefault_name = __('Default title'); +$strConfigTitleServer_desc = __('Title of browser window when a server is selected'); +$strConfigTitleServer_name = __('Server'); +$strConfigTitleTable_desc = __('Title of browser window when a table is selected'); +$strConfigTitleTable_name = __('Table'); +$strConfigTrustedProxies_desc = __('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]'); +$strConfigTrustedProxies_name = __('List of trusted proxies for IP allow/deny'); +$strConfigUploadDir_desc = __('Directory on server where you can upload files for import'); +$strConfigUploadDir_name = __('Upload directory'); +$strConfigUseDbSearch_desc = __('Allow for searching inside the entire database'); +$strConfigUseDbSearch_name = __('Use database search'); +$strConfigUserprefsDeveloperTab_desc = __('When disabled, users cannot set any of the options below, regardless of the checkbox on the right'); +$strConfigUserprefsDeveloperTab_name = __('Enable the Developer tab in settings'); +$strConfigVersionCheckLink = __('Check for latest version'); +$strConfigVersionCheck_desc = __('Enables check for latest version on main phpMyAdmin page'); +$strConfigVersionCheck_name = __('Version check'); +$strConfigProxyUrl_desc = __('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"'); +$strConfigProxyUrl_name = __('Proxy url'); +$strConfigProxyUser_desc = __('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.'); +$strConfigProxyUser_name = __('Proxy username'); +$strConfigProxyPass_desc = __('The password for authenticating with the proxy'); +$strConfigProxyPass_name = __('Proxy password'); + +$strConfigZipDump_desc = __('Enable [a@http://en.wikipedia.org/wiki/ZIP_(file_format)]ZIP[/a] compression for import and export operations'); +$strConfigZipDump_name = __('ZIP'); +$strConfigCaptchaLoginPublicKey_desc = __('Enter your public key for your domain reCaptcha service'); +$strConfigCaptchaLoginPublicKey_name = __('Public key for reCaptcha'); +$strConfigCaptchaLoginPrivateKey_desc = __('Enter your private key for your domain reCaptcha service'); +$strConfigCaptchaLoginPrivateKey_name = __('Private key for reCaptcha'); + +$strConfigSendErrorReports_desc = __('Choose the default action when sending error reports'); +$strConfigSendErrorReports_name = __('Send error reports'); + +?> diff --git a/sources/libraries/config/setup.forms.php b/sources/libraries/config/setup.forms.php new file mode 100644 index 0000000..257f6e1 --- /dev/null +++ b/sources/libraries/config/setup.forms.php @@ -0,0 +1,378 @@ + array(1 => array('host'))); + * can be written as + * $forms['Form group']['Form name'] = array('Servers/1/host'); + * + * 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' + * + * @package PhpMyAdmin-Setup + */ + +$forms = array(); +$forms['_config.php'] = array( + 'DefaultLang', + 'ServerDefault'); +$forms['Servers']['Server'] = array('Servers' => array(1 => array( + 'verbose', + 'host', + 'port', + 'socket', + 'ssl', + 'connect_type', + 'extension', + 'compress', + 'nopassword'))); +$forms['Servers']['Server_auth'] = array('Servers' => array(1 => array( + 'auth_type', + ':group:' . __('Config authentication'), + 'user', + 'password', + ':group:end', + ':group:' . __('Cookie authentication'), + 'auth_swekey_config' => './swekey.conf', + ':group:end', + ':group:' . __('HTTP authentication'), + 'auth_http_realm', + ':group:end', + ':group:' . __('Signon authentication'), + 'SignonSession', + 'SignonURL', + 'LogoutURL'))); +$forms['Servers']['Server_config'] = array('Servers' => array(1 => array( + 'only_db', + 'hide_db', + 'AllowRoot', + 'AllowNoPassword', + 'AllowDeny/order', + 'AllowDeny/rules'))); +$forms['Servers']['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', + 'table_uiprefs' => 'pma__table_uiprefs', + 'tracking' => 'pma__tracking', + 'table_coords' => 'pma__table_coords', + 'pdf_pages' => 'pma__pdf_pages', + 'designer_coords' => 'pma__designer_coords', + 'MaxTableUiprefs' => 100))); +$forms['Servers']['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', +))); +$forms['Features']['Import_export'] = array( + 'UploadDir', + 'SaveDir', + 'RecodingEngine' => ':group', + 'IconvExtraParams', + ':group:end', + 'ZipDump', + 'GZipDump', + 'BZipDump', + 'CompressOnFly'); +$forms['Features']['Security'] = array( + 'blowfish_secret', + 'ForceSSL', + 'CheckConfigurationPermissions', + 'TrustedProxies', + 'AllowUserDropDatabase', + 'AllowArbitraryServer', + 'LoginCookieRecall', + 'LoginCookieValidity', + 'LoginCookieStore', + 'LoginCookieDeleteAll', + 'CaptchaLoginPublicKey', + 'CaptchaLoginPrivateKey'); +$forms['Features']['Page_titles'] = array( + 'TitleDefault', + 'TitleTable', + 'TitleDatabase', + 'TitleServer'); +$forms['Features']['Warnings'] = array( + 'ServerLibraryDifference_DisableWarning', + 'PmaNoRelation_DisableWarning', + 'SuhosinDisableWarning', + 'McryptDisableWarning'); +$forms['Features']['Developer'] = array( + 'UserprefsDeveloperTab', + 'Error_Handler/display', + 'DBG/sql'); +$forms['Features']['Other_core_settings'] = array( + 'NaturalOrder', + 'InitialSlidersState', + 'MaxDbList', + 'MaxTableList', + 'NumRecentTables', + 'ShowHint', + 'OBGzip', + 'PersistentConnections', + 'ExecTimeLimit', + 'MemoryLimit', + 'SkipLockedTables', + 'DisableMultiTableMaintenance', + 'UseDbSearch', + 'VersionCheck', + 'SendErrorReports', + 'ProxyUrl', + 'ProxyUser', + 'ProxyPass', + 'AllowThirdPartyFraming', +); +$forms['Sql_queries']['Sql_queries'] = array( + 'ShowSQL', + 'Confirm', + 'QueryHistoryDB', + 'QueryHistoryMax', + 'IgnoreMultiSubmitErrors', + 'MaxCharactersInDisplayedSQL', + 'EditInWindow', + //'QueryWindowWidth', // overridden in theme + //'QueryWindowHeight', + 'QueryWindowDefTab', + 'RetainQueryBox', + 'CodemirrorEnable'); +$forms['Sql_queries']['Sql_box'] = array('SQLQuery' => array( + 'Edit', + 'Explain', + 'ShowAsPHP', + 'Validate', + 'Refresh')); +$forms['Sql_queries']['Sql_validator'] = array('SQLValidator' => array( + 'use', + 'username', + 'password')); +$forms['Navi_panel']['Navi_panel'] = array( + 'NavigationDisplayLogo', + 'NavigationLogoLink', + 'NavigationLogoLinkWindow', + 'NavigationTreePointerEnable', + 'MaxNavigationItems', + 'NavigationTreeEnableGrouping', + 'NavigationTreeDisplayItemFilterMinimum'); +$forms['Navi_panel']['Navi_servers'] = array( + 'NavigationDisplayServers', + 'DisplayServersList'); +$forms['Navi_panel']['Navi_databases'] = array( + 'NavigationTreeDbSeparator'); +$forms['Navi_panel']['Navi_tables'] = array( + 'NavigationTreeDefaultTabTable', + 'NavigationTreeTableSeparator', + 'NavigationTreeTableLevel', +); +$forms['Main_panel']['Startup'] = array( + 'ShowCreateDb', + 'ShowStats', + 'ShowServerInfo', + 'ShowPhpInfo', + 'ShowChgPassword'); +$forms['Main_panel']['DbStructure'] = array( + 'ShowDbStructureCreation', + 'ShowDbStructureLastUpdate', + 'ShowDbStructureLastCheck'); +$forms['Main_panel']['TableStructure'] = array( + 'HideStructureActions'); +$forms['Main_panel']['Browse'] = array( + 'TableNavigationLinksMode', + 'ShowAll', + 'MaxRows', + 'Order', + 'BrowsePointerEnable', + 'BrowseMarkerEnable', + 'GridEditing', + 'SaveCellsAtOnce', + 'ShowDisplayDirection', + 'RepeatCells', + 'LimitChars', + 'RowActionLinks', + 'DefaultDisplay', + 'RememberSorting'); +$forms['Main_panel']['Edit'] = array( + 'ProtectBinary', + 'ShowFunctionFields', + 'ShowFieldTypesInDataEditView', + 'CharEditing', + 'MinSizeForInputField', + 'MaxSizeForInputField', + 'CharTextareaCols', + 'CharTextareaRows', + 'TextareaCols', + 'TextareaRows', + 'LongtextDoubleTextarea', + 'InsertRows', + 'ForeignKeyDropdownOrder', + 'ForeignKeyMaxLimit'); +$forms['Main_panel']['Tabs'] = array( + 'TabsMode', + 'ActionLinksMode', + 'DefaultTabServer', + 'DefaultTabDatabase', + 'DefaultTabTable', + 'QueryWindowDefTab'); +$forms['Import']['Import_defaults'] = array('Import' => array( + 'format', + 'charset', + 'allow_interrupt', + 'skip_queries')); +$forms['Import']['Sql'] = array('Import' => array( + 'sql_compatibility', + 'sql_no_auto_value_on_zero')); +$forms['Import']['Csv'] = array('Import' => array( + ':group:' . __('CSV'), + 'csv_replace', + 'csv_ignore', + 'csv_terminated', + 'csv_enclosed', + 'csv_escaped', + 'csv_col_names', + ':group:end', + ':group:' . __('CSV using LOAD DATA'), + 'ldi_replace', + 'ldi_ignore', + 'ldi_terminated', + 'ldi_enclosed', + 'ldi_escaped', + 'ldi_local_option', + ':group:end')); +$forms['Import']['Open_Document'] = array('Import' => array( + ':group:' . __('OpenDocument Spreadsheet'), + 'ods_col_names', + 'ods_empty_rows', + 'ods_recognize_percentages', + 'ods_recognize_currency')); +$forms['Export']['Export_defaults'] = array('Export' => array( + 'method', + ':group:' . __('Quick'), + 'quick_export_onserver', + 'quick_export_onserver_overwrite', + ':group:end', + ':group:' . __('Custom'), + 'format', + 'compression', + 'charset', + 'asfile' => ':group', + 'onserver', + 'onserver_overwrite', + ':group:end', + 'remember_file_template', + 'file_template_table', + 'file_template_database', + 'file_template_server')); +$forms['Export']['Sql'] = array('Export' => array( + 'sql_include_comments' => ':group', + 'sql_dates', + 'sql_relation', + 'sql_mime', + ':group:end', + 'sql_use_transaction', + 'sql_disable_fk', + 'sql_views_as_tables', + 'sql_compatibility', + ':group:' . __('Database export options'), + 'sql_drop_database', + 'sql_structure_or_data', + ':group:end', + ':group:' . __('Structure'), + 'sql_drop_table', + 'sql_procedure_function', + 'sql_create_table_statements' => ':group', + 'sql_if_not_exists', + 'sql_auto_increment', + ':group:end', + 'sql_backquotes', + ':group:end', + ':group:' . __('Data'), + 'sql_delayed', + 'sql_ignore', + 'sql_type', + 'sql_insert_syntax', + 'sql_max_query_size', + 'sql_hex_for_blob', + 'sql_utc_time')); +$forms['Export']['CodeGen'] = array('Export' => array( + 'codegen_format')); +$forms['Export']['Csv'] = array('Export' => array( + ':group:' . __('CSV'), + 'csv_separator', + 'csv_enclosed', + 'csv_escaped', + 'csv_terminated', + 'csv_null', + 'csv_removeCRLF', + 'csv_columns', + ':group:end', + ':group:' . __('CSV for MS Excel'), + 'excel_null', + 'excel_removeCRLF', + 'excel_columns', + 'excel_edition')); +$forms['Export']['Latex'] = array('Export' => array( + 'latex_caption', + 'latex_structure_or_data', + ':group:' . __('Structure'), + 'latex_structure_caption', + 'latex_structure_continued_caption', + 'latex_structure_label', + 'latex_relation', + 'latex_comments', + 'latex_mime', + ':group:end', + ':group:' . __('Data'), + 'latex_columns', + 'latex_data_caption', + 'latex_data_continued_caption', + 'latex_data_label', + 'latex_null')); +$forms['Export']['Microsoft_Office'] = array('Export' => array( + ':group:' . __('Microsoft Word 2000'), + 'htmlword_structure_or_data', + 'htmlword_null', + 'htmlword_columns')); +$forms['Export']['Open_Document'] = array('Export' => array( + ':group:' . __('OpenDocument Spreadsheet'), + 'ods_columns', + 'ods_null', + ':group:end', + ':group:' . __('OpenDocument Text'), + 'odt_structure_or_data', + ':group:' . __('Structure'), + 'odt_relation', + 'odt_comments', + 'odt_mime', + ':group:end', + ':group:' . __('Data'), + 'odt_columns', + 'odt_null')); +$forms['Export']['Texy'] = array('Export' => array( + 'texytext_structure_or_data', + ':group:' . __('Data'), + 'texytext_null', + 'texytext_columns')); +?> diff --git a/sources/libraries/config/user_preferences.forms.php b/sources/libraries/config/user_preferences.forms.php new file mode 100644 index 0000000..e97bd34 --- /dev/null +++ b/sources/libraries/config/user_preferences.forms.php @@ -0,0 +1,276 @@ + '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' + * + * @package PhpMyAdmin + */ + +$forms = array(); +$forms['Features']['General'] = array( + 'VersionCheck', + 'NaturalOrder', + 'InitialSlidersState', + 'LoginCookieValidity', + 'Servers/1/only_db', // saves to Server/only_db + 'Servers/1/hide_db', // saves to Server/hide_db + 'SkipLockedTables', + 'DisableMultiTableMaintenance', + 'MaxDbList', + 'MaxTableList', + 'NumRecentTables', + 'ShowHint', + 'SendErrorReports'); +$forms['Features']['Text_fields'] = array( + 'CharEditing', + 'MinSizeForInputField', + 'MaxSizeForInputField', + 'CharTextareaCols', + 'CharTextareaRows', + 'TextareaCols', + 'TextareaRows', + 'LongtextDoubleTextarea'); +$forms['Features']['Page_titles'] = array( + 'TitleDefault', + 'TitleTable', + 'TitleDatabase', + 'TitleServer'); +$forms['Features']['Warnings'] = array( + 'ServerLibraryDifference_DisableWarning', + 'PmaNoRelation_DisableWarning', + 'SuhosinDisableWarning', + 'McryptDisableWarning', + 'ReservedWordDisableWarning'); +// settings from this form are treated specially, +// see prefs_forms.php and user_preferences.lib.php +$forms['Features']['Developer'] = array( + 'Error_Handler/display', + 'DBG/sql'); +$forms['Sql_queries']['Sql_queries'] = array( + 'ShowSQL', + 'Confirm', + 'QueryHistoryMax', + 'IgnoreMultiSubmitErrors', + 'MaxCharactersInDisplayedSQL', + 'EditInWindow', + //'QueryWindowWidth', // overridden in theme + //'QueryWindowHeight', + 'QueryWindowDefTab', + 'RetainQueryBox', + 'CodemirrorEnable'); +$forms['Sql_queries']['Sql_box'] = array( + 'SQLQuery/Edit', + 'SQLQuery/Explain', + 'SQLQuery/ShowAsPHP', + 'SQLQuery/Validate', + 'SQLQuery/Refresh'); +$forms['Navi_panel']['Navi_panel'] = array( + 'NavigationDisplayLogo', + 'NavigationLogoLink', + 'NavigationLogoLinkWindow', + 'NavigationTreePointerEnable', + 'MaxNavigationItems', + 'NavigationTreeEnableGrouping', + 'NavigationTreeDisplayItemFilterMinimum'); +$forms['Navi_panel']['Navi_databases'] = array( + 'NavigationTreeDisplayDbFilterMinimum', + 'NavigationTreeDbSeparator'); +$forms['Navi_panel']['Navi_tables'] = array( + 'NavigationTreeDefaultTabTable', + 'NavigationTreeTableSeparator', + 'NavigationTreeTableLevel', +); +$forms['Main_panel']['Startup'] = array( + 'ShowCreateDb', + 'ShowStats', + 'ShowServerInfo'); +$forms['Main_panel']['DbStructure'] = array( + 'ShowDbStructureCreation', + 'ShowDbStructureLastUpdate', + 'ShowDbStructureLastCheck'); +$forms['Main_panel']['TableStructure'] = array( + 'HideStructureActions'); +$forms['Main_panel']['Browse'] = array( + 'TableNavigationLinksMode', + 'ActionLinksMode', + 'ShowAll', + 'MaxRows', + 'Order', + 'DisplayBinaryAsHex', + 'BrowsePointerEnable', + 'BrowseMarkerEnable', + 'GridEditing', + 'SaveCellsAtOnce', + 'ShowDisplayDirection', + 'RepeatCells', + 'LimitChars', + 'RowActionLinks', + 'DefaultDisplay', + 'RememberSorting'); +$forms['Main_panel']['Edit'] = array( + 'ProtectBinary', + 'ShowFunctionFields', + 'ShowFieldTypesInDataEditView', + 'InsertRows', + 'ForeignKeyDropdownOrder', + 'ForeignKeyMaxLimit'); +$forms['Main_panel']['Tabs'] = array( + 'TabsMode', + 'DefaultTabServer', + 'DefaultTabDatabase', + 'DefaultTabTable'); +$forms['Main_panel']['DisplayRelationalSchema'] = array( + 'PDFDefaultPageSize'); + +$forms['Import']['Import_defaults'] = array( + 'Import/format', + 'Import/charset', + 'Import/allow_interrupt', + 'Import/skip_queries'); +$forms['Import']['Sql'] = array( + 'Import/sql_compatibility', + 'Import/sql_no_auto_value_on_zero'); +$forms['Import']['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'); +$forms['Import']['Open_Document'] = array( + ':group:' . __('OpenDocument Spreadsheet'), + 'Import/ods_col_names', + 'Import/ods_empty_rows', + 'Import/ods_recognize_percentages', + 'Import/ods_recognize_currency'); +$forms['Export']['Export_defaults'] = 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/asfile' => ':group', + 'Export/onserver', + 'Export/onserver_overwrite', + ':group:end', + 'Export/file_template_table', + 'Export/file_template_database', + 'Export/file_template_server'); +$forms['Export']['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_compatibility', + ':group:' . __('Database export options'), + 'Export/sql_drop_database', + 'Export/sql_structure_or_data', + ':group:end', + ':group:' . __('Structure'), + 'Export/sql_drop_table', + 'Export/sql_procedure_function', + 'Export/sql_create_table_statements' => ':group', + 'Export/sql_if_not_exists', + 'Export/sql_auto_increment', + ':group:end', + '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_blob', + 'Export/sql_utc_time'); +$forms['Export']['CodeGen'] = array( + 'Export/codegen_format'); +$forms['Export']['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'); +$forms['Export']['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'); +$forms['Export']['Microsoft_Office'] = array( + ':group:' . __('Microsoft Word 2000'), + 'Export/htmlword_structure_or_data', + 'Export/htmlword_null', + 'Export/htmlword_columns'); +$forms['Export']['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'); +$forms['Export']['Texy'] = array( + 'Export/texytext_structure_or_data', + ':group:' . __('Data'), + 'Export/texytext_null', + 'Export/texytext_columns'); +?> diff --git a/sources/libraries/core.lib.php b/sources/libraries/core.lib.php new file mode 100644 index 0000000..30f33c3 --- /dev/null +++ b/sources/libraries/core.lib.php @@ -0,0 +1,827 @@ + + * // $_REQUEST['db'] not set + * echo PMA_ifSetOr($_REQUEST['db'], ''); // '' + * // $_REQUEST['sql_query'] not set + * echo PMA_ifSetOr($_REQUEST['sql_query']); // null + * // $cfg['ForceSSL'] not set + * echo PMA_ifSetOr($cfg['ForceSSL'], false, 'boolean'); // false + * echo PMA_ifSetOr($cfg['ForceSSL']); // null + * // $cfg['ForceSSL'] set to 1 + * echo PMA_ifSetOr($cfg['ForceSSL'], false, 'boolean'); // false + * echo PMA_ifSetOr($cfg['ForceSSL'], false, 'similar'); // 1 + * echo PMA_ifSetOr($cfg['ForceSSL'], false); // 1 + * // $cfg['ForceSSL'] set to true + * echo PMA_ifSetOr($cfg['ForceSSL'], 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 PMA_isValid() + */ +function PMA_ifSetOr(&$var, $default = null, $type = 'similar') +{ + if (! PMA_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; + * PMA_isValid($_REQUEST['doit'], 'identical', 'true'); // false + * // $_REQUEST['doit'] = 'true'; + * PMA_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 http://php.net/gettype + */ +function PMA_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 aliaes 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 (bool) strlen($var); + } + return $is_scalar; + } + + if ($type === 'numeric') { + return is_numeric($var); + } + + if (gettype($var) === $type) { + return true; + } + + return false; +} + +/** + * 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 + */ +function PMA_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 + * @param boolean $delete_session whether to delete session cookie + * + * @return void + */ +function PMA_fatalError( + $error_message, $message_args = null, $delete_session = true +) { + /* 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); + } + + if ($GLOBALS['is_ajax_request']) { + $response = PMA_Response::getInstance(); + $response->isSuccess(false); + $response->addJSON('message', PMA_Message::error($error_message)); + } else { + $error_message = strtr($error_message, array('
      ' => '[br]')); + + /* Load gettext for fatal errors */ + if (!function_exists('__')) { + include_once './libraries/php-gettext/gettext.inc'; + } + + // these variables are used in the included file libraries/error.inc.php + $error_header = __('Error'); + $lang = $GLOBALS['available_languages'][$GLOBALS['lang']][1]; + $dir = $GLOBALS['text_dir']; + + // on fatal errors it cannot hurt to always delete the current session + if ($delete_session + && isset($GLOBALS['session_name']) + && isset($_COOKIE[$GLOBALS['session_name']]) + ) { + $GLOBALS['PMA_Config']->removeCookie($GLOBALS['session_name']); + } + + // 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 + */ +function PMA_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 PMA_linkURL('http://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 messsage. + * + * @return void + */ +function PMA_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 = PMA_getPHPDocLink('book.' . $extension . '.php'); + $message = sprintf( + $message, + '[a@' . $doclink . '@Documentation][em]' . $extension . '[/em][/a]' + ); + if ($extra != '') { + $message .= ' ' . $extra; + } + if ($fatal) { + PMA_fatalError($message); + } else { + $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 + */ +function PMA_getTableCount($db) +{ + $tables = $GLOBALS['dbi']->tryQuery( + 'SHOW TABLES FROM ' . PMA_Util::backquote($db) . ';', + null, PMA_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 (http://moodle.org) by Martin Dougiamas + * (renamed with PMA prefix to avoid double definition when embedded + * in Moodle) + * + * @param string $size size + * + * @return integer $size + */ +function PMA_getRealSize($size = 0) +{ + if (! $size) { + return 0; + } + + $scan['gb'] = 1073741824; //1024 * 1024 * 1024; + $scan['g'] = 1073741824; //1024 * 1024 * 1024; + $scan['mb'] = 1048576; + $scan['m'] = 1048576; + $scan['kb'] = 1024; + $scan['k'] = 1024; + $scan['b'] = 1; + + foreach ($scan as $unit => $factor) { + if (strlen($size) > strlen($unit) + && strtolower(substr($size, strlen($size) - strlen($unit))) == $unit + ) { + return substr($size, 0, strlen($size) - strlen($unit)) * $factor; + } + } + + return $size; +} // end function PMA_getRealSize() + +/** + * merges array recursive like array_merge_recursive() but keyed-values are + * always overwritten. + * + * array PMA_arrayMergeRecursive(array $array1[, array $array2[, array ...]]) + * + * @return array merged array + * + * @see http://php.net/array_merge + * @see http://php.net/array_merge_recursive + */ +function PMA_arrayMergeRecursive() +{ + switch(func_num_args()) { + case 0 : + return false; + break; + case 1 : + // when does that happen? + return func_get_arg(0); + break; + case 2 : + $args = func_get_args(); + if (! is_array($args[0]) || ! is_array($args[1])) { + return $args[1]; + } + foreach ($args[1] as $key2 => $value2) { + if (isset($args[0][$key2]) && !is_int($key2)) { + $args[0][$key2] = PMA_arrayMergeRecursive( + $args[0][$key2], $value2 + ); + } else { + // we erase the parent array, otherwise we cannot override + // a directive that contains array elements, like this: + // (in config.default.php) + // $cfg['ForeignKeyDropdownOrder']= array('id-content','content-id'); + // (in config.inc.php) + // $cfg['ForeignKeyDropdownOrder']= array('content-id'); + if (is_int($key2) && $key2 == 0) { + unset($args[0]); + } + $args[0][$key2] = $value2; + } + } + return $args[0]; + break; + default : + $args = func_get_args(); + $args[1] = PMA_arrayMergeRecursive($args[0], $args[1]); + array_shift($args); + return call_user_func_array('PMA_arrayMergeRecursive', $args); + break; + } +} + +/** + * calls $function for every element in $array recursively + * + * this function is protected against deep recursion attack CVE-2006-1549, + * 1000 seems to be more than enough + * + * @param array &$array array to walk + * @param string $function function to call for every array element + * @param bool $apply_to_keys_also whether to call the function for the keys also + * + * @return void + * + * @see http://www.php-security.org/MOPB/MOPB-02-2007.html + * @see http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2006-1549 + */ +function PMA_arrayWalkRecursive(&$array, $function, $apply_to_keys_also = false) +{ + static $recursive_counter = 0; + if (++$recursive_counter > 1000) { + PMA_fatalError(__('possible deep recursion attack')); + } + foreach ($array as $key => $value) { + if (is_array($value)) { + PMA_arrayWalkRecursive($array[$key], $function, $apply_to_keys_also); + } else { + $array[$key] = $function($value); + } + + if ($apply_to_keys_also && is_string($key)) { + $new_key = $function($key); + if ($new_key != $key) { + $array[$new_key] = $array[$key]; + unset($array[$key]); + } + } + } + $recursive_counter--; +} + +/** + * boolean phpMyAdmin.PMA_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 + * + * @return boolean whether $page is valid or not (in $whitelist or not) + */ +function PMA_checkPageValidity(&$page, $whitelist) +{ + if (! isset($page) || !is_string($page)) { + return false; + } + + if (in_array($page, $whitelist)) { + return true; + } elseif (in_array(substr($page, 0, strpos($page . '?', '?')), $whitelist)) { + return true; + } else { + $_page = urldecode($page); + if (in_array(substr($_page, 0, strpos($_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 + */ +function PMA_getenv($var_name) +{ + if (isset($_SERVER[$var_name])) { + return $_SERVER[$var_name]; + } elseif (isset($_ENV[$var_name])) { + return $_ENV[$var_name]; + } elseif (getenv($var_name)) { + return getenv($var_name); + } elseif (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 boolean always true + */ +function PMA_sendHeaderLocation($uri, $use_refresh = false) +{ + if (PMA_IS_IIS && strlen($uri) > 600) { + include_once './libraries/js_escape.lib.php'; + PMA_Response::getInstance()->disable(); + + echo '- - -' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + + } else { + if (SID) { + if (strpos($uri, '?') === false) { + header('Location: ' . $uri . '?' . SID); + } else { + $separator = PMA_URL_getArgSeparator(); + header('Location: ' . $uri . $separator . SID); + } + } else { + session_write_close(); + if (headers_sent()) { + if (function_exists('debug_print_backtrace')) { + echo '
      ';
      +                    debug_print_backtrace();
      +                    echo '
      '; + } + trigger_error( + 'PMA_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 (PMA_IS_IIS && $use_refresh) { + header('Refresh: 0; ' . $uri); + } else { + header('Location: ' . $uri); + } + } + } +} + +/** + * Outputs headers to prevent caching in browser (and on the way). + * + * @return void + */ +function PMA_noCacheHeader() +{ + if (defined('TESTSUITE')) { + return; + } + // rfc2616 - Section 14.21 + header('Expires: ' . date(DATE_RFC1123)); + // HTTP/1.1 + header( + 'Cache-Control: no-store, no-cache, must-revalidate,' + . ' pre-check=0, post-check=0, max-age=0' + ); + if (PMA_USR_BROWSER_AGENT == 'IE') { + /* On SSL IE sometimes fails with: + * + * Internet Explorer was not able to open this Internet site. The + * requested site is either unavailable or cannot be found. Please + * try again later. + * + * Adding Pragma: public fixes this. + */ + header('Pragma: public'); + } else { + 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: ' . date(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 + */ +function PMA_downloadHeader($filename, $mimetype, $length = 0, $no_cache = true) +{ + if ($no_cache) { + PMA_noCacheHeader(); + } + /* Replace all possibly dangerous chars in filename */ + $filename = str_replace(array(';', '"', "\n", "\r"), '-', $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) + if (strpos($mimetype, 'gzip') !== false) { + header('Content-Encoding: gzip'); + } + header('Content-Transfer-Encoding: binary'); + if ($length > 0) { + header('Content-Length: ' . $length); + } +} + +/** + * Checks whether element given by $path exists in $array. + * $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 arry + * @param array $array the array + * + * @return mixed array element or $default + */ +function PMA_arrayKeyExists($path, $array) +{ + $keys = explode('/', $path); + $value =& $array; + foreach ($keys as $key) { + if (! isset($value[$key])) { + return false; + } + $value =& $value[$key]; + } + return true; +} + +/** + * 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 arry + * @param array $array the array + * @param mixed $default default value + * + * @return mixed array element or $default + */ +function PMA_arrayRead($path, $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 + */ +function PMA_arrayWrite($path, &$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 + */ +function PMA_arrayRemove($path, &$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. + */ +function PMA_linkURL($url) +{ + if (!preg_match('#^https?://#', $url) || defined('PMA_SETUP')) { + return $url; + } else { + if (!function_exists('PMA_URL_getCommon')) { + include_once './libraries/url_generating.lib.php'; + } + $params = array(); + $params['url'] = $url; + return './url.php' . PMA_URL_getCommon($params); + } +} + +/** + * Adds JS code snippets to be displayed by the PMA_Response class. + * Adds a newline to each snippet. + * + * @param string $str Js code to be added (e.g. "token=1234;") + * + * @return void + */ +function PMA_addJSCode($str) +{ + $response = PMA_Response::getInstance(); + $header = $response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addCode($str); +} + +/** + * Adds JS code snippet for variable assignment + * to be displayed by the PMA_Response class. + * + * @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 void + */ +function PMA_addJSVar($key, $value, $escape = true) +{ + PMA_addJSCode(PMA_getJsValue($key, $value, $escape)); +} + +?> diff --git a/sources/libraries/create_addfield.lib.php b/sources/libraries/create_addfield.lib.php new file mode 100644 index 0000000..1323b7a --- /dev/null +++ b/sources/libraries/create_addfield.lib.php @@ -0,0 +1,325 @@ + 0 + ) { + $num_fields = (int) $_REQUEST['num_fields']; + } else { + $num_fields = 4; + } + + return $num_fields; +} + +/** + * Function to execute the column creation statement + * + * @param string $db current database + * @param string $table current table + * @param string $err_url error page url + * + * @return array + */ +function PMA_tryColumnCreationQuery($db, $table, $err_url) +{ + // get column addition statements + $sql_statement = PMA_getColumnCreationStatements(false); + + // To allow replication, we first select the db to use and then run queries + // on this db. + $GLOBALS['dbi']->selectDb($db) + or PMA_Util::mysqlDie( + $GLOBALS['dbi']->getError(), + 'USE ' . PMA_Util::backquote($db), '', + $err_url + ); + $sql_query = 'ALTER TABLE ' . + PMA_Util::backquote($table) . ' ' . $sql_statement . ';'; + return array($GLOBALS['dbi']->tryQuery($sql_query) , $sql_query); +} +?> diff --git a/sources/libraries/data_dictionary_relations.lib.php b/sources/libraries/data_dictionary_relations.lib.php new file mode 100644 index 0000000..99a0526 --- /dev/null +++ b/sources/libraries/data_dictionary_relations.lib.php @@ -0,0 +1,167 @@ + array( + 'DEFAULT_COLLATE_NAME' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ) + ), + 'COLLATIONS' => array( + 'CHARACTER_SET_NAME' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'CHARACTER_SETS', + 'foreign_field' => 'CHARACTER_SET_NAME' + ) + ), + 'COLUMNS' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'SCHEMAS', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'COLLATION_NAME' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ) + ), + 'INDEXES' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'SCHEMAS', + 'foreign_field' => 'SCHEMA_NAME' + ) + ), + 'INDEX_PARTS' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'SCHEMAS', + 'foreign_field' => 'SCHEMA_NAME' + ) + ), + 'INNODB_LOCKS' => array( + 'LOCK_TRX_ID' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'INNODB_TRX', + 'foreign_field' => 'TRX_ID' + ) + ), + 'INNODB_LOCK_WAITS' => array( + 'REQUESTING_TRX_ID' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'INNODB_TRX', + 'foreign_field' => 'TRX_ID' + ), + 'REQUESTED_LOCK_ID' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'INNODB_LOCKS', + 'foreign_field' => 'LOCK_ID' + ), + 'BLOCKING_TRX_ID' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'INNODB_TRX', + 'foreign_field' => 'TRX_ID' + ), + 'BLOCKING_LOCK_ID' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'INNODB_LOCKS', + 'foreign_field' => 'LOCK_ID' + ) + ), + 'INNODB_SYS_COLUMNS' => array( + 'TABLE_ID' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'INNODB_SYS_TABLES', + 'foreign_field' => 'TABLE_ID' + ) + ), + 'INNODB_SYS_FIELDS' => array( + 'INDEX_ID' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'INNODB_SYS_INDEXES', + 'foreign_field' => 'INDEX_ID' + ) + ), + 'INNODB_SYS_INDEXES' => array( + 'TABLE_ID' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'INNODB_SYS_TABLES', + 'foreign_field' => 'TABLE_ID' + ) + ), + 'INNODB_SYS_TABLESTATS' => array( + 'TABLE_ID' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'INNODB_SYS_TABLES', + 'foreign_field' => 'TABLE_ID' + ) + ), + 'PLUGINS' => array( + 'MODULE_NAME' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'MODULES', + 'foreign_field' => 'MODULE_NAME' + ) + ), + 'SCHEMAS' => array( + 'DEFAULT_COLLATION_NAME' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ) + ), + 'TABLES' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'SCHEMAS', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'TABLE_COLLATION' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ) + ), + 'TABLE_CACHE' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'SCHEMAS', + 'foreign_field' => 'SCHEMA_NAME' + ) + ), + 'TABLE_CONSTRAINTS' => array( + 'CONSTRAINT_SCHEMA' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'SCHEMAS', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'SCHEMAS', + 'foreign_field' => 'SCHEMA_NAME' + ) + ), + 'TABLE_DEFINITION_CACHE' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'data_dictionary', + 'foreign_table' => 'SCHEMAS', + 'foreign_field' => 'SCHEMA_NAME' + ) + ) +); + +?> diff --git a/sources/libraries/database_interface.inc.php b/sources/libraries/database_interface.inc.php new file mode 100644 index 0000000..2ad1706 --- /dev/null +++ b/sources/libraries/database_interface.inc.php @@ -0,0 +1,87 @@ + diff --git a/sources/libraries/db_common.inc.php b/sources/libraries/db_common.inc.php new file mode 100644 index 0000000..a16a700 --- /dev/null +++ b/sources/libraries/db_common.inc.php @@ -0,0 +1,100 @@ +isSystemSchema($db); +if ($db_is_information_schema) { + $is_show_stats = false; +} + +/** + * Defines the urls to return to in case of error in a sql statement + */ +$err_url_0 = 'index.php?' . PMA_URL_getCommon(); +$err_url = $cfg['DefaultTabDatabase'] . '?' . PMA_URL_getCommon($db); + + +/** + * Ensures the database exists (else move to the "parent" script) and displays + * headers + */ +if (! isset($is_db) || ! $is_db) { + if (strlen($db)) { + $is_db = $GLOBALS['dbi']->selectDb($db); + // This "Command out of sync" 2014 error may happen, for example + // after calling a MySQL procedure; at this point we can't select + // the db but it's not necessarily wrong + if ($GLOBALS['dbi']->getError() && $GLOBALS['errno'] == 2014) { + $is_db = true; + unset($GLOBALS['errno']); + } + } + // Not a valid db name -> back to the welcome page + $uri = $cfg['PmaAbsoluteUri'] . 'index.php?' + . PMA_URL_getCommon('', '', '&') + . (isset($message) ? '&message=' . urlencode($message) : '') . '&reload=1'; + if (! strlen($db) || ! $is_db) { + $response = PMA_Response::getInstance(); + if ($response->isAjax()) { + $response->isSuccess(false); + $response->addJSON( + 'message', + PMA_Message::error(__('No databases selected.')) + ); + } else { + PMA_sendHeaderLocation($uri); + } + exit; + } +} // end if (ensures db exists) + +/** + * Changes database charset if requested by the user + */ +if (isset($_REQUEST['submitcollation']) + && isset($_REQUEST['db_collation']) + && ! empty($_REQUEST['db_collation']) +) { + list($db_charset) = explode('_', $_REQUEST['db_collation']); + $sql_query = 'ALTER DATABASE ' + . PMA_Util::backquote($db) + . ' DEFAULT' . PMA_generateCharsetQueryPart($_REQUEST['db_collation']); + $result = $GLOBALS['dbi']->query($sql_query); + $message = PMA_Message::success(); + unset($db_charset); + + /** + * If we are in an Ajax request, let us stop the execution here. Necessary for + * db charset change action on db_operations.php. If this causes a bug on + * other pages, we might have to move this to a different location. + */ + if ( $GLOBALS['is_ajax_request'] == true) { + $response = PMA_Response::getInstance(); + $response->isSuccess($message->isSuccess()); + $response->addJSON('message', $message); + exit; + } +} + +/** + * Set parameters for links + */ +$url_query = PMA_URL_getCommon($db); + +?> diff --git a/sources/libraries/db_info.inc.php b/sources/libraries/db_info.inc.php new file mode 100644 index 0000000..ea964a6 --- /dev/null +++ b/sources/libraries/db_info.inc.php @@ -0,0 +1,248 @@ +isSystemSchema($db)) { + $is_show_stats = false; + $db_is_information_schema = true; +} + +/** + * @global array information about tables in db + */ +$tables = array(); + +$tooltip_truename = array(); +$tooltip_aliasname = array(); + +// Special speedup for newer MySQL Versions (in 4.0 format changed) +if (true === $cfg['SkipLockedTables'] && ! PMA_DRIZZLE) { + $db_info_result = $GLOBALS['dbi']->query( + 'SHOW OPEN TABLES FROM ' . PMA_Util::backquote($db) . ';' + ); + + // Blending out tables in use + if ($db_info_result && $GLOBALS['dbi']->numRows($db_info_result) > 0) { + while ($tmp = $GLOBALS['dbi']->fetchAssoc($db_info_result)) { + // if in use, memorize table name + if ($tmp['In_use'] > 0) { + $sot_cache[$tmp['Table']] = true; + } + } + $GLOBALS['dbi']->freeResult($db_info_result); + + if (isset($sot_cache)) { + $db_info_result = false; + + $tblGroupSql = ""; + $whereAdded = false; + if (PMA_isValid($_REQUEST['tbl_group'])) { + $group = PMA_Util::escapeMysqlWildcards($_REQUEST['tbl_group']); + $groupWithSeperator = PMA_Util::escapeMysqlWildcards( + $_REQUEST['tbl_group'] + . $GLOBALS['cfg']['NavigationTreeTableSeparator'] + ); + $tblGroupSql .= " WHERE (" + . PMA_Util::backquote('Tables_in_' . $db) + . " LIKE '" . $groupWithSeperator . "%'" + . " OR " + . PMA_Util::backquote('Tables_in_' . $db) + . " LIKE '" . $group . "')"; + $whereAdded = true; + } + if (PMA_isValid($_REQUEST['tbl_type'], array('table', 'view'))) { + $tblGroupSql .= $whereAdded ? " AND" : " WHERE"; + if ($_REQUEST['tbl_type'] == 'view') { + $tblGroupSql .= " `Table_type` != 'BASE TABLE'"; + } else { + $tblGroupSql .= " `Table_type` = 'BASE TABLE'"; + } + } + $db_info_result = $GLOBALS['dbi']->query( + 'SHOW FULL TABLES FROM ' . PMA_Util::backquote($db) . $tblGroupSql, + null, PMA_DatabaseInterface::QUERY_STORE + ); + unset($tblGroupSql, $whereAdded); + + if ($db_info_result && $GLOBALS['dbi']->numRows($db_info_result) > 0) { + while ($tmp = $GLOBALS['dbi']->fetchRow($db_info_result)) { + if (! isset($sot_cache[$tmp[0]])) { + $sts_result = $GLOBALS['dbi']->query( + "SHOW TABLE STATUS FROM " . PMA_Util::backquote($db) + . " LIKE '" . PMA_Util::sqlAddSlashes($tmp[0], true) + . "';" + ); + $sts_tmp = $GLOBALS['dbi']->fetchAssoc($sts_result); + $GLOBALS['dbi']->freeResult($sts_result); + unset($sts_result); + + if (! isset($sts_tmp['Type']) && isset($sts_tmp['Engine'])) { + $sts_tmp['Type'] =& $sts_tmp['Engine']; + } + $tables[$sts_tmp['Name']] = $sts_tmp; + } else { // table in use + $tables[$tmp[0]] = array( + 'TABLE_NAME' => $tmp[0], + 'ENGINE' => '', + 'TABLE_TYPE' => '', + 'TABLE_ROWS' => 0, + ); + } + } + if ($GLOBALS['cfg']['NaturalOrder']) { + uksort($tables, 'strnatcasecmp'); + } + + $sot_ready = true; + } elseif ($db_info_result) { + $GLOBALS['dbi']->freeResult($db_info_result); + } + unset($sot_cache); + } + unset($tmp); + } elseif ($db_info_result) { + $GLOBALS['dbi']->freeResult($db_info_result); + } +} + +if (! isset($sot_ready)) { + + // Set some sorting defaults + $sort = 'Name'; + $sort_order = 'ASC'; + + if (isset($_REQUEST['sort'])) { + $sortable_name_mappings = array( + 'table' => 'Name', + 'records' => 'Rows', + 'type' => 'Engine', + 'collation' => 'Collation', + 'size' => 'Data_length', + 'overhead' => 'Data_free', + 'creation' => 'Create_time', + 'last_update' => 'Update_time', + 'last_check' => 'Check_time' + ); + + // Make sure the sort type is implemented + if (isset($sortable_name_mappings[$_REQUEST['sort']])) { + $sort = $sortable_name_mappings[$_REQUEST['sort']]; + if ($_REQUEST['sort_order'] == 'DESC') { + $sort_order = 'DESC'; + } + } + } + + $tbl_group = false; + $groupWithSeperator = false; + $tbl_type = null; + $limit_offset = 0; + $limit_count = false; + $groupTable = array(); + + if (! empty($_REQUEST['tbl_group']) || ! empty($_REQUEST['tbl_type'])) { + if (! empty($_REQUEST['tbl_type'])) { + // only tables for selected type + $tbl_type = $_REQUEST['tbl_type']; + } + if (! empty($_REQUEST['tbl_group'])) { + // only tables for selected group + $tbl_group = $_REQUEST['tbl_group']; + // include the table with the exact name of the group if such exists + $groupTable = $GLOBALS['dbi']->getTablesFull( + $db, $tbl_group, false, null, $limit_offset, + $limit_count, $sort, $sort_order, $tbl_type + ); + $groupWithSeperator = $tbl_group + . $GLOBALS['cfg']['NavigationTreeTableSeparator']; + } + } else { + // all tables in db + // - get the total number of tables + // (needed for proper working of the MaxTableList feature) + $tables = $GLOBALS['dbi']->getTables($db); + $total_num_tables = count($tables); + if (isset($sub_part) && $sub_part == '_export') { + // (don't fetch only a subset if we are coming from db_export.php, + // because I think it's too risky to display only a subset of the + // table names when exporting a db) + /** + * + * @todo Page selector for table names? + */ + } else { + // fetch the details for a possible limited subset + $limit_offset = $pos; + $limit_count = true; + } + } + $tables = array_merge( + $groupTable, + $GLOBALS['dbi']->getTablesFull( + $db, $groupWithSeperator, ($groupWithSeperator != false), null, + $limit_offset, $limit_count, $sort, $sort_order, $tbl_type + ) + ); +} + +/** + * @global int count of tables in db + */ +$num_tables = count($tables); +// (needed for proper working of the MaxTableList feature) +if (! isset($total_num_tables)) { + $total_num_tables = $num_tables; +} + +/** + * cleanup + */ +unset($each_table, $db_info_result); + +/** + * If coming from a Show MySQL link on the home page, + * put something in $sub_part + */ +if (empty($sub_part)) { + $sub_part = '_structure'; +} +?> diff --git a/sources/libraries/db_table_exists.lib.php b/sources/libraries/db_table_exists.lib.php new file mode 100644 index 0000000..706629e --- /dev/null +++ b/sources/libraries/db_table_exists.lib.php @@ -0,0 +1,104 @@ +selectDb($db); + } else { + $is_db = false; + } + + if (! $is_db) { + // not a valid db name -> back to the welcome page + if (! defined('IS_TRANSFORMATION_WRAPPER')) { + $response = PMA_Response::getInstance(); + if ($response->isAjax()) { + $response->isSuccess(false); + $response->addJSON( + 'message', + PMA_Message::error(__('No databases selected.')) + ); + } else { + $url_params = array('reload' => 1); + if (isset($message)) { + $url_params['message'] = $message; + } + if (! empty($sql_query)) { + $url_params['sql_query'] = $sql_query; + } + if (isset($show_as_php)) { + $url_params['show_as_php'] = $show_as_php; + } + PMA_sendHeaderLocation( + $cfg['PmaAbsoluteUri'] . 'index.php' + . PMA_URL_getCommon($url_params, '&') + ); + } + exit; + } + } +} // end if (ensures db exists) + +if (empty($is_table) + && !defined('PMA_SUBMIT_MULT') + && ! defined('TABLE_MAY_BE_ABSENT') +) { + // Not a valid table name -> back to the db_sql.php + + if (strlen($table)) { + $is_table = isset(PMA_Table::$cache[$db][$table]); + + if (! $is_table) { + $_result = $GLOBALS['dbi']->tryQuery( + 'SHOW TABLES LIKE \'' . PMA_Util::sqlAddSlashes($table, true) + . '\';', + null, PMA_DatabaseInterface::QUERY_STORE + ); + $is_table = @$GLOBALS['dbi']->numRows($_result); + $GLOBALS['dbi']->freeResult($_result); + } + } else { + $is_table = false; + } + + if (! $is_table) { + if (! defined('IS_TRANSFORMATION_WRAPPER')) { + if (strlen($table)) { + // SHOW TABLES doesn't show temporary tables, so try select + // (as it can happen just in case temporary table, it should be + // fast): + + /** + * @todo should this check really + * only happen if IS_TRANSFORMATION_WRAPPER? + */ + $_result = $GLOBALS['dbi']->tryQuery( + 'SELECT COUNT(*) FROM ' . PMA_Util::backquote($table) . ';', + null, + PMA_DatabaseInterface::QUERY_STORE + ); + $is_table = ($_result && @$GLOBALS['dbi']->numRows($_result)); + $GLOBALS['dbi']->freeResult($_result); + } + + if (! $is_table) { + include './db_sql.php'; + exit; + } + } + + if (! $is_table) { + exit; + } + } +} // end if (ensures table exists) +?> diff --git a/sources/libraries/dbi/DBIDrizzle.class.php b/sources/libraries/dbi/DBIDrizzle.class.php new file mode 100644 index 0000000..82293d9 --- /dev/null +++ b/sources/libraries/dbi/DBIDrizzle.class.php @@ -0,0 +1,712 @@ +addUds($uds, $user, $password, $db, $options); + } else { + $con = $drizzle->addTcp($host, $port, $user, $password, $db, $options); + } + + return $con; + } + + /** + * connects to the database server + * + * @param string $user drizzle user name + * @param string $password drizzle user password + * @param bool $is_controluser whether this is a control user connection + * @param array $server host/port/socket/persistent + * @param bool $auxiliary_connection (when true, don't go back to login if + * connection fails) + * + * @return mixed false on error or a mysqli object on success + */ + public function connect($user, $password, $is_controluser = false, + $server = null, $auxiliary_connection = false + ) { + global $cfg; + + if ($server) { + $server_port = (empty($server['port'])) + ? false + : (int)$server['port']; + $server_socket = (empty($server['socket'])) + ? '' + : $server['socket']; + $server['host'] = (empty($server['host'])) + ? 'localhost' + : $server['host']; + } else { + $server_port = (empty($cfg['Server']['port'])) + ? false + : (int) $cfg['Server']['port']; + $server_socket = (empty($cfg['Server']['socket'])) + ? null + : $cfg['Server']['socket']; + } + + if (strtolower($GLOBALS['cfg']['Server']['connect_type']) == 'tcp') { + $GLOBALS['cfg']['Server']['socket'] = ''; + } + + $drizzle = new PMA_Drizzle(); + + $client_flags = 0; + + /* Optionally compress connection */ + if ($GLOBALS['cfg']['Server']['compress']) { + $client_flags |= DRIZZLE_CAPABILITIES_COMPRESS; + } + + /* Optionally enable SSL */ + if ($GLOBALS['cfg']['Server']['ssl']) { + $client_flags |= DRIZZLE_CAPABILITIES_SSL; + } + + if (! $server) { + $link = @$this->_realConnect( + $drizzle, $cfg['Server']['host'], + $server_port, $server_socket, $user, + $password, false, $client_flags + ); + // Retry with empty password if we're allowed to + if ($link == false && isset($cfg['Server']['nopassword']) + && $cfg['Server']['nopassword'] && ! $is_controluser + ) { + $link = @$this->_realConnect( + $drizzle, $cfg['Server']['host'], $server_port, $server_socket, + $user, null, false, $client_flags + ); + } + } else { + $link = @$this->_realConnect( + $drizzle, $server['host'], $server_port, $server_socket, + $user, $password + ); + } + + if ($link == false) { + if ($is_controluser) { + trigger_error( + __( + 'Connection for controluser as defined' + . ' in your configuration failed.' + ), + E_USER_WARNING + ); + return false; + } + // we could be calling $GLOBALS['dbi']->connect() to connect to another + // server, for example in the Synchronize feature, so do not + // go back to main login if it fails + if (! $auxiliary_connection) { + PMA_logUser($user, 'drizzle-denied'); + global $auth_plugin; + $auth_plugin->authFails(); + } else { + return false; + } + } else { + $GLOBALS['dbi']->postConnect($link, $is_controluser); + } + + return $link; + } + + /** + * selects given database + * + * @param string $dbname database name to select + * @param PMA_DrizzleCom $link connection object + * + * @return bool + */ + public function selectDb($dbname, $link = null) + { + if (empty($link)) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + return $link->selectDb($dbname); + } + + /** + * runs a query and returns the result + * + * @param string $query query to execute + * @param PMA_DrizzleCon $link connection object + * @param int $options query options + * + * @return PMA_DrizzleResult + */ + public function realQuery($query, $link, $options) + { + $buffer_mode = $options & PMA_DatabaseInterface::QUERY_UNBUFFERED + ? PMA_Drizzle::BUFFER_ROW + : PMA_Drizzle::BUFFER_RESULT; + $res = $link->query($query, $buffer_mode); + return $res; + } + + /** + * Run the multi query and output the results + * + * @param object $link connection object + * @param string $query multi query statement to execute + * + * @return result collection | boolean(false) + */ + public function realMultiQuery($link, $query) + { + return false; + } + + /** + * returns array of rows with associative and numeric keys from $result + * + * @param PMA_DrizzleResult $result Drizzle result object + * + * @return array + */ + public function fetchArray($result) + { + return $result->fetchRow(PMA_Drizzle::FETCH_BOTH); + } + + /** + * returns array of rows with associative keys from $result + * + * @param PMA_DrizzleResult $result Drizzle result object + * + * @return array + */ + public function fetchAssoc($result) + { + return $result->fetchRow(PMA_Drizzle::FETCH_ASSOC); + } + + /** + * returns array of rows with numeric keys from $result + * + * @param PMA_DrizzleResult $result Drizzle result object + * + * @return array + */ + public function fetchRow($result) + { + return $result->fetchRow(PMA_Drizzle::FETCH_NUM); + } + + /** + * Adjusts the result pointer to an arbitrary row in the result + * + * @param PMA_DrizzleResult $result Drizzle result object + * @param int $offset offset to seek + * + * @return boolean true on success, false on failure + */ + public function dataSeek($result, $offset) + { + return $result->seek($offset); + } + + /** + * Frees memory associated with the result + * + * @param PMA_DrizzleResult $result database result + * + * @return void + */ + public function freeResult($result) + { + if ($result instanceof PMA_DrizzleResult) { + $result->free(); + } + } + + /** + * Check if there are any more query results from a multi query + * + * @param object $link the connection object + * + * @return bool false + */ + public function moreResults($link = null) + { + // 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 object $link the connection object + * + * @return bool false + */ + public function nextResult($link = null) + { + // 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 PMA_DrizzleCon $link connection object + * + * @return string type of connection used + */ + public function getHostInfo($link = null) + { + if (null === $link) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + + $str = $link->port() + ? $link->host() . ':' . $link->port() . ' via TCP/IP' + : 'Localhost via UNIX socket'; + return $str; + } + + /** + * Returns the version of the Drizzle protocol used + * + * @param PMA_DrizzleCon $link connection object + * + * @return int version of the Drizzle protocol used + */ + public function getProtoInfo($link = null) + { + if (null === $link) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + + return $link->protocolVersion(); + } + + /** + * returns a string that represents the client library version + * + * @return string Drizzle client library version + */ + public function getClientInfo() + { + return 'libdrizzle (Drizzle ' . drizzle_version() . ')'; + } + + /** + * returns last error message or false if no errors occurred + * + * @param PMA_DrizzleCon $link connection object + * + * @return string|bool $error or false + */ + public function getError($link = null) + { + $GLOBALS['errno'] = 0; + + /* Treat false same as null because of controllink */ + if ($link === false) { + $link = null; + } + + if (null === $link && isset($GLOBALS['userlink'])) { + $link =& $GLOBALS['userlink']; + // Do not stop now. We still can get the error code + // with mysqli_connect_errno() + // } else { + // return false; + } + + if (null !== $link) { + $error_number = drizzle_con_errno($link->getConnectionObject()); + $error_message = drizzle_con_error($link->getConnectionObject()); + } else { + $error_number = drizzle_errno(); + $error_message = drizzle_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 PMA_DrizzleResult $result Drizzle result object + * + * @return string|int + */ + public function numRows($result) + { + // see the note for $GLOBALS['dbi']->tryQuery(); + if (!is_bool($result)) { + return @$result->numRows(); + } else { + return 0; + } + } + + /** + * returns last inserted auto_increment id for given $link + * or $GLOBALS['userlink'] + * + * @param PMA_DrizzleCon $link connection object + * + * @return string|int + */ + public function insertId($link = null) + { + if (empty($link)) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + + // copied from mysql and mysqli + + // 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 $GLOBALS['dbi']->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link); + // Curiously, this problem does not happen with the mysql extension but + // there is another problem with BIGINT primary keys so insertId() + // in the mysql extension also uses this logic. + } + + /** + * returns the number of rows affected by last query + * + * @param PMA_DrizzleResult $link connection object + * @param bool $get_from_cache whether to retrieve from cache + * + * @return string|int + */ + public function affectedRows($link = null, $get_from_cache = true) + { + if (empty($link)) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + if ($get_from_cache) { + return $GLOBALS['cached_affected_rows']; + } else { + return $link->affectedRows(); + } + } + + /** + * returns metainfo for fields in $result + * + * @param PMA_DrizzleResult $result Drizzle result object + * + * @return array meta info for fields in $result + */ + public function getFieldsMeta($result) + { + // Build an associative array for a type look up + $typeAr = array(); + /*$typeAr[DRIZZLE_COLUMN_TYPE_DECIMAL] = 'real'; + $typeAr[DRIZZLE_COLUMN_TYPE_NEWDECIMAL] = 'real'; + $typeAr[DRIZZLE_COLUMN_TYPE_BIT] = 'int'; + $typeAr[DRIZZLE_COLUMN_TYPE_TINY] = 'int'; + $typeAr[DRIZZLE_COLUMN_TYPE_SHORT] = 'int'; + $typeAr[DRIZZLE_COLUMN_TYPE_LONG] = 'int'; + $typeAr[DRIZZLE_COLUMN_TYPE_FLOAT] = 'real'; + $typeAr[DRIZZLE_COLUMN_TYPE_DOUBLE] = 'real'; + $typeAr[DRIZZLE_COLUMN_TYPE_NULL] = 'null'; + $typeAr[DRIZZLE_COLUMN_TYPE_TIMESTAMP] = 'timestamp'; + $typeAr[DRIZZLE_COLUMN_TYPE_LONGLONG] = 'int'; + $typeAr[DRIZZLE_COLUMN_TYPE_INT24] = 'int'; + $typeAr[DRIZZLE_COLUMN_TYPE_DATE] = 'date'; + $typeAr[DRIZZLE_COLUMN_TYPE_TIME] = 'date'; + $typeAr[DRIZZLE_COLUMN_TYPE_DATETIME] = 'datetime'; + $typeAr[DRIZZLE_COLUMN_TYPE_YEAR] = 'year'; + $typeAr[DRIZZLE_COLUMN_TYPE_NEWDATE] = 'date'; + $typeAr[DRIZZLE_COLUMN_TYPE_ENUM] = 'unknown'; + $typeAr[DRIZZLE_COLUMN_TYPE_SET] = 'unknown'; + $typeAr[DRIZZLE_COLUMN_TYPE_VIRTUAL] = 'unknown'; + $typeAr[DRIZZLE_COLUMN_TYPE_TINY_BLOB] = 'blob'; + $typeAr[DRIZZLE_COLUMN_TYPE_MEDIUM_BLOB] = 'blob'; + $typeAr[DRIZZLE_COLUMN_TYPE_LONG_BLOB] = 'blob'; + $typeAr[DRIZZLE_COLUMN_TYPE_BLOB] = 'blob'; + $typeAr[DRIZZLE_COLUMN_TYPE_VAR_STRING] = 'string'; + $typeAr[DRIZZLE_COLUMN_TYPE_VARCHAR] = 'string'; + $typeAr[DRIZZLE_COLUMN_TYPE_STRING] = 'string'; + $typeAr[DRIZZLE_COLUMN_TYPE_GEOMETRY] = 'geometry';*/ + + $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_BLOB] = 'blob'; + $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_DATE] = 'date'; + $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_DATETIME] = 'datetime'; + $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_DOUBLE] = 'real'; + $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_ENUM] = 'unknown'; + $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_LONG] = 'int'; + $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_LONGLONG] = 'int'; + $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_MAX] = 'unknown'; + $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_NULL] = 'null'; + $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_TIMESTAMP] = 'timestamp'; + $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_TINY] = 'int'; + $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_VARCHAR] = 'string'; + + // array of DrizzleColumn + $columns = $result->getColumns(); + // columns in a standarized format + $std_columns = array(); + + foreach ($columns as $k => $column) { + $c = new stdClass(); + $c->name = $column->name(); + $c->orgname = $column->origName(); + $c->table = $column->table(); + $c->orgtable = $column->origTable(); + $c->def = $column->defaultValue(); + $c->db = $column->db(); + $c->catalog = $column->catalog(); + // $column->maxSize() returns always 0 while size() seems + // to return a correct value (drizzle extension v.0.5, API v.7) + $c->max_length = $column->size(); + $c->decimals = $column->decimals(); + $c->charsetnr = $column->charset(); + $c->type = $typeAr[$column->typeDrizzle()]; + $c->_type = $column->type(); + $c->flags = $this->fieldFlags($result, $k); + $c->_flags = $column->flags(); + + $c->multiple_key = (int) (bool) ( + $c->_flags & DRIZZLE_COLUMN_FLAGS_MULTIPLE_KEY + ); + $c->primary_key = (int) (bool) ( + $c->_flags & DRIZZLE_COLUMN_FLAGS_PRI_KEY + ); + $c->unique_key = (int) (bool) ( + $c->_flags & DRIZZLE_COLUMN_FLAGS_UNIQUE_KEY + ); + $c->not_null = (int) (bool) ( + $c->_flags & DRIZZLE_COLUMN_FLAGS_NOT_NULL + ); + $c->unsigned = (int) (bool) ( + $c->_flags & DRIZZLE_COLUMN_FLAGS_UNSIGNED + ); + $c->zerofill = (int) (bool) ( + $c->_flags & DRIZZLE_COLUMN_FLAGS_ZEROFILL + ); + $c->numeric = (int) (bool) ( + $c->_flags & DRIZZLE_COLUMN_FLAGS_NUM + ); + $c->blob = (int) (bool) ( + $c->_flags & DRIZZLE_COLUMN_FLAGS_BLOB + ); + + $std_columns[] = $c; + } + + return $std_columns; + } + + /** + * return number of fields in given $result + * + * @param PMA_DrizzleResult $result Drizzle result object + * + * @return int field count + */ + public function numFields($result) + { + return $result->numColumns(); + } + + /** + * returns the length of the given field $i in $result + * + * @param PMA_DrizzleResult $result Drizzle result object + * @param int $i field + * + * @return int length of field + */ + public function fieldLen($result, $i) + { + $colums = $result->getColumns(); + return $colums[$i]->size(); + } + + /** + * returns name of $i. field in $result + * + * @param PMA_DrizzleResult $result Drizzle result object + * @param int $i field + * + * @return string name of $i. field in $result + */ + public function fieldName($result, $i) + { + $colums = $result->getColumns(); + return $colums[$i]->name(); + } + + /** + * returns concatenated string of human readable field flags + * + * @param PMA_DrizzleResult $result Drizzle result object + * @param int $i field + * + * @return string field flags + */ + public function fieldFlags($result, $i) + { + $columns = $result->getColumns(); + $f = $columns[$i]; + $type = $f->typeDrizzle(); + $charsetnr = $f->charset(); + $f = $f->flags(); + $flags = ''; + if ($f & DRIZZLE_COLUMN_FLAGS_UNIQUE_KEY) { + $flags .= 'unique '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_NUM) { + $flags .= 'num '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_PART_KEY) { + $flags .= 'part_key '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_SET) { + $flags .= 'set '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_TIMESTAMP) { + $flags .= 'timestamp '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_AUTO_INCREMENT) { + $flags .= 'auto_increment '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_ENUM) { + $flags .= 'enum '; + } + // See http://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 == DRIZZLE_COLUMN_TYPE_DRIZZLE_BLOB + || $type == DRIZZLE_COLUMN_TYPE_DRIZZLE_VARCHAR) + && 63 == $charsetnr + ) { + $flags .= 'binary '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_ZEROFILL) { + $flags .= 'zerofill '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_UNSIGNED) { + $flags .= 'unsigned '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_BLOB) { + $flags .= 'blob '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_MULTIPLE_KEY) { + $flags .= 'multiple_key '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_UNIQUE_KEY) { + $flags .= 'unique_key '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_PRI_KEY) { + $flags .= 'primary_key '; + } + if ($f & DRIZZLE_COLUMN_FLAGS_NOT_NULL) { + $flags .= 'not_null '; + } + return trim($flags); + } + + /** + * Store the result returned from multi query + * + * @return false + */ + public function storeResult() + { + return false; + } +} +?> diff --git a/sources/libraries/dbi/DBIDummy.class.php b/sources/libraries/dbi/DBIDummy.class.php new file mode 100644 index 0000000..c9d2eed --- /dev/null +++ b/sources/libraries/dbi/DBIDummy.class.php @@ -0,0 +1,869 @@ + 'SELECT 1', 'result' => array(array('1'))), + array( + 'query' => 'SELECT CURRENT_USER();', + 'result' => array(array('pma_test@localhost')), + ), + array( + 'query' => 'SELECT COUNT(*) FROM mysql.user', + 'result' => false, + ), + array( + 'query' => 'SHOW MASTER LOGS', + 'result' => false, + ), + array( + 'query' => 'SHOW STORAGE ENGINES', + 'result' => array( + array( + 'Engine' => 'dummy', + 'Support' => 'YES', + 'Comment' => 'dummy comment' + ), + array( + 'Engine' => 'dummy2', + 'Support' => 'NO', + 'Comment' => 'dummy2 comment' + ), + array( + 'Engine' => 'FEDERATED', + 'Support' => 'NO', + 'Comment' => 'Federated MySQL storage engine' + ), + ) + ), + array( + 'query' => 'SHOW STATUS WHERE Variable_name' + . ' LIKE \'Innodb\\_buffer\\_pool\\_%\'' + . ' OR Variable_name = \'Innodb_page_size\';', + 'result' => array( + array('Innodb_buffer_pool_pages_data', 0), + array('Innodb_buffer_pool_pages_dirty', 0), + array('Innodb_buffer_pool_pages_flushed', 0), + array('Innodb_buffer_pool_pages_free', 0), + array('Innodb_buffer_pool_pages_misc', 0), + array('Innodb_buffer_pool_pages_total', 4096), + array('Innodb_buffer_pool_read_ahead_rnd', 0), + array('Innodb_buffer_pool_read_ahead', 0), + array('Innodb_buffer_pool_read_ahead_evicted', 0), + array('Innodb_buffer_pool_read_requests', 64), + array('Innodb_buffer_pool_reads', 32), + array('Innodb_buffer_pool_wait_free', 0), + array('Innodb_buffer_pool_write_requests', 64), + array('Innodb_page_size', 16384), + ) + ), + array( + 'query' => 'SHOW INNODB STATUS;', + 'result' => false, + ), + array( + 'query' => 'SELECT @@innodb_version;', + 'result' => array( + array('1.1.8'), + ) + ), + array( + 'query' => 'SHOW GLOBAL VARIABLES LIKE \'innodb_file_per_table\';', + 'result' => array( + array('innodb_file_per_table', 'OFF'), + ) + ), + array( + 'query' => 'SHOW GLOBAL VARIABLES LIKE \'innodb_file_format\';', + 'result' => array( + array('innodb_file_format', 'Antelope'), + ) + ), + array( + 'query' => 'SHOW VARIABLES LIKE \'collation_server\'', + 'result' => array( + array('collation_server', 'utf8_general_ci'), + ) + ), + array( + 'query' => 'SHOW VARIABLES LIKE \'language\';', + 'result' => array(), + ), + array( + 'query' => 'SHOW TABLES FROM `pma_test`;', + 'result' => array( + array('table1'), + array('table2'), + ) + ), + array( + 'query' => 'SHOW TABLES FROM `pmadb`', + 'result' => array( + array('column_info'), + ) + ), + array( + 'query' => 'SHOW COLUMNS FROM `pma_test`.`table1`', + 'columns' => array( + 'Field', 'Type', 'Null', 'Key', 'Default', 'Extra' + ), + 'result' => array( + array('i', 'int(11)', 'NO', 'PRI', 'NULL', 'auto_increment'), + array('o', 'int(11)', 'NO', 'MUL', 'NULL', ''), + ) + ), + array( + 'query' => 'SHOW INDEXES FROM `pma_test`.`table1` WHERE (Non_unique = 0)', + 'result' => array(), + ), + array( + 'query' => 'SHOW COLUMNS FROM `pma_test`.`table2`', + 'columns' => array( + 'Field', 'Type', 'Null', 'Key', 'Default', 'Extra' + ), + 'result' => array( + array('i', 'int(11)', 'NO', 'PRI', 'NULL', 'auto_increment'), + array('o', 'int(11)', 'NO', 'MUL', 'NULL', ''), + ) + ), + array( + 'query' => 'SHOW INDEXES FROM `pma_test`.`table1`', + 'result' => array(), + ), + array( + 'query' => 'SHOW INDEXES FROM `pma_test`.`table2`', + 'result' => array(), + ), + array( + 'query' => 'SHOW COLUMNS FROM `pma`.`table1`', + 'columns' => array( + 'Field', 'Type', 'Null', 'Key', 'Default', 'Extra', + 'Privileges', 'Comment' + ), + 'result' => array( + array( + 'i', 'int(11)', 'NO', 'PRI', 'NULL', 'auto_increment', + 'select,insert,update,references', '' + ), + array( + 'o', 'varchar(100)', 'NO', 'MUL', 'NULL', '', + 'select,insert,update,references', '' + ), + ) + ), + array( + 'query' => 'SELECT * FROM information_schema.CHARACTER_SETS', + 'columns' => array( + 'CHARACTER_SET_NAME', 'DEFAULT_COLLATE_NAME', 'DESCRIPTION', 'MAXLEN' + ), + 'result' => array( + array('utf8', 'utf8_general_ci', 'UTF-8 Unicode', 3), + ) + ), + array( + 'query' => 'SELECT * FROM information_schema.COLLATIONS', + 'columns' => array( + 'COLLATION_NAME', 'CHARACTER_SET_NAME', 'ID', 'IS_DEFAULT', + 'IS_COMPILED', 'SORTLEN' + ), + 'result' => array( + array('utf8_general_ci', 'utf8', 33, 'Yes', 'Yes', 1), + array('utf8_bin', 'utf8', 83, '', 'Yes', 1), + ) + ), + array( + 'query' => 'SELECT `TABLE_NAME` FROM `INFORMATION_SCHEMA`.`TABLES`' + . ' WHERE `TABLE_SCHEMA`=\'pma_test\' AND `TABLE_TYPE`=\'BASE TABLE\'', + 'result' => array(), + ), + array( + 'query' => 'SELECT upper(plugin_name) f FROM data_dictionary.plugins' + . ' WHERE plugin_name IN (\'MYSQL_PASSWORD\',\'ROT13\')' + . ' AND plugin_type = \'Function\' AND is_active', + 'columns' => array('f'), + 'result' => array(array('ROT13')), + ), + array( + 'query' => 'SELECT `column_name`, `mimetype`, `transformation`,' + . ' `transformation_options` FROM `pmadb`.`column_info`' + . ' WHERE `db_name` = \'pma_test\' AND `table_name` = \'table1\'' + . ' AND ( `mimetype` != \'\' OR `transformation` != \'\'' + . ' OR `transformation_options` != \'\')', + 'columns' => array( + 'column_name', 'mimetype', 'transformation', 'transformation_options' + ), + 'result' => array( + array('o', 'text/plain', 'sql'), + ) + ), + array( + 'query' => 'SELECT TABLE_NAME FROM information_schema.VIEWS' + . ' WHERE TABLE_SCHEMA = \'pma_test\' AND TABLE_NAME = \'table1\'', + 'result' => array(), + ), + array( + 'query' => '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 BINARY `TABLE_SCHEMA` IN (\'pma_test\')' + . ' AND t.`TABLE_NAME` = \'table1\' ORDER BY Name ASC', + 'columns' => array( + 'TABLE_CATALOG', 'TABLE_SCHEMA', 'TABLE_NAME', 'TABLE_TYPE', 'ENGINE', + 'VERSION', 'ROW_FORMAT', 'TABLE_ROWS', 'AVG_ROW_LENGTH', 'DATA_LENGTH', + 'MAX_DATA_LENGTH', 'INDEX_LENGTH', 'DATA_FREE', 'AUTO_INCREMENT', + 'CREATE_TIME', 'UPDATE_TIME', 'CHECK_TIME', 'TABLE_COLLATION', + 'CHECKSUM', 'CREATE_OPTIONS', 'TABLE_COMMENT', 'Db', 'Name', + 'TABLE_TYPE', 'Engine', 'Type', 'Version', 'Row_format', 'Rows', + 'Avg_row_length', 'Data_length', 'Max_data_length', 'Index_length', + 'Data_free', 'Auto_increment', 'Create_time', 'Update_time', + 'Check_time', 'Collation', 'Checksum', 'Create_options', 'Comment' + ), + 'result' => array( + array( + 'def', 'smash', 'issues_issue', 'BASE TABLE', 'InnoDB', '10', + 'Compact', '9136', '862', '7880704', '0', '1032192', '420478976', + '155862', '2012-08-29 13:28:28', 'NULL', 'NULL', 'utf8_general_ci', + 'NULL', '', '', 'smash', 'issues_issue', 'BASE TABLE', 'InnoDB', + 'InnoDB', '10', 'Compact', '9136', '862', '7880704', '0', '1032192', + '420478976', '155862', '2012-08-29 13:28:28', 'NULL', 'NULL', + 'utf8_general_ci', 'NULL' + ), + ), + ), + array( + 'query' => '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` IN (\'pma_test\')' + . ' AND t.`TABLE_NAME` = \'table1\' ORDER BY Name ASC', + 'columns' => array('TABLE_CATALOG', 'TABLE_SCHEMA', 'TABLE_NAME', + 'TABLE_TYPE', 'ENGINE', 'VERSION', 'ROW_FORMAT', 'TABLE_ROWS', + 'AVG_ROW_LENGTH', 'DATA_LENGTH', 'MAX_DATA_LENGTH', + 'INDEX_LENGTH', 'DATA_FREE', 'AUTO_INCREMENT', 'CREATE_TIME', + 'UPDATE_TIME', 'CHECK_TIME', 'TABLE_COLLATION', 'CHECKSUM', + 'CREATE_OPTIONS', 'TABLE_COMMENT', 'Db', 'Name', 'TABLE_TYPE', + 'Engine', 'Type', 'Version', 'Row_format', 'Rows', + 'Avg_row_length', 'Data_length', 'Max_data_length', + 'Index_length', 'Data_free', 'Auto_increment', 'Create_time', + 'Update_time', 'Check_time', 'Collation', 'Checksum', + 'Create_options', 'Comment'), + 'result' => array( + array('def', 'smash', 'issues_issue', 'BASE TABLE', 'InnoDB', '10', + 'Compact', '9136', '862', '7880704', '0', '1032192', + '420478976', '155862', '2012-08-29 13:28:28', 'NULL', 'NULL', + 'utf8_general_ci', 'NULL', '', '', 'smash', 'issues_issue', + 'BASE TABLE', 'InnoDB', 'InnoDB', '10', 'Compact', '9136', + '862', '7880704', '0', '1032192', '420478976', '155862', + '2012-08-29 13:28:28', 'NULL', 'NULL', 'utf8_general_ci', + 'NULL'), + ), + ), + array( + 'query' => 'SELECT COUNT(*) FROM `pma_test`.`table1`', + 'result' => array(array(0)), + ), + array( + 'query' => 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.' + . '`USER_PRIVILEGES`' + . ' WHERE GRANTEE=\'\'\'pma_test\'\'@\'\'localhost\'\'\'' + . ' AND PRIVILEGE_TYPE=\'TRIGGER\'', + 'result' => array(), + ), + array( + 'query' => 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.' + . '`SCHEMA_PRIVILEGES`' + . ' WHERE GRANTEE=\'\'\'pma_test\'\'@\'\'localhost\'\'\'' + . ' AND PRIVILEGE_TYPE=\'TRIGGER\' AND \'pma\\\\_test\'' + . ' REGEXP REPLACE(REPLACE(TABLE_SCHEMA, \'_\', \'.\'), \'%\', \'.*\')', + 'result' => array(), + ), + array( + 'query' => 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.' + . '`TABLE_PRIVILEGES`' + . ' WHERE GRANTEE=\'\'\'pma_test\'\'@\'\'localhost\'\'\'' + . ' AND PRIVILEGE_TYPE=\'TRIGGER\' AND \'pma\\\\_test\'' + . ' REGEXP REPLACE(REPLACE(TABLE_SCHEMA, \'_\', \'.\'), \'%\', \'.*\')' + . ' AND TABLE_NAME=\'table1\'', + 'result' => array(), + ), + array( + 'query' => 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.' + . '`USER_PRIVILEGES`' + . ' WHERE GRANTEE=\'\'\'pma_test\'\'@\'\'localhost\'\'\'' + . ' AND PRIVILEGE_TYPE=\'EVENT\'', + 'result' => array(), + ), + array( + 'query' => 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.' + . '`SCHEMA_PRIVILEGES`' + . ' WHERE GRANTEE=\'\'\'pma_test\'\'@\'\'localhost\'\'\'' + . ' AND PRIVILEGE_TYPE=\'EVENT\' AND \'pma\\\\_test\'' + . ' REGEXP REPLACE(REPLACE(TABLE_SCHEMA, \'_\', \'.\'), \'%\', \'.*\')', + 'result' => array(), + ), + array( + 'query' => 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.' + . '`TABLE_PRIVILEGES`' + . ' WHERE GRANTEE=\'\'\'pma_test\'\'@\'\'localhost\'\'\'' + . ' AND PRIVILEGE_TYPE=\'EVENT\'' + . ' AND TABLE_SCHEMA=\'pma\\\\_test\' AND TABLE_NAME=\'table1\'', + 'result' => array(), + ), + array( + 'query' => 'RENAME TABLE `pma_test`.`table1` TO `pma_test`.`table3`;', + 'result' => array(), + ), + array( + '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 TRIGGER_SCHEMA= \'pma_test\'' + . ' AND EVENT_OBJECT_TABLE = \'table1\';', + 'result' => array(), + ), + array( + 'query' => 'SHOW TABLES FROM `pma`;', + 'result' => array(), + ), + array( + 'query' => "SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`." + . "`SCHEMA_PRIVILEGES` WHERE GRANTEE='''pma_test''@''localhost'''" + . " AND PRIVILEGE_TYPE='EVENT' AND TABLE_SCHEMA='pma'", + 'result' => array(), + ), + array( + 'query' => "SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`." + . "`SCHEMA_PRIVILEGES` WHERE GRANTEE='''pma_test''@''localhost'''" + . " AND PRIVILEGE_TYPE='TRIGGER' AND TABLE_SCHEMA='pma'", + 'result' => array(), + ), + array( + 'query' => 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA' + . ' WHERE SCHEMA_NAME = \'pma_test\' LIMIT 1', + 'columns' => array('DEFAULT_COLLATION_NAME'), + 'result' => array( + array('utf8_general_ci') + ) + ), + array( + 'query' => 'SELECT DEFAULT_COLLATION_NAME FROM data_dictionary.SCHEMAS' + . ' WHERE SCHEMA_NAME = \'pma_test\' LIMIT 1', + 'columns' => array('DEFAULT_COLLATION_NAME'), + 'result' => array( + array('utf8_general_ci_pma_drizzle') + ) + ), + array( + 'query' => 'SHOW VARIABLES LIKE \'collation_database\'', + 'columns' => array('variable_name', 'variable_value'), + 'result' => array( + array('foo', 'bar'), + ) + ), + array( + 'query' => "SHOW TABLES FROM `phpmyadmin`", + 'result' => array(), + ), + array( + 'query' => "SELECT tracking_active FROM pma_table_tracking" . + " WHERE db_name = 'pma_test_db'" . + " AND table_name = 'pma_test_table'" . + " ORDER BY version DESC", + 'columns' => array('tracking_active'), + 'result' => array( + array(1) + ) + ), + array( + 'query' => "SELECT tracking_active FROM pma_table_tracking" . + " WHERE db_name = 'pma_test_db'" . + " AND table_name = 'pma_test_table2'" . + " ORDER BY version DESC", + 'result' => array() + ), + array( + 'query' => "SHOW SLAVE STATUS", + 'result' => array( + array( + 'Slave_IO_State' => 'running', + 'Master_Host' => 'locahost', + 'Master_User' => 'Master_User', + 'Master_Port' => '1002', + 'Connect_Retry' => 'Connect_Retry', + 'Master_Log_File' => 'Master_Log_File', + 'Read_Master_Log_Pos' => 'Read_Master_Log_Pos', + 'Relay_Log_File' => 'Relay_Log_File', + 'Relay_Log_Pos' => 'Relay_Log_Pos', + 'Relay_Master_Log_File' => 'Relay_Master_Log_File', + 'Slave_IO_Running' => 'NO', + 'Slave_SQL_Running' => 'NO', + 'Replicate_Do_DB' => 'Replicate_Do_DB', + 'Replicate_Ignore_DB' => 'Replicate_Ignore_DB', + 'Replicate_Do_Table' => 'Replicate_Do_Table', + 'Replicate_Ignore_Table' => 'Replicate_Ignore_Table', + 'Replicate_Wild_Do_Table' => 'Replicate_Wild_Do_Table', + 'Replicate_Wild_Ignore_Table' => 'Replicate_Wild_Ignore_Table', + 'Last_Errno' => 'Last_Errno', + 'Last_Error' => 'Last_Error', + 'Skip_Counter' => 'Skip_Counter', + 'Exec_Master_Log_Pos' => 'Exec_Master_Log_Pos', + 'Relay_Log_Space' => 'Relay_Log_Space', + 'Until_Condition' => 'Until_Condition', + 'Until_Log_File' => 'Until_Log_File', + 'Until_Log_Pos' => 'Until_Log_Pos', + 'Master_SSL_Allowed' => 'Master_SSL_Allowed', + 'Master_SSL_CA_File' => 'Master_SSL_CA_File', + 'Master_SSL_CA_Path' => 'Master_SSL_CA_Path', + 'Master_SSL_Cert' => 'Master_SSL_Cert', + 'Master_SSL_Cipher' => 'Master_SSL_Cipher', + 'Master_SSL_Key' => 'Master_SSL_Key', + 'Seconds_Behind_Master' => 'Seconds_Behind_Master', + ) + ) + ), + array( + 'query' => "SHOW MASTER STATUS", + 'result' => array( + array( + "File" => "master-bin.000030", + "Position" => "107", + "Binlog_Do_DB" => "Binlog_Do_DB", + "Binlog_Ignore_DB" => "Binlog_Ignore_DB", + ) + ) + ), + array( + 'query' => "SHOW GRANTS", + 'result' => array() + ) +); +/** + * Current database. + */ +$GLOBALS['dummy_db'] = ''; + +/* Some basic setup for dummy driver */ +$GLOBALS['userlink'] = 1; +$GLOBALS['controllink'] = 2; +$GLOBALS['cfg']['DBG']['sql'] = false; +if (! defined('PMA_DRIZZLE')) { + define('PMA_DRIZZLE', 0); +} + +/** + * Fake database driver for testing purposes + * + * It has hardcoded results for given queries what makes easy to use it + * in testsuite. Feel free to include other queries which your test will + * need. + * + * @package PhpMyAdmin-DBI + * @subpackage Dummy + */ +class PMA_DBI_Dummy implements PMA_DBI_Extension +{ + /** + * connects to the database server + * + * @param string $user mysql user name + * @param string $password mysql user password + * @param bool $is_controluser whether this is a control user connection + * @param array $server host/port/socket/persistent + * @param bool $auxiliary_connection (when true, don't go back to login if + * connection fails) + * + * @return mixed false on error or a mysqli object on success + */ + public function connect( + $user, $password, $is_controluser = false, $server = null, + $auxiliary_connection = false + ) { + return true; + } + + /** + * selects given database + * + * @param string $dbname name of db to select + * @param resource $link mysql link resource + * + * @return bool + */ + public function selectDb($dbname, $link = null) + { + $GLOBALS['dummy_db'] = $dbname; + return true; + } + + /** + * runs a query and returns the result + * + * @param string $query query to run + * @param resource $link mysql link resource + * @param int $options query options + * + * @return mixed + */ + public function realQuery($query, $link = null, $options = 0) + { + $query = trim(preg_replace('/ */', ' ', str_replace("\n", ' ', $query))); + for ($i = 0; $i < count($GLOBALS['dummy_queries']); $i++) { + if ($GLOBALS['dummy_queries'][$i]['query'] == $query) { + $GLOBALS['dummy_queries'][$i]['pos'] = 0; + if (is_array($GLOBALS['dummy_queries'][$i]['result'])) { + return $i; + } else { + return false; + } + } + } + echo "Not supported query: $query\n"; + return false; + } + + /** + * Run the multi query and output the results + * + * @param object $link connection object + * @param string $query multi query statement to execute + * + * @return result collection | boolean(false) + */ + public function realMultiQuery($link, $query) + { + return false; + } + + /** + * returns result data from $result + * + * @param resource $result result MySQL result + * + * @return array + */ + public function fetchAny($result) + { + $query_data = $GLOBALS['dummy_queries'][$result]; + if ($query_data['pos'] >= count($query_data['result'])) { + return false; + } + $ret = $query_data['result'][$query_data['pos']]; + $GLOBALS['dummy_queries'][$result]['pos'] += 1; + return $ret; + } + + /** + * returns array of rows with associative and numeric keys from $result + * + * @param resource $result result MySQL result + * + * @return array + */ + public function fetchArray($result) + { + $data = $this->fetchAny($result); + if (is_array($data) + && isset($GLOBALS['dummy_queries'][$result]['columns']) + ) { + foreach ($data as $key => $val) { + $data[$GLOBALS['dummy_queries'][$result]['columns'][$key]] = $val; + } + return $data; + } + return $data; + } + + /** + * returns array of rows with associative keys from $result + * + * @param resource $result MySQL result + * + * @return array + */ + public function fetchAssoc($result) + { + $data = $this->fetchAny($result); + if (is_array($data) + && isset($GLOBALS['dummy_queries'][$result]['columns']) + ) { + $ret = array(); + foreach ($data as $key => $val) { + $ret[$GLOBALS['dummy_queries'][$result]['columns'][$key]] = $val; + } + return $ret; + } + return $data; + } + + /** + * returns array of rows with numeric keys from $result + * + * @param resource $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 resource $result database result + * @param integer $offset offset to seek + * + * @return bool true on success, false on failure + */ + public function dataSeek($result, $offset) + { + if ($offset > count($GLOBALS['dummy_queries'][$result]['result'])) { + return false; + } + $GLOBALS['dummy_queries'][$result]['pos'] = $offset; + return true; + } + + /** + * Frees memory associated with the result + * + * @param resource $result database result + * + * @return void + */ + public function freeResult($result) + { + return; + } + + /** + * Check if there are any more query results from a multi query + * + * @param object $link the connection object + * + * @return bool false + */ + public function moreResults($link = null) + { + return false; + } + + /** + * Prepare next result from multi_query + * + * @param object $link the connection object + * + * @return boolean false + */ + public function nextResult($link = null) + { + return false; + } + + /** + * Store the result returned from multi query + * + * @return mixed false when empty results / result set when not empty + */ + public function storeResult() + { + return false; + } + + /** + * Returns a string representing the type of connection used + * + * @param object $link mysql link + * + * @return string type of connection used + */ + public function getHostInfo($link = null) + { + return ''; + } + + /** + * Returns the version of the MySQL protocol used + * + * @param object $link mysql link + * + * @return integer version of the MySQL protocol used + */ + public function getProtoInfo($link = null) + { + 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 object $link connection link + * + * @return string|bool $error or false + */ + public function getError($link = null) + { + return false; + } + + /** + * 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 count($GLOBALS['dummy_queries'][$result]['result']); + } else { + return 0; + } + } + + /** + * returns last inserted auto_increment id for given $link + * or $GLOBALS['userlink'] + * + * @param object $link the connection object + * + * @return string|int + */ + public function insertId($link = null) + { + return -1; + } + + /** + * 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 resource $result MySQL result + * + * @return int field count + */ + public function numFields($result) + { + if (isset($GLOBALS['dummy_queries'][$result]['columns'])) { + return count($GLOBALS['dummy_queries'][$result]['columns']); + } else { + return 0; + } + } + + /** + * 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 ''; + } +} +?> diff --git a/sources/libraries/dbi/DBIExtension.int.php b/sources/libraries/dbi/DBIExtension.int.php new file mode 100644 index 0000000..0e6ec3f --- /dev/null +++ b/sources/libraries/dbi/DBIExtension.int.php @@ -0,0 +1,248 @@ + \ No newline at end of file diff --git a/sources/libraries/dbi/DBIMysql.class.php b/sources/libraries/dbi/DBIMysql.class.php new file mode 100644 index 0000000..d67c64b --- /dev/null +++ b/sources/libraries/dbi/DBIMysql.class.php @@ -0,0 +1,570 @@ +_realConnect( + $cfg['Server']['host'] . $server_port . $server_socket, + $user, $password, empty($client_flags) ? null : $client_flags + ); + + // Retry with empty password if we're allowed to + if (empty($link) && $cfg['Server']['nopassword'] && ! $is_controluser) { + $link = $this->_realConnect( + $cfg['Server']['host'] . $server_port . $server_socket, + $user, '', empty($client_flags) ? null : $client_flags + ); + } + } else { + if (!isset($server['host'])) { + $link = $this->_realConnect($server_socket, $user, $password, null); + } else { + $link = $this->_realConnect( + $server['host'] . $server_port . $server_socket, + $user, $password, null + ); + } + } + if (empty($link)) { + if ($is_controluser) { + trigger_error( + __( + 'Connection for controluser as defined' + . ' in your configuration failed.' + ), + E_USER_WARNING + ); + return false; + } + // we could be calling $GLOBALS['dbi']->connect() to connect to another + // server, for example in the Synchronize feature, so do not + // go back to main login if it fails + if (! $auxiliary_connection) { + PMA_logUser($user, 'mysql-denied'); + global $auth_plugin; + $auth_plugin->authFails(); + } else { + return false; + } + } // end if + if (! $server) { + $GLOBALS['dbi']->postConnect($link, $is_controluser); + } + return $link; + } + + /** + * selects given database + * + * @param string $dbname name of db to select + * @param resource $link mysql link resource + * + * @return bool + */ + public function selectDb($dbname, $link = null) + { + if (empty($link)) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + return mysql_select_db($dbname, $link); + } + + /** + * runs a query and returns the result + * + * @param string $query query to run + * @param resource $link mysql link resource + * @param int $options query options + * + * @return mixed + */ + public function realQuery($query, $link, $options) + { + if ($options == ($options | PMA_DatabaseInterface::QUERY_STORE)) { + return mysql_query($query, $link); + } elseif ($options == ($options | PMA_DatabaseInterface::QUERY_UNBUFFERED)) { + return mysql_unbuffered_query($query, $link); + } else { + 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 object $link the connection object + * + * @return bool false + */ + public function moreResults($link = null) + { + // 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 object $link the connection object + * + * @return boolean false + */ + public function nextResult($link = null) + { + // 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 $link mysql link + * + * @return string type of connection used + */ + public function getHostInfo($link = null) + { + if (null === $link) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + return mysql_get_host_info($link); + } + + /** + * Returns the version of the MySQL protocol used + * + * @param resource $link mysql link + * + * @return int version of the MySQL protocol used + */ + public function getProtoInfo($link = null) + { + if (null === $link) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + 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 $link mysql link + * + * @return string|bool $error or false + */ + public function getError($link = null) + { + $GLOBALS['errno'] = 0; + + /* Treat false same as null because of controllink */ + if ($link === false) { + $link = null; + } + + if (null === $link && isset($GLOBALS['userlink'])) { + $link =& $GLOBALS['userlink']; + + // Do not stop now. On the initial connection, we don't have a $link, + // we don't have a $GLOBALS['userlink'], but we can catch the error code + // } else { + // return false; + } + + 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 mysql_num_rows($result); + } else { + return 0; + } + } + + /** + * returns last inserted auto_increment id for given $link + * or $GLOBALS['userlink'] + * + * @param resource $link the mysql object + * + * @return string|int + */ + public function insertId($link = null) + { + if (empty($link)) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + // 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() + // + return $GLOBALS['dbi']->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link); + } + + /** + * 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) + { + if (empty($link)) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + + if ($get_from_cache) { + return $GLOBALS['cached_affected_rows']; + } else { + 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 + * + * @return false + */ + public function storeResult() + { + return false; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/dbi/DBIMysqli.class.php b/sources/libraries/dbi/DBIMysqli.class.php new file mode 100644 index 0000000..31a389c --- /dev/null +++ b/sources/libraries/dbi/DBIMysqli.class.php @@ -0,0 +1,764 @@ +_realConnect( + $link, + $cfg['Server']['host'], + $user, + $password, + false, + $server_port, + $server_socket, + $client_flags + ); + // Retry with empty password if we're allowed to + if ($return_value == false + && isset($cfg['Server']['nopassword']) + && $cfg['Server']['nopassword'] + && ! $is_controluser + ) { + $return_value = @$this->_realConnect( + $link, + $cfg['Server']['host'], + $user, + '', + false, + $server_port, + $server_socket, + $client_flags + ); + } + } else { + $return_value = @$this->_realConnect( + $link, + $server['host'], + $user, + $password, + false, + $server_port, + $server_socket + ); + } + + if ($return_value == false) { + if ($is_controluser) { + trigger_error( + __('Connection for controluser as defined in your configuration failed.'), + E_USER_WARNING + ); + return false; + } + // we could be calling $GLOBALS['dbi']->connect() to connect to another + // server, for example in the Synchronize feature, so do not + // go back to main login if it fails + if (! $auxiliary_connection) { + PMA_logUser($user, 'mysql-denied'); + global $auth_plugin; + $auth_plugin->authFails(); + } else { + return false; + } + } else { + $GLOBALS['dbi']->postConnect($link, $is_controluser); + } + + 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 = null) + { + if (empty($link)) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + 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 | PMA_DatabaseInterface::QUERY_STORE)) { + $method = MYSQLI_STORE_RESULT; + } elseif ($options == ($options | PMA_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 resource $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 = null) + { + if (empty($link)) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + 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 = null) + { + if (empty($link)) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + return mysqli_next_result($link); + } + + /** + * Store the result returned from multi query + * + * @return mixed false when empty results / result set when not empty + */ + public function storeResult() + { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + 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 = null) + { + if (null === $link) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + 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 = null) + { + if (null === $link) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + 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 = null) + { + $GLOBALS['errno'] = 0; + + /* Treat false same as null because of controllink */ + if ($link === false) { + $link = null; + } + + if (null === $link && isset($GLOBALS['userlink'])) { + $link =& $GLOBALS['userlink']; + // Do not stop now. We still can get the error code + // with mysqli_connect_errno() + } + + if (null !== $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 @mysqli_num_rows($result); + } else { + return 0; + } + } + + /** + * returns last inserted auto_increment id for given $link + * or $GLOBALS['userlink'] + * + * @param mysqli $link the mysqli object + * + * @return string|int + */ + public function insertId($link = null) + { + if (empty($link)) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + // 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 $GLOBALS['dbi']->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link); + // Curiously, this problem does not happen with the mysql extension but + // there is another problem with BIGINT primary keys so insertId() + // in the mysql extension also uses this logic. + } + + /** + * returns the number of rows affected by last query + * + * @param mysqli $link the mysqli object + * @param bool $get_from_cache whether to retrieve from cache + * + * @return string|int + */ + public function affectedRows($link = null, $get_from_cache = true) + { + if (empty($link)) { + if (isset($GLOBALS['userlink'])) { + $link = $GLOBALS['userlink']; + } else { + return false; + } + } + if ($get_from_cache) { + return $GLOBALS['cached_affected_rows']; + } else { + 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'; + $typeAr[MYSQLI_TYPE_VARCHAR] = 'string'; // for Drizzle + // 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 + // https://sourceforge.net/p/phpmyadmin/bugs/2205/ + //$typeAr[MYSQLI_TYPE_CHAR] = 'string'; + $typeAr[MYSQLI_TYPE_GEOMETRY] = 'geometry'; + $typeAr[MYSQLI_TYPE_BIT] = 'bit'; + + $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 compatibilty + //$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) + { + 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) + { + 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) + { + $f = mysqli_fetch_field_direct($result, $i); + $type = $f->type; + $charsetnr = $f->charsetnr; + $f = $f->flags; + $flags = ''; + if ($f & MYSQLI_UNIQUE_KEY_FLAG) { + $flags .= 'unique '; + } + if ($f & MYSQLI_NUM_FLAG) { + $flags .= 'num '; + } + if ($f & MYSQLI_PART_KEY_FLAG) { + $flags .= 'part_key '; + } + if ($f & MYSQLI_SET_FLAG) { + $flags .= 'set '; + } + if ($f & MYSQLI_TIMESTAMP_FLAG) { + $flags .= 'timestamp '; + } + if ($f & MYSQLI_AUTO_INCREMENT_FLAG) { + $flags .= 'auto_increment '; + } + if ($f & MYSQLI_ENUM_FLAG) { + $flags .= 'enum '; + } + // See http://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 '; + } + if ($f & MYSQLI_ZEROFILL_FLAG) { + $flags .= 'zerofill '; + } + if ($f & MYSQLI_UNSIGNED_FLAG) { + $flags .= 'unsigned '; + } + if ($f & MYSQLI_BLOB_FLAG) { + $flags .= 'blob '; + } + if ($f & MYSQLI_MULTIPLE_KEY_FLAG) { + $flags .= 'multiple_key '; + } + if ($f & MYSQLI_UNIQUE_KEY_FLAG) { + $flags .= 'unique_key '; + } + if ($f & MYSQLI_PRI_KEY_FLAG) { + $flags .= 'primary_key '; + } + if ($f & MYSQLI_NOT_NULL_FLAG) { + $flags .= 'not_null '; + } + return trim($flags); + } +} +?> diff --git a/sources/libraries/dbi/drizzle-wrappers.lib.php b/sources/libraries/dbi/drizzle-wrappers.lib.php new file mode 100644 index 0000000..a712e7d --- /dev/null +++ b/sources/libraries/dbi/drizzle-wrappers.lib.php @@ -0,0 +1,435 @@ +_dcon = $dcon; + } + + /** + * Executes given query. Opens database connection if not already done. + * + * @param string $query query to execute + * @param int $bufferMode PMA_Drizzle::BUFFER_RESULT,PMA_Drizzle::BUFFER_ROW + * @param int $fetchMode PMA_Drizzle::FETCH_ASSOC, PMA_Drizzle::FETCH_NUM + * or PMA_Drizzle::FETCH_BOTH + * + * @return PMA_DrizzleResult + */ + public function query($query, $bufferMode = PMA_Drizzle::BUFFER_RESULT, + $fetchMode = PMA_Drizzle::FETCH_ASSOC + ) { + $result = $this->_dcon->query($query); + if ($result instanceof DrizzleResult) { + $this->_lastResult = new PMA_DrizzleResult( + $result, $bufferMode, $fetchMode + ); + return $this->_lastResult; + } + return $result; + } + + /** + * Returns the number of rows affected by last query + * + * @return int|false + */ + public function affectedRows() + { + return $this->_lastResult + ? $this->_lastResult->affectedRows() + : false; + } + + /** + * Pass calls of undefined methods to DrizzleCon object + * + * @param string $method method name + * @param mixed $args method parameters + * + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array(array($this->_dcon, $method), $args); + } + + /** + * Returns original Drizzle connection object + * + * @return DrizzleCon + */ + public function getConnectionObject() + { + return $this->_dcon; + } +} + +/** + * Wrapper around DrizzleResult. + * + * Allows for reading result rows as an associative array and hides complexity + * behind buffering. + * + * @package PhpMyAdmin-DBI + * @subpackage Drizzle + */ +class PMA_DrizzleResult +{ + /** + * Instamce of DrizzleResult class + * @var DrizzleResult + */ + private $_dresult; + /** + * Fetch mode + * @var int + */ + private $_fetchMode; + /** + * Buffering mode + * @var int + */ + private $_bufferMode; + + /** + * Cached column data + * @var DrizzleColumn[] + */ + private $_columns = null; + /** + * Cached column names + * @var string[] + */ + private $_columnNames = null; + + /** + * Constructor + * + * @param DrizzleResult $dresult result handler + * @param int $bufferMode buffering mode + * @param int $fetchMode fetching mode + */ + public function __construct(DrizzleResult $dresult, $bufferMode, $fetchMode) + { + $this->_dresult = $dresult; + $this->_bufferMode = $bufferMode; + $this->_fetchMode = $fetchMode; + + if ($this->_bufferMode == PMA_Drizzle::BUFFER_RESULT) { + $this->_dresult->buffer(); + } + } + + /** + * Sets fetch mode + * + * @param int $fetchMode fetch mode + * + * @return void + */ + public function setFetchMode($fetchMode) + { + $this->_fetchMode = $fetchMode; + } + + /** + * Reads information about columns contained in current result + * set into {@see $_columns} and {@see $_columnNames} arrays + * + * @return void + */ + private function _readColumns() + { + $this->_columns = array(); + $this->_columnNames = array(); + if ($this->_bufferMode == PMA_Drizzle::BUFFER_RESULT) { + while (($column = $this->_dresult->columnNext()) !== null) { + $this->_columns[] = $column; + $this->_columnNames[] = $column->name(); + } + } else { + while (($column = $this->_dresult->columnRead()) !== null) { + $this->_columns[] = $column; + $this->_columnNames[] = $column->name(); + } + } + } + + /** + * Returns columns in current result + * + * @return DrizzleColumn[] + */ + public function getColumns() + { + if (! $this->_columns) { + $this->_readColumns(); + } + return $this->_columns; + } + + /** + * Returns number if columns in result + * + * @return int + */ + public function numColumns() + { + return $this->_dresult->columnCount(); + } + + /** + * Transforms result row to conform to current fetch mode + * + * @param mixed &$row row to process + * @param int $fetchMode fetch mode + * + * @return void + */ + private function _transformResultRow(&$row, $fetchMode) + { + if (! $row) { + return; + } + + switch ($fetchMode) { + case PMA_Drizzle::FETCH_ASSOC: + $row = array_combine($this->_columnNames, $row); + break; + case PMA_Drizzle::FETCH_BOTH: + $length = count($row); + for ($i = 0; $i < $length; $i++) { + $row[$this->_columnNames[$i]] = $row[$i]; + } + break; + default: + break; + } + } + + /** + * Fetches next for from this result set + * + * @param int $fetchMode fetch mode to use, if not given the default one is used + * + * @return array|null + */ + public function fetchRow($fetchMode = null) + { + // read column names on first fetch, only buffered results + // allow for reading it later + if (! $this->_columns) { + $this->_readColumns(); + } + if ($fetchMode === null) { + $fetchMode = $this->_fetchMode; + } + $row = null; + switch ($this->_bufferMode) { + case PMA_Drizzle::BUFFER_RESULT: + $row = $this->_dresult->rowNext(); + break; + case PMA_Drizzle::BUFFER_ROW: + $row = $this->_dresult->rowBuffer(); + break; + } + $this->_transformResultRow($row, $fetchMode); + return $row; + } + + /** + * Adjusts the result pointer to an arbitrary row in buffered result + * + * @param integer $row_index where to seek + * + * @return bool + */ + public function seek($row_index) + { + if ($this->_bufferMode != PMA_Drizzle::BUFFER_RESULT) { + trigger_error( + __("Can't seek in an unbuffered result set"), E_USER_WARNING + ); + return false; + } + // rowSeek always returns NULL (drizzle extension v.0.5, API v.7) + if ($row_index >= 0 && $row_index < $this->_dresult->rowCount()) { + $this->_dresult->rowSeek($row_index); + return true; + } + return false; + } + + /** + * Returns the number of rows in buffered result set + * + * @return int|false + */ + public function numRows() + { + if ($this->_bufferMode != PMA_Drizzle::BUFFER_RESULT) { + trigger_error( + __("Can't count rows in an unbuffered result set"), E_USER_WARNING + ); + return false; + } + return $this->_dresult->rowCount(); + } + + /** + * Returns the number of rows affected by query + * + * @return int|false + */ + public function affectedRows() + { + return $this->_dresult->affectedRows(); + } + + /** + * Frees resources taken by this result + * + * @return void + */ + public function free() + { + unset($this->_columns); + unset($this->_columnNames); + drizzle_result_free($this->_dresult); + unset($this->_dresult); + } +} diff --git a/sources/libraries/display_change_password.lib.php b/sources/libraries/display_change_password.lib.php new file mode 100644 index 0000000..3d068d4 --- /dev/null +++ b/sources/libraries/display_change_password.lib.php @@ -0,0 +1,98 @@ += 5 + && PMA_USR_BROWSER_VER < 7) + ? 'onpropertychange' + : 'onchange'; + + $html = '
      '; + + $html .= PMA_URL_getHiddenInputs(); + + if (strpos($GLOBALS['PMA_PHP_SELF'], 'server_privileges') !== false) { + $html .= '' + . ''; + } + $html .= '
      ' + . '' . __('Change password') . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '
      ' + . '' + . '' + . '
      ' + . '' + . '' + . '' + . '' + . '  ' . __('Re-type:') . ' ' + . '' + . '
      ' . __('Password Hashing:') + . '' + . '' + . '' + . '
       ' + . '' + . '' + . '
      ' + . '
      ' + . '' + . '
      '; + return $html; +} diff --git a/sources/libraries/display_create_database.lib.php b/sources/libraries/display_create_database.lib.php new file mode 100644 index 0000000..425d13c --- /dev/null +++ b/sources/libraries/display_create_database.lib.php @@ -0,0 +1,63 @@ +'; + $html .= ' ' + . PMA_Util::showMySQLDocu('CREATE_DATABASE'); + $html .= '
      '; + $html .= PMA_URL_getHiddenInputs('', '', 5); + $html .= ''; + $html .= ''; + + include_once './libraries/mysql_charsets.inc.php'; + $html .= PMA_generateCharsetDropdownBox( + PMA_CSDROPDOWN_COLLATION, + 'db_collation', + null, + null, + true, + 5 + ); + + if (! empty($dbstats)) { + $html .= ''; + } + + $html .= ''; + $html .= ''; +} else { + $html .= ''; + $html .= '' . __('Create database:') . ' ' + . PMA_Util::showMySQLDocu('CREATE_DATABASE') + . '
      '; + + $html .= '' + . PMA_Util::getImage( + 's_error2.png', + '', + array('hspace' => 2, 'border' => 0, 'align' => 'middle') + ) + . '' . __('No Privileges') .''; +} // end create db form or message +?> diff --git a/sources/libraries/display_create_table.lib.php b/sources/libraries/display_create_table.lib.php new file mode 100644 index 0000000..124ae3f --- /dev/null +++ b/sources/libraries/display_create_table.lib.php @@ -0,0 +1,81 @@ += 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 + */ +if (! defined('PHPMYADMIN')) { + exit; +} + +/** + * + */ +require_once './libraries/check_user_privileges.lib.php'; + +$is_create_table_priv = true; + +/** + * Returns the html for create table. + * + * @param string $db database name + * + * @return string + */ +function PMA_getHtmlForCreateTable($db) +{ + $html = '
      '; + $html .= '
      '; + $html .= ''; + + if (PMA_Util::showIcons('ActionLinksMode')) { + $html .= PMA_Util::getImage('b_newtbl.png'); + } + $html .= __('Create table'); + + $html .= ' '; + $html .= PMA_URL_getHiddenInputs($db); + $html .= '
      '; + $html .= __('Name') . ":"; + $html .= ' '; + $html .= '
      '; + $html .= '
      '; + $html .= __('Number of columns') . ":"; + $html .= ' '; + $html .= '
      '; + $html .= '
      '; + $html .= '
      '; + $html .= '
      '; + $html .= ' '; + $html .= '
      '; + $html .= '
      '; + + return $html; +} + +if (!defined('TESTSUITE')) { + echo PMA_getHtmlForCreateTable($db); +} +?> diff --git a/sources/libraries/display_export.inc.php b/sources/libraries/display_export.inc.php new file mode 100644 index 0000000..39b07d3 --- /dev/null +++ b/sources/libraries/display_export.inc.php @@ -0,0 +1,72 @@ + $export_type, + 'single_table' => isset($single_table) + ) +); + +/* Fail if we didn't find any plugin */ +if (empty($export_list)) { + PMA_Message::error( + __('Could not load export plugins, please check your installation!') + )->display(); + exit; +} + +$html = '
      '; + +//output Hidden Inputs +$single_table_str = isset($single_table)? $single_table : ''; +$sql_query_str = isset($sql_query)? $sql_query : ''; +$html .= PMA_getHtmlForHiddenInput( + $export_type, + $db, + $table, + $single_table_str, + $sql_query_str +); + +//output Export Options +$num_tables_str = isset($num_tables)? $num_tables : ''; +$unlim_num_rows_str = isset($unlim_num_rows)? $unlim_num_rows : ''; +$multi_values_str = isset($multi_values)? $multi_values : ''; +$html .= PMA_getHtmlForExportOptions( + $export_type, + $db, + $table, + $multi_values_str, + $num_tables_str, + $export_list, + $unlim_num_rows_str +); + +$html .= '
      '; + +$response = PMA_Response::getInstance(); +$response->addHTML($html); diff --git a/sources/libraries/display_export.lib.php b/sources/libraries/display_export.lib.php new file mode 100644 index 0000000..d3a897a --- /dev/null +++ b/sources/libraries/display_export.lib.php @@ -0,0 +1,705 @@ +'; + $multi_values .= ''; + $multi_values .= __('Select All'); + $multi_values .= ''; + $multi_values .= ' / '; + $multi_values .= ''; + $multi_values .= __('Unselect All') . '
      '; + + $multi_values .= '
      '; + + return $multi_values; +} + +/** + * Prints Html For Export Hidden Input + * + * @param String $export_type Selected Export Type + * @param String $db Selected DB + * @param String $table Selected Table + * @param String $single_table Single Table + * @param String $sql_query Sql Query + * + * @return string + */ +function PMA_getHtmlForHiddenInput( + $export_type, $db, $table, $single_table, $sql_query +) { + global $cfg; + $html = ""; + if ($export_type == 'server') { + $html .= PMA_URL_getHiddenInputs('', '', 1); + } elseif ($export_type == 'database') { + $html .= PMA_URL_getHiddenInputs($db, '', 1); + } else { + $html .= PMA_URL_getHiddenInputs($db, $table, 1); + } + + // just to keep this value for possible next display of this form after saving + // on server + if (!empty($single_table)) { + $html .= '' + . "\n"; + } + + $html .= ''; + $html .= "\n"; + + // If the export method was not set, the default is quick + if (isset($_GET['export_method'])) { + $cfg['Export']['method'] = $_GET['export_method']; + } elseif (! isset($cfg['Export']['method'])) { + $cfg['Export']['method'] = 'quick'; + } + // The export method (quick, custom or custom-no-form) + $html .= ''; + + + if (isset($_GET['sql_query'])) { + $html .= '' . "\n"; + } elseif (! empty($sql_query)) { + $html .= '' . "\n"; + } + + return $html; +} + +/** + * Prints Html For Export Options Header + * + * @param String $export_type Selected Export Type + * @param String $db Selected DB + * @param String $table Selected Table + * + * @return string + */ +function PMA_getHtmlForExportOptionHeader($export_type, $db, $table) +{ + $html = ''; + + return $html; +} + +/** + * Prints Html For Export Options Method + * + * @return string + */ +function PMA_getHtmlForExportOptionsMethod() +{ + global $cfg; + if (isset($_GET['quick_or_custom'])) { + $export_method = $_GET['quick_or_custom']; + } else { + $export_method = $cfg['Export']['method']; + } + + $html = '
      '; + $html .= '

      ' . __('Export Method:') . '

      '; + $html .= '
        '; + $html .= '
      • '; + $html .= ''; + } else if ($export_type == 'database') { + $html .= '

        ' . __('Table(s):') . '

        '; + } + if (! empty($multi_values)) { + $html .= $multi_values; + } + $html .= '
      '; + + return $html; +} + +/** + * Prints Html For Export Options Format + * + * @param String $export_list Export List + * + * @return string + */ +function PMA_getHtmlForExportOptionsFormat($export_list) +{ + $html = '
      '; + $html .= '

      ' . __('Format:') . '

      '; + $html .= PMA_pluginGetChoice('Export', 'what', $export_list, 'format'); + $html .= '
      '; + + $html .= '
      '; + $html .= '

      ' . __('Format-specific options:') . '

      '; + $html .= '

      '; + $html .= __( + 'Scroll down to fill in the options for the selected format ' + . 'and ignore the options for other formats.' + ); + $html .= '

      '; + $html .= PMA_pluginGetOptions('Export', $export_list); + $html .= '
      '; + + if (function_exists('PMA_Kanji_encodingForm')) { + // Encoding setting form appended by Y.Kawada + // Japanese encoding setting + $html .= '
      '; + $html .= '

      ' . __('Encoding Conversion:') . '

      '; + $html .= PMA_Kanji_encodingForm(); + $html .= '
      '; + } + + $html .= '
      '; + + $html .= PMA_Util::getExternalBug( + __('SQL compatibility mode'), 'mysql', '50027', '14515' + ); + global $cfg; + $html .= ''; + $html .= '
      '; + + return $html; +} +/** + * Prints Html For Export Options Rows + * + * @param String $db Selected DB + * @param String $table Selected Table + * @param String $unlim_num_rows Num of Rows + * + * @return string + */ +function PMA_getHtmlForExportOptionsRows($db, $table, $unlim_num_rows) +{ + $html = '
      '; + $html .= '

      ' . __('Rows:') . '

      '; + $html .= '
        '; + $html .= '
      • '; + $html .= ''; + $html .= '
          '; + $html .= '
        • '; + $html .= ''; + $html .= ''; + $html .= '
        • '; + $html .= '
        • '; + $html .= ''; + $html .= ''; + $html .= '
        • '; + $html .= '
        '; + $html .= '
      • '; + $html .= '
      • '; + $html .= ''; + $html .= '
      • '; + $html .= '
      '; + $html .= '
      '; + return $html; +} + +/** + * Prints Html For Export Options Quick Export + * + * @return string + */ +function PMA_getHtmlForExportOptionsQuickExport() +{ + global $cfg; + $html = '
      '; + $html .= '

      ' . __('Output:') . '

      '; + $html .= '
        '; + $html .= '
      • '; + $html .= '%s'), + htmlspecialchars(PMA_Util::userDir($cfg['SaveDir'])) + ); + $html .= ''; + $html .= '
      • '; + $html .= '
      • '; + $html .= '%s'), + htmlspecialchars(PMA_Util::userDir($cfg['SaveDir'])) + ); + $html .= ''; + $html .= '
      • '; + $html .= '
      • '; + $html .= 'addMessage(__('@SERVER@ will become the server name')); + if ($export_type == 'database' || $export_type == 'table') { + $trans->addMessage(__(', @DATABASE@ will become the database name')); + if ($export_type == 'table') { + $trans->addMessage(__(', @TABLE@ will become the table name')); + } + } + + $msg = new PMA_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->addParam( + '', + false + ); + $msg->addParam('', false); + $msg->addParam($trans); + $doc_url = PMA_Util::getDocuLink('faq', 'faq6-27'); + $msg->addParam( + '', + false + ); + $msg->addParam('', false); + + $html .= PMA_Util::showHint($msg); + $html .= ''; + $html .= 'getUserValue( + 'pma_db_filename_template', + $GLOBALS['cfg']['Export']['file_template_database'] + ) + ); + } elseif ($export_type == 'table') { + $html .= htmlspecialchars( + $GLOBALS['PMA_Config']->getUserValue( + 'pma_table_filename_template', + $GLOBALS['cfg']['Export']['file_template_table'] + ) + ); + } else { + $html .= htmlspecialchars( + $GLOBALS['PMA_Config']->getUserValue( + 'pma_server_filename_template', + $GLOBALS['cfg']['Export']['file_template_server'] + ) + ); + } + } + $html .= '"'; + $html .= '/>'; + $html .= '' . "\n"; + reset($cfg['AvailableCharsets']); + $html .= '
      • '; + + return $html; +} + +/** + * Prints Html For Export Options Compression + * + * @return string + */ +function PMA_getHtmlForExportOptionsOutputCompression() +{ + global $cfg; + if (isset($_GET['compression'])) { + $selected_compression = $_GET['compression']; + } elseif (isset($cfg['Export']['compression'])) { + $selected_compression = $cfg['Export']['compression']; + } else { + $selected_compression = "none"; + } + + $html = ""; + // zip, gzip and bzip2 encode features + $is_zip = ($cfg['ZipDump'] && @function_exists('gzcompress')); + $is_gzip = ($cfg['GZipDump'] && @function_exists('gzencode')); + $is_bzip2 = ($cfg['BZipDump'] && @function_exists('bzcompress')); + if ($is_zip || $is_gzip || $is_bzip2) { + $html .= '
      • '; + $html .= ''; + $html .= ''; + $html .= '
      • '; + } else { + $html .= ''; + } + + return $html; +} + +/** + * Prints Html For Export Options Radio + * + * @return string + */ +function PMA_getHtmlForExportOptionsOutputRadio() +{ + $html = '
      • '; + $html .= '
      • '; + return $html; +} + +/** + * Prints Html For Export Options + * + * @param String $export_type Selected Export Type + * + * @return string + */ +function PMA_getHtmlForExportOptionsOutput($export_type) +{ + global $cfg; + $html = '
        '; + $html .= '

        ' . __('Output:') . '

        '; + $html .= '
          '; + $html .= '
        • '; + $html .= ''; + $html .= '
            '; + if (isset($cfg['SaveDir']) && !empty($cfg['SaveDir'])) { + $html .= PMA_getHtmlForExportOptionsOutputSaveDir(); + } + + $html .= PMA_getHtmlForExportOptionsOutputFormat($export_type); + + // charset of file + if ($GLOBALS['PMA_recoding_engine'] != PMA_CHARSET_NONE) { + $html .= PMA_getHtmlForExportOptionsOutputCharset(); + } // end if + + $html .= PMA_getHtmlForExportOptionsOutputCompression(); + + $html .= '
          '; + $html .= '
        • '; + + $html .= PMA_getHtmlForExportOptionsOutputRadio(); + + $html .= '
        '; + $html .= '
        '; + + return $html; +} + +/** + * Prints Html For Export Options + * + * @param String $export_type Selected Export Type + * @param String $db Selected DB + * @param String $table Selected Table + * @param String $multi_values Export selection + * @param String $num_tables number of tables + * @param String $export_list Export List + * @param String $unlim_num_rows Number of Rows + * + * @return string + */ +function PMA_getHtmlForExportOptions( + $export_type, $db, $table, $multi_values, + $num_tables, $export_list, $unlim_num_rows +) { + global $cfg; + $html = PMA_getHtmlForExportOptionHeader($export_type, $db, $table); + $html .= PMA_getHtmlForExportOptionsMethod(); + $html .= PMA_getHtmlForExportOptionsSelection($export_type, $multi_values); + + if (strlen($table) && empty($num_tables) && ! PMA_Table::isMerge($db, $table)) { + $html .= PMA_getHtmlForExportOptionsRows($db, $table, $unlim_num_rows); + } + + if (isset($cfg['SaveDir']) && !empty($cfg['SaveDir'])) { + $html .= PMA_getHtmlForExportOptionsQuickExport(); + } + + $html .= PMA_getHtmlForExportOptionsOutput($export_type); + + $html .= PMA_getHtmlForExportOptionsFormat($export_list); + return $html; +} +?> diff --git a/sources/libraries/display_git_revision.lib.php b/sources/libraries/display_git_revision.lib.php new file mode 100644 index 0000000..d794832 --- /dev/null +++ b/sources/libraries/display_git_revision.lib.php @@ -0,0 +1,83 @@ +get('PMA_VERSION_GIT')) { + $response = PMA_Response::getInstance(); + $response->isSuccess(false); + return; + } + + // load revision data from repo + $GLOBALS['PMA_Config']->checkGitRevision(); + + // 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'); + PMA_printListItem( + __('Git revision:') . ' ' + . $branch . ',
        ' + . sprintf( + __('committed on %1$s by %2$s'), + PMA_Util::localisedDate(strtotime($committer['date'])), + '' + . htmlspecialchars($committer['name']) . '' + ) + . ($author != $committer + ? ',
        ' + . sprintf( + __('authored on %1$s by %2$s'), + PMA_Util::localisedDate(strtotime($author['date'])), + '' + . htmlspecialchars($author['name']) . '' + ) + : ''), + 'li_pma_version_git', null, null, null + ); +} diff --git a/sources/libraries/display_import.inc.php b/sources/libraries/display_import.inc.php new file mode 100644 index 0000000..8431f2c --- /dev/null +++ b/sources/libraries/display_import.inc.php @@ -0,0 +1,54 @@ +display(); + exit; +} + +$timeout_passed_str = isset($timeout_passed)? $timeout_passed : null; +$offset_str = isset($offset)? $offset : null; +$html = PMA_getHtmlForImport( + $upload_id, + $import_type, + $db, + $table, + $max_upload_size, + $import_list, + $timeout_passed_str, + $offset_str +); + +$response = PMA_Response::getInstance(); +$response->addHTML($html); + +?> diff --git a/sources/libraries/display_import.lib.php b/sources/libraries/display_import.lib.php new file mode 100644 index 0000000..493330b --- /dev/null +++ b/sources/libraries/display_import.lib.php @@ -0,0 +1,586 @@ +'."\n"; + + return $html; +} + +/** + * Prints Html For Import Javascript + * + * @param int $upload_id The selected upload id + * + * @return string + */ +function PMA_getHtmlForImportJS($upload_id) +{ + global $SESSION_KEY; + $html = ''; + $html .= ''; + + return $html; +} + +/** + * Prints Html For Display Export options + * + * @param String $import_type Import type: server, database, table + * @param String $db Selected DB + * @param String $table Selected Table + * + * @return string + */ +function PMA_getHtmlForExportOptions($import_type, $db, $table) +{ + $html = ' '; + + return $html; +} + +/** + * Prints Html For Display Import options : Compressions + * + * @return string + */ +function PMA_getHtmlForImportCompressions() +{ + global $cfg; + $html = ''; + // 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'; + } + // We don't have show anything about compression, when no supported + if ($compressions != array()) { + $html .= '
        '; + $compress_str = sprintf( + __('File may be compressed (%s) or uncompressed.'), + implode(", ", $compressions) + ); + $html .= $compress_str; + $html .= '
        '; + $html .= __( + 'A compressed file\'s name must end in .[format].[compression]. ' + . 'Example: .sql.zip' + ); + $html .= '
        '; + } + + return $html; +} + +/** + * Prints Html For Display Import charset + * + * @return string + */ +function PMA_getHtmlForImportCharset() +{ + global $cfg; + $html = '
        '; + // charset of file + if ($GLOBALS['PMA_recoding_engine'] != PMA_CHARSET_NONE) { + $html .= ''; + reset($cfg['AvailableCharsets']); + $html .= '
        '; + } else { + $html .= '' . "\n"; + $html .= PMA_generateCharsetDropdownBox( + PMA_CSDROPDOWN_CHARSET, + 'charset_of_file', + 'charset_of_file', + 'utf8', + false + ); + } // end if (recoding) + + $html .= '
        '; + + return $html; +} + +/** + * Prints Html For Display Import options : file property + * + * @param int $max_upload_size Max upload size + * @param Array $import_list import list + * + * @return string + */ +function PMA_getHtmlForImportOptionsFile($max_upload_size, $import_list) +{ + global $cfg; + $html = '
        '; + $html .= '

        ' . __('File to Import:') . '

        '; + $html .= PMA_getHtmlForImportCompressions(); + $html .= '
        '; + + if ($GLOBALS['is_upload'] && !empty($cfg['UploadDir'])) { + $html .= '
          '; + $html .= '
        • '; + $html .= ' '; + $html .= PMA_Util::getBrowseUploadFileBlock($max_upload_size); + $html .= '
        • '; + $html .= '
        • '; + $html .= ' '; + $html .= PMA_Util::getSelectUploadFileBlock($import_list, $cfg['UploadDir']); + $html .= '
        • '; + $html .= '
        '; + + } elseif ($GLOBALS['is_upload']) { + $uid = uniqid(''); + $html .= PMA_Util::getBrowseUploadFileBlock($max_upload_size); + } elseif (!$GLOBALS['is_upload']) { + $html .= PMA_Message::notice( + __('File uploads are not allowed on this server.') + )->getDisplay(); + } elseif (!empty($cfg['UploadDir'])) { + $html .= PMA_Util::getSelectUploadFileBlock($import_list, $cfg['UploadDir']); + } // end if (web-server upload directory) + + $html .= '
        '; + $html .= PMA_getHtmlForImportCharset(); + $html .= '
        '; + + return $html; +} + +/** + * Prints Html For Display Import options : Partial Import + * + * @param String $timeout_passed timeout passed + * @param String $offset timeout offset + * + * @return string + */ +function PMA_getHtmlForImportOptionsPartialImport($timeout_passed, $offset) +{ + $html = '
        '; + $html .= '

        ' . __('Partial Import:') . '

        '; + + if (isset($timeout_passed) && $timeout_passed) { + $html .= '
        ' . "\n"; + $html .= ''; + $html .= sprintf( + __( + 'Previous import timed out, after resubmitting ' + . 'will continue from position %d.' + ), + $offset + ); + $html .= '
        ' . "\n"; + } + + $html .= '
        '; + $html .= ' '; + $html .= '
        '; + $html .= '
        '; + + if (! (isset($timeout_passed) && $timeout_passed)) { + $html .= '
        '; + $html .= ' '; + $html .= ' '; + $html .= '
        '; + + } 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" + $html .= ' '; + } + + $html .= '
        '; + + return $html; +} + +/** + * Prints Html For Display Import options : Format + * + * @param Array $import_list import list + * + * @return string + */ +function PMA_getHtmlForImportOptionsFormat($import_list) +{ + $html = '
        '; + $html .= '

        ' . __('Format:') . '

        '; + $html .= PMA_pluginGetChoice('Import', 'format', $import_list); + $html .= '
        '; + $html .= '
        '; + + $html .= '
        '; + $html .= '

        ' . __('Format-Specific Options:') . '

        '; + $html .= '

        ' + . 'Scroll down to fill in the options for the selected format ' + . 'and ignore the options for other formats.

        '; + $html .= PMA_pluginGetOptions('Import', $import_list); + $html .= '
        '; + $html .= '
        '; + + // Encoding setting form appended by Y.Kawada + if (function_exists('PMA_Kanji_encodingForm')) { + $html .= '
        '; + $html .= '

        ' . __('Encoding Conversion:') . '

        '; + $html .= PMA_Kanji_encodingForm(); + $html .= '
        '; + + } + $html .= "\n"; + + return $html; +} + +/** + * Prints Html For Display Import options : submit + * + * @return string + */ +function PMA_getHtmlForImportOptionsSubmit() +{ + $html = '
        '; + $html .= ' '; + $html .= '
        '; + + return $html; +} + +/** + * Prints Html For Display Import + * + * @param int $upload_id The selected upload id + * @param String $import_type Import type: server, database, table + * @param String $db Selected DB + * @param String $table Selected Table + * @param int $max_upload_size Max upload size + * @param Array $import_list Import list + * @param String $timeout_passed Timeout passed + * @param String $offset Timeout offset + * + * @return string + */ +function PMA_getHtmlForImport( + $upload_id, $import_type, $db, $table, + $max_upload_size, $import_list, $timeout_passed, $offset +) { + global $SESSION_KEY; + $html = ''; + $html .= ''; + $html .= ''; + $html .= '
        '; + $html .= ' ajax clock'; + + $html .= PMA_getHtmlForImportJS($upload_id); + + $html .= '
        '; + + $html .= PMA_getHtmlForHiddenInputs($import_type, $db, $table); + + $html .= PMA_getHtmlForExportOptions($import_type, $db, $table); + + $html .= PMA_getHtmlForImportOptionsFile($max_upload_size, $import_list); + + $html .= PMA_getHtmlForImportOptionsPartialImport($timeout_passed, $offset); + + $html .= PMA_getHtmlForImportOptionsFormat($import_list); + + $html .= PMA_getHtmlForImportOptionsSubmit(); + + $html .= '
        '; + $html .= '
        '; + + return $html; +} + +/** + * Prints javascript for upload with plugin, upload process bar + * + * @param int $upload_id The selected upload id + * + * @return string + */ +function PMA_getHtmlForImportWithPlugin($upload_id) +{ + //some variable for javasript + $ajax_url = "import_status.php?id=" . $upload_id . "&" + . PMA_URL_getCommon(array('import_status'=>1), '&'); + $promot_str = PMA_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.' + ), + false + ); + $statustext_str = PMA_escapeJsString(__('%s of %s')); + $upload_str = PMA_jsFormat(__('Uploading your import file…'), false); + $second_str = PMA_jsFormat(__('%s/sec.'), false); + $remaining_min = PMA_jsFormat(__('About %MIN min. %SEC sec. remaining.'), false); + $remaining_second = PMA_jsFormat(__('About %SEC sec. remaining.'), false); + $processed_str = PMA_jsFormat( + __('The file is being processed, please be patient.'), + false + ); + $import_url = PMA_URL_getCommon(array('import_status'=>1), '&'); + + //start output + $html = 'var finished = false; '; + $html .= 'var percent = 0.0; '; + $html .= 'var total = 0; '; + $html .= 'var complete = 0; '; + $html .= 'var original_title = ' + . 'parent && parent.document ? parent.document.title : false; '; + $html .= 'var import_start; '; + + $html .= 'var perform_upload = function () { '; + $html .= 'new $.getJSON( '; + $html .= ' "' . $ajax_url . '", '; + $html .= ' {}, '; + $html .= ' function(response) { '; + $html .= ' finished = response.finished; '; + $html .= ' percent = response.percent; '; + $html .= ' total = response.total; '; + $html .= ' complete = response.complete; '; + + $html .= ' if (total==0 && complete==0 && percent==0) { '; + $img_tag = 'ajax clock ' + . $promot_str . '\'); '; + $html .= ' $("#upload_form_status").css("display", "none"); '; + $html .= ' } else { '; + $html .= ' var now = new Date(); '; + $html .= ' now = Date.UTC( '; + $html .= ' now.getFullYear(), now.getMonth(), now.getDate(), '; + $html .= ' now.getHours(), now.getMinutes(), now.getSeconds()) '; + $html .= ' + now.getMilliseconds() - 1000; '; + $html .= ' var statustext = $.sprintf("' . $statustext_str . '", '; + $html .= ' formatBytes(complete, 1, PMA_messages.strDecimalSeparator), '; + $html .= ' formatBytes(total, 1, PMA_messages.strDecimalSeparator) '; + $html .= ' ); '; + + $html .= ' if ($("#importmain").is(":visible")) { '; + // show progress UI + $html .= ' $("#importmain").hide(); '; + $html .= ' $("#import_form_status") '; + $html .= ' .html(\'
        ' + . '
        ' + . '
        ' + . '
        ' + . 'ajax clock ' + . $upload_str . '
        \') '; + $html .= ' .show(); '; + $html .= ' import_start = now; '; + $html .= ' } '; + $html .= ' else if (percent > 9 || complete > 2000000) { '; + // calculate estimated time + $html .= ' var used_time = now - import_start; '; + $html .= ' var seconds = ' + . 'parseInt(((total - complete) / complete) * used_time / 1000); '; + $html .= ' var speed = $.sprintf("' . $second_str . '"'; + $html .= ' , formatBytes(complete / used_time * 1000, 1,' + . ' PMA_messages.strDecimalSeparator)); '; + + $html .= ' var minutes = parseInt(seconds / 60); '; + $html .= ' seconds %= 60; '; + $html .= ' var estimated_time; '; + $html .= ' if (minutes > 0) { '; + $html .= ' estimated_time = "' . $remaining_min . '"'; + $html .= ' .replace("%MIN", minutes).replace("%SEC", seconds); '; + $html .= ' } '; + $html .= ' else { '; + $html .= ' estimated_time = "' . $remaining_second . '"'; + $html .= ' .replace("%SEC", seconds); '; + $html .= ' } '; + + $html .= ' statustext += "
        " + speed + "

        " ' + . '+ estimated_time; '; + $html .= ' } '; + + $html .= ' var percent_str = Math.round(percent) + "%"; '; + $html .= ' $("#status").animate({width: percent_str}, 150); '; + $html .= ' $(".percentage").text(percent_str); '; + + // show percent in window title + $html .= ' if (original_title !== false) { '; + $html .= ' parent.document.title = percent_str + " - " + original_title; '; + $html .= ' } '; + $html .= ' else { '; + $html .= ' document.title = percent_str + " - " + original_title; '; + $html .= ' } '; + $html .= ' $("#statustext").html(statustext); '; + $html .= ' } '; + + $html .= ' if (finished == true) { '; + $html .= ' if (original_title !== false) { '; + $html .= ' parent.document.title = original_title; '; + $html .= ' } '; + $html .= ' else { '; + $html .= ' document.title = original_title; '; + $html .= ' } '; + $html .= ' $("#importmain").hide(); '; + // loads the message, either success or mysql error + $html .= ' $("#import_form_status") '; + $html .= ' .html(\'ajax clock ' + . $processed_str . '\')'; + $html .= ' .show(); '; + $html .= ' $("#import_form_status").load("import_status.php?' + . 'message=true&' . $import_url . '"); '; + $html .= ' PMA_reloadNavigation(); '; + + // if finished + $html .= ' } '; + $html .= ' else { '; + $html .= ' setTimeout(perform_upload, 1000); '; + $html .= ' } '; + $html .= '}); '; + $html .= '}; '; + $html .= 'setTimeout(perform_upload, 1000); '; + + return $html; +} + +?> diff --git a/sources/libraries/display_import_ajax.lib.php b/sources/libraries/display_import_ajax.lib.php new file mode 100644 index 0000000..275db02 --- /dev/null +++ b/sources/libraries/display_import_ajax.lib.php @@ -0,0 +1,129 @@ + diff --git a/sources/libraries/display_select_lang.lib.php b/sources/libraries/display_select_lang.lib.php new file mode 100644 index 0000000..0ff4c86 --- /dev/null +++ b/sources/libraries/display_select_lang.lib.php @@ -0,0 +1,98 @@ + 1) { + $retval .= '
        '; + + $_form_params = array( + 'db' => $GLOBALS['db'], + 'table' => $GLOBALS['table'], + ); + $retval .= PMA_URL_getHiddenInputs($_form_params); + + // 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 .= PMA_Util::showDocu('faq', 'faq7-2'); + } + if ($use_fieldset) { + $retval .= '
        ' + . $language_title . ''; + } else { + $retval .= ''; + } + + $retval .= ''; + + if ($use_fieldset) { + $retval .= '
        '; + } + + $retval .= '
        '; + } + return $retval; +} + +?> diff --git a/sources/libraries/display_structure.inc.php b/sources/libraries/display_structure.inc.php new file mode 100644 index 0000000..60eeba3 --- /dev/null +++ b/sources/libraries/display_structure.inc.php @@ -0,0 +1,270 @@ +'; + +$response->addHTML($html_form); +$response->addHTML(PMA_URL_getHiddenInputs($db, $table)); + +$tabletype = ''; +} else if ($tbl_is_view) { + $tabletype .= '"view" />'; +} else { + $tabletype .= '"table" />'; +} +$response->addHTML($tabletype); + +$tablestructure = ''; +$response->addHTML($tablestructure); + + +$response->addHTML( + PMA_getHtmlForTableStructureHeader( + $db_is_information_schema, + $tbl_is_view + ) +); + +$response->addHTML(''); + +// table body + +// prepare comments +$comments_map = array(); +$mime_map = array(); + +if ($GLOBALS['cfg']['ShowPropertyComments']) { + include_once 'libraries/transformations.lib.php'; + $comments_map = PMA_getComments($db, $table); + if ($cfgRelation['mimework'] && $cfg['BrowseMIME']) { + $mime_map = PMA_getMIME($db, $table, true); + } +} + +$rownum = 0; +$columns_list = array(); +$save_row = array(); +$odd_row = true; +foreach ($fields as $row) { + $save_row[] = $row; + $rownum++; + $columns_list[] = $row['Field']; + + $type = $row['Type']; + $extracted_columnspec = PMA_Util::extractColumnSpec($row['Type']); + + if ('set' == $extracted_columnspec['type'] + || 'enum' == $extracted_columnspec['type'] + ) { + $type_nowrap = ''; + } else { + $type_nowrap = ' class="nowrap"'; + } + $type = $extracted_columnspec['print_type']; + if (empty($type)) { + $type = ' '; + } + + $field_charset = ''; + if ($extracted_columnspec['can_contain_collation'] + && ! empty($row['Collation']) + ) { + $field_charset = $row['Collation']; + } + + // Display basic mimetype [MIME] + if ($cfgRelation['commwork'] + && $cfgRelation['mimework'] + && $cfg['BrowseMIME'] + && isset($mime_map[$row['Field']]['mimetype']) + ) { + $type_mime = '
        MIME: ' + . str_replace('_', '/', $mime_map[$row['Field']]['mimetype']); + } else { + $type_mime = ''; + } + + $attribute = $extracted_columnspec['attribute']; + + // prepare a common variable to reuse below; however, + // in case of a VIEW, $analyzed_sql[0]['create_table_fields'] is empty + if (isset($analyzed_sql[0]['create_table_fields'][$row['Field']])) { + $tempField = $analyzed_sql[0]['create_table_fields'][$row['Field']]; + } else { + $tempField = array(); + } + + // MySQL 4.1.2+ TIMESTAMP options + // (if on_update_current_timestamp is set, then it's TRUE) + if (isset($tempField['on_update_current_timestamp'])) { + $attribute = 'on update CURRENT_TIMESTAMP'; + } + + // here, we have a TIMESTAMP that SHOW FULL COLUMNS reports as having the + // NULL attribute, but SHOW CREATE TABLE says the contrary. Believe + // the latter. + if (! empty($tempField['type']) + && $tempField['type'] == 'TIMESTAMP' + && $tempField['timestamp_not_null'] + ) { + $row['Null'] = ''; + } + + + if (! isset($row['Default'])) { + if ($row['Null'] == 'YES') { + $row['Default'] = 'NULL'; + } + } else { + $row['Default'] = htmlspecialchars($row['Default']); + } + + $field_encoded = urlencode($row['Field']); + $field_name = htmlspecialchars($row['Field']); + $displayed_field_name = $field_name; + + // underline commented fields and display a hover-title (CSS only) + + if (isset($comments_map[$row['Field']])) { + $displayed_field_name = '' + . $field_name . ''; + } + + if ($primary && $primary->hasColumn($field_name)) { + $displayed_field_name = '' . $field_name . ''; + } + $response->addHTML( + '' + ); + $odd_row = !$odd_row; + + $response->addHTML( + PMA_getHtmlTableStructureRow( + $row, $rownum, $displayed_field_name, + $type_nowrap, $extracted_columnspec, $type_mime, + $field_charset, $attribute, $tbl_is_view, + $db_is_information_schema, $url_query, $field_encoded, $titles, $table + ) + ); + + if (! $tbl_is_view && ! $db_is_information_schema) { + $response->addHTML( + PMA_getHtmlForActionsInTableStructure( + $type, $tbl_storage_engine, $primary, + $field_name, $url_query, $titles, $row, $rownum, + $hidden_titles, $columns_with_unique_index + ) + ); + } // end if (! $tbl_is_view && ! $db_is_information_schema) + + $response->addHTML(''); + + unset($field_charset); +} // end foreach + +$response->addHTML('
        '); + +$response->addHTML( + PMA_getHtmlForCheckAllTableColumn( + $pmaThemeImage, $text_dir, $tbl_is_view, + $db_is_information_schema, $tbl_storage_engine + ) +); + +$response->addHTML( + '
        ' +); +$response->addHTML( + PMA_getHtmlDivForMoveColumnsDialog() +); + +/** + * Work on the table + */ + +if ($tbl_is_view) { + $response->addHTML(PMA_getHtmlForEditView($url_params)); +} +$response->addHTML( + PMA_getHtmlForOptionalActionLinks( + $url_query, $tbl_is_view, $db_is_information_schema, + $tbl_storage_engine, $cfgRelation + ) +); + +if (! $tbl_is_view && ! $db_is_information_schema) { + $response->addHTML('
        '); + $response->addHTML(PMA_getHtmlForAddColumn($columns_list)); + $response->addHTML( + '
        ' + ); +} + +/** + * Displays indexes + */ + +if (! $tbl_is_view + && ! $db_is_information_schema + && 'ARCHIVE' != $tbl_storage_engine +) { + //return the list of index + $response->addJSON( + 'indexes_list', + PMA_Index::getView($GLOBALS['table'], $GLOBALS['db']) + ); + $response->addHTML(PMA_getHtmlForDisplayIndexes()); +} + +/** + * Displays Space usage and row statistics + */ +// BEGIN - Calc Table Space +// Get valid statistics whatever is the table type +if ($cfg['ShowStats']) { + //get table stats in HTML format + $tablestats = PMA_getHtmlForDisplayTableStats( + $showtable, $table_info_num_rows, $tbl_is_view, + $db_is_information_schema, $tbl_storage_engine, + $url_query, $tbl_collation + ); + //returning the response in JSON format to be used by Ajax + $response->addJSON('tableStat', $tablestats); + $response->addHTML($tablestats); +} +// END - Calc Table Space + +$response->addHTML( + '
        ' +); +?> diff --git a/sources/libraries/engines/bdb.lib.php b/sources/libraries/engines/bdb.lib.php new file mode 100644 index 0000000..d5203cf --- /dev/null +++ b/sources/libraries/engines/bdb.lib.php @@ -0,0 +1,84 @@ + 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 + */ + function getVariablesLikePattern() + { + return '%bdb%'; + } + + /** + * returns string with filename for the MySQL helppage + * about this storage engine + * + * @return string mysql helppage filename + */ + function getMysqlHelpPage() + { + return 'bdb'; + } +} + +?> diff --git a/sources/libraries/engines/berkeleydb.lib.php b/sources/libraries/engines/berkeleydb.lib.php new file mode 100644 index 0000000..d955886 --- /dev/null +++ b/sources/libraries/engines/berkeleydb.lib.php @@ -0,0 +1,24 @@ + diff --git a/sources/libraries/engines/binlog.lib.php b/sources/libraries/engines/binlog.lib.php new file mode 100644 index 0000000..ea81738 --- /dev/null +++ b/sources/libraries/engines/binlog.lib.php @@ -0,0 +1,28 @@ + diff --git a/sources/libraries/engines/innobase.lib.php b/sources/libraries/engines/innobase.lib.php new file mode 100644 index 0000000..88dbecd --- /dev/null +++ b/sources/libraries/engines/innobase.lib.php @@ -0,0 +1,25 @@ + diff --git a/sources/libraries/engines/innodb.lib.php b/sources/libraries/engines/innodb.lib.php new file mode 100644 index 0000000..c7b639b --- /dev/null +++ b/sources/libraries/engines/innodb.lib.php @@ -0,0 +1,411 @@ + 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 + */ + function getVariablesLikePattern() + { + return 'innodb\\_%'; + } + + /** + * Get information pages + * + * @return array detail pages + */ + 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 + */ + 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" + . ' : ' + . PMA_Util::formatNumber( + $status['Innodb_buffer_pool_pages_total'], 0 + ) + . ' ' . __('pages') + . ' / ' + . join( + ' ', + PMA_Util::formatByteDown( + $status['Innodb_buffer_pool_pages_total'] + * $status['Innodb_page_size'] + ) + ) . "\n" + . '
        ' . __('Free pages') . '' + . PMA_Util::formatNumber( + $status['Innodb_buffer_pool_pages_free'], 0 + ) + . '
        ' . __('Dirty pages') . '' + . PMA_Util::formatNumber( + $status['Innodb_buffer_pool_pages_dirty'], 0 + ) + . '
        ' . __('Pages containing data') . '' + . PMA_Util::formatNumber( + $status['Innodb_buffer_pool_pages_data'], 0 + ) . "\n" + . '
        ' . __('Pages to be flushed') . '' + . PMA_Util::formatNumber( + $status['Innodb_buffer_pool_pages_flushed'], 0 + ) . "\n" + . '
        ' . __('Busy pages') . '' + . PMA_Util::formatNumber( + $status['Innodb_buffer_pool_pages_misc'], 0 + ) . "\n" + . '
        ' . __('Latched pages') . '' + . PMA_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') . '' + . PMA_Util::formatNumber( + $status['Innodb_buffer_pool_read_requests'], 0 + ) . "\n" + . '
        ' . __('Write requests') . '' + . PMA_Util::formatNumber( + $status['Innodb_buffer_pool_write_requests'], 0 + ) . "\n" + . '
        ' . __('Read misses') . '' + . PMA_Util::formatNumber( + $status['Innodb_buffer_pool_reads'], 0 + ) . "\n" + . '
        ' . __('Write waits') . '' + . PMA_Util::formatNumber( + $status['Innodb_buffer_pool_wait_free'], 0 + ) . "\n" + . '
        ' . __('Read misses in %') . '' + . ($status['Innodb_buffer_pool_read_requests'] == 0 + ? '---' + : htmlspecialchars( + PMA_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( + PMA_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 INNODB STATUS inside pre tags + */ + function getPageStatus() + { + return '
        ' . "\n"
        +            . htmlspecialchars(
        +                $GLOBALS['dbi']->fetchValue('SHOW INNODB STATUS;', 0, 'Status')
        +            ) . "\n"
        +            . '
        ' . "\n"; + } + + /** + * Returns content for page $id + * + * @param string $id page id + * + * @return string html output + */ + function getPage($id) + { + if (! array_key_exists($id, $this->getInfoPages())) { + return false; + } + + $id = 'getPage' . $id; + + return $this->$id(); + } + + /** + * returns string with filename for the MySQL helppage + * about this storage engine + * + * @return string mysql helppage filename + */ + function getMysqlHelpPage() + { + return 'innodb-storage-engine'; + } + + /** + * Gets the InnoDB plugin version number + * + * http://www.innodb.com/products/innodb_plugin + * (do not confuse this with phpMyAdmin's storage engine plugins!) + * + * @return string the version number, or empty if not running as a plugin + */ + function getInnodbPluginVersion() + { + return $GLOBALS['dbi']->fetchValue('SELECT @@innodb_version;'); + } + + /** + * Gets the InnoDB file format + * + * (works only for the InnoDB plugin) + * http://www.innodb.com/products/innodb_plugin + * (do not confuse this with phpMyAdmin's storage engine plugins!) + * + * @return string the InnoDB file format + */ + 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 + * + * (works only for the InnoDB plugin) + * http://www.innodb.com/products/innodb_plugin + * (do not confuse this with phpMyAdmin's storage engine plugins!) + * + * @return boolean whether this feature is supported or not + */ + function supportsFilePerTable() + { + $innodb_file_per_table = $GLOBALS['dbi']->fetchValue( + "SHOW GLOBAL VARIABLES LIKE 'innodb_file_per_table';", 0, 1 + ); + if ($innodb_file_per_table == 'ON') { + return true; + } else { + return false; + } + + } +} + +?> diff --git a/sources/libraries/engines/memory.lib.php b/sources/libraries/engines/memory.lib.php new file mode 100644 index 0000000..8e9fe5f --- /dev/null +++ b/sources/libraries/engines/memory.lib.php @@ -0,0 +1,34 @@ + array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + ); + } +} + +?> diff --git a/sources/libraries/engines/merge.lib.php b/sources/libraries/engines/merge.lib.php new file mode 100644 index 0000000..4bccfc7 --- /dev/null +++ b/sources/libraries/engines/merge.lib.php @@ -0,0 +1,21 @@ + diff --git a/sources/libraries/engines/mrg_myisam.lib.php b/sources/libraries/engines/mrg_myisam.lib.php new file mode 100644 index 0000000..d342536 --- /dev/null +++ b/sources/libraries/engines/mrg_myisam.lib.php @@ -0,0 +1,33 @@ + diff --git a/sources/libraries/engines/myisam.lib.php b/sources/libraries/engines/myisam.lib.php new file mode 100644 index 0000000..5882824 --- /dev/null +++ b/sources/libraries/engines/myisam.lib.php @@ -0,0 +1,69 @@ + 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/sources/libraries/engines/ndbcluster.lib.php b/sources/libraries/engines/ndbcluster.lib.php new file mode 100644 index 0000000..e57846b --- /dev/null +++ b/sources/libraries/engines/ndbcluster.lib.php @@ -0,0 +1,55 @@ + array( + ), + ); + } + + /** + * Returns the pattern to be used in the query for SQL variables + * related to NDBCLUSTER storage engine + * + * @return string SQL query LIKE pattern + */ + function getVariablesLikePattern() + { + return 'ndb\\_%'; + } + + /** + * Returns string with filename for the MySQL help page + * about this storage engine + * + * @return string mysql helppage filename + */ + function getMysqlHelpPage() + { + return 'ndbcluster'; + } +} + +?> diff --git a/sources/libraries/engines/pbxt.lib.php b/sources/libraries/engines/pbxt.lib.php new file mode 100644 index 0000000..4b7bcf9 --- /dev/null +++ b/sources/libraries/engines/pbxt.lib.php @@ -0,0 +1,167 @@ + 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 + */ + function resolveTypeSize($formatted_size) + { + if (preg_match('/^[0-9]+[a-zA-Z]+$/', $formatted_size)) { + $value = PMA_Util::extractValueFromFormattedSize($formatted_size); + } else { + $value = $formatted_size; + } + return PMA_Util::formatByteDown($value); + } + + //-------------------- + /** + * Get information about pages + * + * @return array Information about pages + */ + function getInfoPages() + { + $pages = array(); + $pages['Documentation'] = __('Documentation'); + return $pages; + } + + //-------------------- + /** + * Get content of a page + * + * @param string $id Id of searched page + * + * @return string + */ + function getPage($id) + { + if (! array_key_exists($id, $this->getInfoPages())) { + return false; + } + + $id = 'getPage' . $id; + + return $this->$id(); + } + + /** + * Get content of documentation page + * + * @return string + */ + function getPageDocumentation() + { + $output = '

        ' . sprintf( + __( + 'Documentation and further information about PBXT' + . ' can be found on the %sPrimeBase XT Home Page%s.' + ), + '', '' + ) + . '

        ' . "\n" + . '

        ' . __('Related Links') . '

        ' . "\n" + . '' . "\n"; + + return $output; + } +} + +?> diff --git a/sources/libraries/engines/performance_schema.lib.php b/sources/libraries/engines/performance_schema.lib.php new file mode 100644 index 0000000..f7664b6 --- /dev/null +++ b/sources/libraries/engines/performance_schema.lib.php @@ -0,0 +1,28 @@ + diff --git a/sources/libraries/error.inc.php b/sources/libraries/error.inc.php new file mode 100644 index 0000000..c48a7f1 --- /dev/null +++ b/sources/libraries/error.inc.php @@ -0,0 +1,59 @@ + + + + + + + phpMyAdmin + + + + +

        phpMyAdmin -

        +

        + + + diff --git a/sources/libraries/error_report.lib.php b/sources/libraries/error_report.lib.php new file mode 100644 index 0000000..3b2316d --- /dev/null +++ b/sources/libraries/error_report.lib.php @@ -0,0 +1,358 @@ + $exception, + "script_name" => $script_name, + "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" => + empty($GLOBALS['cfg']['Servers'][1]['pmadb']) ? "disabled" : + "enabled", + "php_version" => phpversion(), + "microhistory" => $_REQUEST['microhistory'], + ); + + if (! empty($_REQUEST['description'])) { + $report['steps'] = $_REQUEST['description']; + } + + if ($pretty_print) { + /* JSON_PRETTY_PRINT available since PHP 5.4 */ + if (defined('JSON_PRETTY_PRINT')) { + return json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } else { + return PMA_prettyPrint($report); + } + } else { + return $report; + } +} + +/** + * Sanitize a url to remove the identifiable host name and extract the + * current scriptname 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 + */ +function PMA_sanitizeUrl($url) +{ + $components = parse_url($url); + if (isset($components["fragment"]) + && preg_match("", $components["fragment"], $matches) + ) { + $uri = str_replace($matches[0], "", $components["fragment"]); + $url = "http://dummy_host/" . $uri; + $components = parse_url($url); + } + + // get script name + preg_match("<([a-zA-Z\-_\d]*\.php)$>", $components["path"], $matches); + $script_name = $matches[1]; + + // remove deployment specific details to make uri more generic + parse_str($components["query"], $query_array); + unset($query_array["db"]); + unset($query_array["table"]); + unset($query_array["token"]); + unset($query_array["server"]); + $query = http_build_query($query_array); + + $uri = $script_name . "?" . $query; + return array($uri, $script_name); +} + +/** + * Sends report data to the error reporting server + * + * @param Array $report the report info to be sent + * + * @return String the reply of the server + */ +function PMA_sendErrorReport($report) +{ + $data_string = json_encode($report); + if (ini_get('allow_url_fopen')) { + $context = array("http" => + array( + 'method' => 'POST', + 'content' => $data_string, + 'header' => "Content-Type: multipart/form-data\r\n", + ) + ); + if (strlen($GLOBALS['cfg']['ProxyUrl'])) { + $context['http'] = array( + 'proxy' => $GLOBALS['cfg']['ProxyUrl'], + 'request_fulluri' => true + ); + if (strlen($GLOBALS['cfg']['ProxyUser'])) { + $auth = base64_encode( + $GLOBALS['cfg']['ProxyUser'] . ':' . $GLOBALS['cfg']['ProxyPass'] + ); + $context['http']['header'] .= 'Proxy-Authorization: Basic ' + . $auth . "\r\n"; + } + } + $response = file_get_contents( + SUBMISSION_URL, + false, + stream_context_create($context) + ); + } else if (function_exists('curl_init')) { + $curl_handle = curl_init(SUBMISSION_URL); + if (strlen($GLOBALS['cfg']['ProxyUrl'])) { + curl_setopt($curl_handle, CURLOPT_PROXY, $GLOBALS['cfg']['ProxyUrl']); + if (strlen($GLOBALS['cfg']['ProxyUser'])) { + curl_setopt( + $curl_handle, + CURLOPT_PROXYUSERPWD, + $GLOBALS['cfg']['ProxyUser'] . ':' . $GLOBALS['cfg']['ProxyPass'] + ); + } + } + curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($curl_handle, CURLOPT_HTTPHEADER, array('Expect:')); + curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $data_string); + curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1); + $response = curl_exec($curl_handle); + curl_close($curl_handle); + } + return $response; +} + +/** + * Returns number of lines in given javascript file. + * + * @param string $filename javascript filename + * + * @return Number of lines + */ +function PMA_countLines($filename) +{ + global $LINE_COUNT; + if (!defined('LINE_COUNTS')) { + $linecount = 0; + $handle = fopen('./js/' . $filename, 'r'); + while (!feof($handle)) { + $line = fgets($handle); + if ($line === false) { + break; + } + $linecount++; + } + fclose($handle); + return $linecount; + } else { + return $LINE_COUNT[$filename]; + } +} + +/** + * returns the translated linenumber and the file name from the cumulative line + * number and an array of files + * + * uses the $LINE_COUNT global array of file names and line numbers + * + * @param Array $filenames list of files in order of concatenation + * @param Integer $cumulative_number the cumulative line number in the + * concatenated files + * + * @return Array the filename and linenumber + * Returns two variables in an array: + * - A String $filename the filename where the requested cumulative number + * exists + * - Integer $linenumber the translated line number in the returned file + */ +function PMA_getLineNumber($filenames, $cumulative_number) +{ + $cumulative_sum = 0; + foreach ($filenames as $filename) { + $filecount = PMA_countLines($filename); + if ($cumulative_number <= $cumulative_sum + $filecount + 2) { + $linenumber = $cumulative_number - $cumulative_sum; + break; + } + $cumulative_sum += $filecount + 2; + } + return array($filename, $linenumber); +} + +/** + * translates the cumulative line numbers in the stactrace as well as sanitize + * urls and trim long lines in the context + * + * @param Array $stack the stacktrace + * + * @return Array $stack the modified stacktrace + */ +function PMA_translateStacktrace($stack) +{ + foreach ($stack as &$level) { + foreach ($level["context"] as &$line) { + if (strlen($line) > 80) { + $line = substr($line, 0, 75) . "//..."; + } + } + if (preg_match("", $level["url"], $matches)) { + parse_str($matches[1], $vars); + List($file_name, $line_number) = PMA_getLineNumber( + $vars["scripts"], $level["line"] + ); + $level["filename"] = $file_name; + $level["line"] = $line_number; + } else { + unset($level["context"]); + List($uri, $script_name) = PMA_sanitizeUrl($level["url"]); + $level["uri"] = $uri; + $level["scriptname"] = $script_name; + } + 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 + */ +function PMA_getErrorReportForm() +{ + $html = ""; + $html .= '
        ' + .'
        '; + + $html .= '

        ' . __( + 'phpMyAdmin has encountered an error. We have collected data about' + .' this error as well as information about relevant configuration' + .' settings to send to the phpMyAdmin team to help us in' + .' debugging the problem.' + ) .'

        '; + + $html .= '
        ' + .'
        '
        +            .PMA_getReportData()
        +            .'
        '; + + $html .= '
        ' + .''; + + $html .= '' + .''; + + $html .= '
        '; + + $html .= PMA_URL_getHiddenInputs(); + + $reportData = PMA_getReportData(false); + if (! empty($reportData)) { + $html .= PMA_getHiddenFields($reportData); + } + + $html .= '
        '; + + return $html; +} + +/** + * generates the error report form to collect user description and preview the + * report before being sent + * + * @return String the form + */ +function PMA_hasLatestLineCounts() +{ + $line_counts_time = filemtime("js/line_counts.php"); + $js_time = filemtime("js"); + return $line_counts_time >= $js_time; +} + +/** + * pretty print a variable for the user + * + * @param mixed $object the variable to pretty print + * @param String $namespace the namespace to use for printing values + * + * @return String the human readable form of the variable + */ +function PMA_prettyPrint($object, $namespace="") +{ + if (! is_array($object)) { + if (empty($namespace)) { + return "$object\n"; + } else { + return "$namespace: \"$object\"\n"; + } + } + $output = ""; + foreach ($object as $key => $value) { + if ($namespace == "") { + $new_namespace = "$key"; + } else { + $new_namespace = $namespace . "[$key]"; + } + $output .= PMA_prettyPrint($value, $new_namespace); + } + return $output; +} diff --git a/sources/libraries/file_listing.lib.php b/sources/libraries/file_listing.lib.php new file mode 100644 index 0000000..2c457ef --- /dev/null +++ b/sources/libraries/file_listing.lib.php @@ -0,0 +1,99 @@ +' . htmlspecialchars($val) . '' . "\n"; + } + return $result; +} + +/** + * Get currently supported decompressions. + * + * @return string separated list of extensions usable in PMA_getDirContent + */ +function PMA_supportedDecompressions() +{ + global $cfg; + + $compressions = ''; + + if ($cfg['GZipDump'] && @function_exists('gzopen')) { + if (!empty($compressions)) { + $compressions .= '|'; + } + $compressions .= 'gz'; + } + if ($cfg['BZipDump'] && @function_exists('bzopen')) { + if (!empty($compressions)) { + $compressions .= '|'; + } + $compressions .= 'bz2'; + } + if ($cfg['ZipDump'] && @function_exists('gzinflate')) { + if (!empty($compressions)) { + $compressions .= '|'; + } + $compressions .= 'zip'; + } + + return $compressions; +} diff --git a/sources/libraries/gis/pma_gis_factory.php b/sources/libraries/gis/pma_gis_factory.php new file mode 100644 index 0000000..a8f8b37 --- /dev/null +++ b/sources/libraries/gis/pma_gis_factory.php @@ -0,0 +1,63 @@ + diff --git a/sources/libraries/gis/pma_gis_geometry.php b/sources/libraries/gis/pma_gis_geometry.php new file mode 100644 index 0000000..b457db5 --- /dev/null +++ b/sources/libraries/gis/pma_gis_geometry.php @@ -0,0 +1,361 @@ + $min_max['maxX']) { + $min_max['maxX'] = $x; + } + if (! isset($min_max['minX']) || $x < $min_max['minX']) { + $min_max['minX'] = $x; + } + $y = (float) $cordinates[1]; + if (! isset($min_max['maxY']) || $y > $min_max['maxY']) { + $min_max['maxY'] = $y; + } + if (! isset($min_max['minY']) || $y < $min_max['minY']) { + $min_max['minY'] = $y; + } + } + return $min_max; + } + + /** + * Generates parameters for the GIS data editor from the value of the GIS column. + * This method performs common work. + * More specific work is performed by each of the geom classes. + * + * @param string $value value of the GIS column + * + * @return array parameters for the GIS editor from the value of the GIS column + * @access protected + */ + protected function generateParams($value) + { + $geom_types = '(POINT|MULTIPOINT|LINESTRING|MULTILINESTRING' + . '|POLYGON|MULTIPOLYGON|GEOMETRYCOLLECTION)'; + $srid = 0; + $wkt = ''; + if (preg_match("/^'" . $geom_types . "\(.*\)',[0-9]*$/i", $value)) { + $last_comma = strripos($value, ","); + $srid = trim(substr($value, $last_comma + 1)); + $wkt = trim(substr($value, 1, $last_comma - 2)); + } elseif (preg_match("/^" . $geom_types . "\(.*\)$/i", $value)) { + $wkt = $value; + } + return array('srid' => $srid, 'wkt' => $wkt); + } + + /** + * Extracts points, scales and returns them as an array. + * + * @param string $point_set string of comma sperated points + * @param array $scale_data data related to scaling + * @param boolean $linear if true, as a 1D array, else as a 2D array + * + * @return array scaled points + * @access protected + */ + protected function extractPoints($point_set, $scale_data, $linear = false) + { + $points_arr = array(); + + // Seperate each point + $points = explode(",", $point_set); + + foreach ($points as $point) { + // Extract cordinates of the point + $cordinates = explode(" ", $point); + + if (isset($cordinates[0]) && trim($cordinates[0]) != '' + && isset($cordinates[1]) && trim($cordinates[1]) != '' + ) { + if ($scale_data != null) { + $x = ($cordinates[0] - $scale_data['x']) * $scale_data['scale']; + $y = $scale_data['height'] + - ($cordinates[1] - $scale_data['y']) * $scale_data['scale']; + } else { + $x = trim($cordinates[0]); + $y = trim($cordinates[1]); + } + } else { + $x = ''; + $y = ''; + } + + + if (! $linear) { + $points_arr[] = array($x, $y); + } else { + $points_arr[] = $x; + $points_arr[] = $y; + } + } + + return $points_arr; + } + + /** + * Generates JavaScript for adding an array of polygons to OpenLayers. + * + * @param array $polygons x and y coordinates for each polygon + * @param string $srid spatial reference id + * + * @return string JavaScript for adding an array of polygons to OpenLayers + * @access protected + */ + protected function getPolygonArrayForOpenLayers($polygons, $srid) + { + $ol_array = 'new Array('; + foreach ($polygons as $polygon) { + $rings = explode("),(", $polygon); + $ol_array .= $this->getPolygonForOpenLayers($rings, $srid) . ', '; + } + $ol_array = substr($ol_array, 0, strlen($ol_array) - 2); + $ol_array .= ')'; + + return $ol_array; + } + + /** + * Generates JavaScript for adding points for OpenLayers polygon. + * + * @param array $polygon x and y coordinates for each line + * @param string $srid spatial reference id + * + * @return string JavaScript for adding points for OpenLayers polygon + * @access protected + */ + protected function getPolygonForOpenLayers($polygon, $srid) + { + return 'new OpenLayers.Geometry.Polygon(' + . $this->getLineArrayForOpenLayers($polygon, $srid, false) + . ')'; + } + + /** + * Generates JavaScript for adding an array of LineString + * or LineRing to OpenLayers. + * + * @param array $lines x and y coordinates for each line + * @param string $srid spatial reference id + * @param bool $is_line_string whether it's an array of LineString + * + * @return string JavaScript for adding an array of LineString + * or LineRing to OpenLayers + * @access protected + */ + protected function getLineArrayForOpenLayers($lines, $srid, + $is_line_string = true + ) { + $ol_array = 'new Array('; + foreach ($lines as $line) { + $points_arr = $this->extractPoints($line, null); + $ol_array .= $this->getLineForOpenLayers( + $points_arr, $srid, $is_line_string + ); + $ol_array .= ', '; + } + $ol_array = substr($ol_array, 0, strlen($ol_array) - 2); + $ol_array .= ')'; + + return $ol_array; + } + + /** + * Generates JavaScript for adding a LineString or LineRing to OpenLayers. + * + * @param array $points_arr x and y coordinates for each point + * @param string $srid spatial reference id + * @param bool $is_line_string whether it's a LineString + * + * @return string JavaScript for adding a LineString or LineRing to OpenLayers + * @access protected + */ + protected function getLineForOpenLayers($points_arr, $srid, + $is_line_string = true + ) { + return 'new OpenLayers.Geometry.' + . ($is_line_string ? 'LineString' : 'LinearRing') . '(' + . $this->getPointsArrayForOpenLayers($points_arr, $srid) + . ')'; + } + + /** + * Generates JavaScript for adding an array of points to OpenLayers. + * + * @param array $points_arr x and y coordinates for each point + * @param string $srid spatial reference id + * + * @return string JavaScript for adding an array of points to OpenLayers + * @access protected + */ + protected function getPointsArrayForOpenLayers($points_arr, $srid) + { + $ol_array = 'new Array('; + foreach ($points_arr as $point) { + $ol_array .= $this->getPointForOpenLayers($point, $srid) . ', '; + } + $ol_array = substr($ol_array, 0, strlen($ol_array) - 2); + $ol_array .= ')'; + + return $ol_array; + } + + /** + * Generates JavaScript for adding a point to OpenLayers. + * + * @param array $point array containing the x and y coordinates of the point + * @param string $srid spatial reference id + * + * @return string JavaScript for adding points to OpenLayers + * @access protected + */ + protected function getPointForOpenLayers($point, $srid) + { + return '(new OpenLayers.Geometry.Point(' . $point[0] . ',' . $point[1] . '))' + . '.transform(new OpenLayers.Projection("EPSG:' + . $srid . '"), map.getProjectionObject())'; + } +} +?> diff --git a/sources/libraries/gis/pma_gis_geometrycollection.php b/sources/libraries/gis/pma_gis_geometrycollection.php new file mode 100644 index 0000000..09b04ed --- /dev/null +++ b/sources/libraries/gis/pma_gis_geometrycollection.php @@ -0,0 +1,336 @@ +_explodeGeomCol($goem_col); + + foreach ($sub_parts as $sub_part) { + $type_pos = stripos($sub_part, '('); + $type = substr($sub_part, 0, $type_pos); + + $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } + $scale_data = $gis_obj->scaleRow($sub_part); + + // Upadate minimum/maximum values for x and y cordinates. + $c_maxX = (float) $scale_data['maxX']; + if (! isset($min_max['maxX']) || $c_maxX > $min_max['maxX']) { + $min_max['maxX'] = $c_maxX; + } + + $c_minX = (float) $scale_data['minX']; + if (! isset($min_max['minX']) || $c_minX < $min_max['minX']) { + $min_max['minX'] = $c_minX; + } + + $c_maxY = (float) $scale_data['maxY']; + if (! isset($min_max['maxY']) || $c_maxY > $min_max['maxY']) { + $min_max['maxY'] = $c_maxY; + } + + $c_minY = (float) $scale_data['minY']; + if (! isset($min_max['minY']) || $c_minY < $min_max['minY']) { + $min_max['minY'] = $c_minY; + } + } + return $min_max; + } + + /** + * Adds to the PNG image object, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS GEOMETRYCOLLECTION object + * @param string $label label for the GIS GEOMETRYCOLLECTION object + * @param string $color color for the GIS GEOMETRYCOLLECTION object + * @param array $scale_data array containing data related to scaling + * @param object $image image object + * + * @return resource the modified image object + * @access public + */ + public function prepareRowAsPng($spatial, $label, $color, $scale_data, $image) + { + // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')' + $goem_col = substr($spatial, 19, (strlen($spatial) - 20)); + // Split the geometry collection object to get its constituents. + $sub_parts = $this->_explodeGeomCol($goem_col); + + foreach ($sub_parts as $sub_part) { + $type_pos = stripos($sub_part, '('); + $type = substr($sub_part, 0, $type_pos); + + $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } + $image = $gis_obj->prepareRowAsPng( + $sub_part, $label, $color, $scale_data, $image + ); + } + return $image; + } + + /** + * Adds to the TCPDF instance, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS GEOMETRYCOLLECTION object + * @param string $label label for the GIS GEOMETRYCOLLECTION object + * @param string $color color for the GIS GEOMETRYCOLLECTION object + * @param array $scale_data array containing data related to scaling + * @param TCPDF $pdf TCPDF instance + * + * @return TCPDF the modified TCPDF instance + * @access pubilc + */ + public function prepareRowAsPdf($spatial, $label, $color, $scale_data, $pdf) + { + // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')' + $goem_col = substr($spatial, 19, (strlen($spatial) - 20)); + // Split the geometry collection object to get its constituents. + $sub_parts = $this->_explodeGeomCol($goem_col); + + foreach ($sub_parts as $sub_part) { + $type_pos = stripos($sub_part, '('); + $type = substr($sub_part, 0, $type_pos); + + $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } + $pdf = $gis_obj->prepareRowAsPdf( + $sub_part, $label, $color, $scale_data, $pdf + ); + } + return $pdf; + } + + /** + * Prepares and returns the code related to a row in the GIS dataset as SVG. + * + * @param string $spatial GIS GEOMETRYCOLLECTION object + * @param string $label label for the GIS GEOMETRYCOLLECTION object + * @param string $color color for the GIS GEOMETRYCOLLECTION object + * @param array $scale_data array containing data related to scaling + * + * @return string the code related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsSvg($spatial, $label, $color, $scale_data) + { + $row = ''; + + // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')' + $goem_col = substr($spatial, 19, (strlen($spatial) - 20)); + // Split the geometry collection object to get its constituents. + $sub_parts = $this->_explodeGeomCol($goem_col); + + foreach ($sub_parts as $sub_part) { + $type_pos = stripos($sub_part, '('); + $type = substr($sub_part, 0, $type_pos); + + $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } + $row .= $gis_obj->prepareRowAsSvg( + $sub_part, $label, $color, $scale_data + ); + } + return $row; + } + + /** + * Prepares JavaScript related to a row in the GIS dataset + * to visualize it with OpenLayers. + * + * @param string $spatial GIS GEOMETRYCOLLECTION object + * @param int $srid spatial reference ID + * @param string $label label for the GIS GEOMETRYCOLLECTION object + * @param string $color color for the GIS GEOMETRYCOLLECTION object + * @param array $scale_data array containing data related to scaling + * + * @return string JavaScript related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsOl($spatial, $srid, $label, $color, $scale_data) + { + $row = ''; + + // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')' + $goem_col = substr($spatial, 19, (strlen($spatial) - 20)); + // Split the geometry collection object to get its constituents. + $sub_parts = $this->_explodeGeomCol($goem_col); + + foreach ($sub_parts as $sub_part) { + $type_pos = stripos($sub_part, '('); + $type = substr($sub_part, 0, $type_pos); + + $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } + $row .= $gis_obj->prepareRowAsOl( + $sub_part, $srid, $label, $color, $scale_data + ); + } + return $row; + } + + /** + * Splits the GEOMETRYCOLLECTION object and get its constituents. + * + * @param string $goem_col geometry collection string + * + * @return array the constituents of the geometry collection object + * @access private + */ + private function _explodeGeomCol($goem_col) + { + $sub_parts = array(); + $br_count = 0; + $start = 0; + $count = 0; + foreach (str_split($goem_col) as $char) { + if ($char == '(') { + $br_count++; + } elseif ($char == ')') { + $br_count--; + if ($br_count == 0) { + $sub_parts[] = substr($goem_col, $start, ($count + 1 - $start)); + $start = $count + 2; + } + } + $count++; + } + return $sub_parts; + } + + /** + * Generates the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index index into the parameter object + * @param string $empty value for empty points + * + * @return string WKT with the set of parameters passed by the GIS editor + * @access public + */ + public function generateWkt($gis_data, $index, $empty = '') + { + $geom_count = (isset($gis_data['GEOMETRYCOLLECTION']['geom_count'])) + ? $gis_data['GEOMETRYCOLLECTION']['geom_count'] : 1; + $wkt = 'GEOMETRYCOLLECTION('; + for ($i = 0; $i < $geom_count; $i++) { + if (isset($gis_data[$i]['gis_type'])) { + $type = $gis_data[$i]['gis_type']; + $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } + $wkt .= $gis_obj->generateWkt($gis_data, $i, $empty) . ','; + } + } + if (isset($gis_data[0]['gis_type'])) { + $wkt = substr($wkt, 0, strlen($wkt) - 1); + } + $wkt .= ')'; + return $wkt; + } + + /** + * Generates parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value of the GIS column + * + * @return array parameters for the GIS editor from the value of the GIS column + * @access public + */ + public function generateParams($value) + { + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + + // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')' + $goem_col = substr($wkt, 19, (strlen($wkt) - 20)); + // Split the geometry collection object to get its constituents. + $sub_parts = $this->_explodeGeomCol($goem_col); + $params['GEOMETRYCOLLECTION']['geom_count'] = count($sub_parts); + + $i = 0; + foreach ($sub_parts as $sub_part) { + $type_pos = stripos($sub_part, '('); + $type = substr($sub_part, 0, $type_pos); + $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } + $params = array_merge($params, $gis_obj->generateParams($sub_part, $i)); + $i++; + } + return $params; + } +} +?> diff --git a/sources/libraries/gis/pma_gis_linestring.php b/sources/libraries/gis/pma_gis_linestring.php new file mode 100644 index 0000000..306e88b --- /dev/null +++ b/sources/libraries/gis/pma_gis_linestring.php @@ -0,0 +1,298 @@ +setMinMax($linesrting, array()); + } + + /** + * Adds to the PNG image object, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS LINESTRING object + * @param string $label Label for the GIS LINESTRING object + * @param string $line_color Color for the GIS LINESTRING object + * @param array $scale_data Array containing data related to scaling + * @param object $image Image object + * + * @return resource the modified image object + * @access public + */ + public function prepareRowAsPng($spatial, $label, $line_color, + $scale_data, $image + ) { + // allocate colors + $black = imagecolorallocate($image, 0, 0, 0); + $red = hexdec(substr($line_color, 1, 2)); + $green = hexdec(substr($line_color, 3, 2)); + $blue = hexdec(substr($line_color, 4, 2)); + $color = imagecolorallocate($image, $red, $green, $blue); + + // Trim to remove leading 'LINESTRING(' and trailing ')' + $linesrting = substr($spatial, 11, (strlen($spatial) - 12)); + $points_arr = $this->extractPoints($linesrting, $scale_data); + + foreach ($points_arr as $point) { + if (! isset($temp_point)) { + $temp_point = $point; + } else { + // draw line section + imageline( + $image, $temp_point[0], $temp_point[1], + $point[0], $point[1], $color + ); + $temp_point = $point; + } + } + // print label if applicable + if (isset($label) && trim($label) != '') { + imagestring( + $image, 1, $points_arr[1][0], + $points_arr[1][1], trim($label), $black + ); + } + return $image; + } + + /** + * Adds to the TCPDF instance, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS LINESTRING object + * @param string $label Label for the GIS LINESTRING object + * @param string $line_color Color for the GIS LINESTRING object + * @param array $scale_data Array containing data related to scaling + * @param TCPDF $pdf TCPDF instance + * + * @return TCPDF the modified TCPDF instance + * @access public + */ + public function prepareRowAsPdf($spatial, $label, $line_color, $scale_data, $pdf) + { + // allocate colors + $red = hexdec(substr($line_color, 1, 2)); + $green = hexdec(substr($line_color, 3, 2)); + $blue = hexdec(substr($line_color, 4, 2)); + $line = array('width' => 1.5, 'color' => array($red, $green, $blue)); + + // Trim to remove leading 'LINESTRING(' and trailing ')' + $linesrting = substr($spatial, 11, (strlen($spatial) - 12)); + $points_arr = $this->extractPoints($linesrting, $scale_data); + + foreach ($points_arr as $point) { + if (! isset($temp_point)) { + $temp_point = $point; + } else { + // draw line section + $pdf->Line( + $temp_point[0], $temp_point[1], + $point[0], $point[1], $line + ); + $temp_point = $point; + } + } + // print label + if (isset($label) && trim($label) != '') { + $pdf->SetXY($points_arr[1][0], $points_arr[1][1]); + $pdf->SetFontSize(5); + $pdf->Cell(0, 0, trim($label)); + } + return $pdf; + } + + /** + * Prepares and returns the code related to a row in the GIS dataset as SVG. + * + * @param string $spatial GIS LINESTRING object + * @param string $label Label for the GIS LINESTRING object + * @param string $line_color Color for the GIS LINESTRING object + * @param array $scale_data Array containing data related to scaling + * + * @return string the code related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsSvg($spatial, $label, $line_color, $scale_data) + { + $line_options = array( + 'name' => $label, + 'id' => $label . rand(), + 'class' => 'linestring vector', + 'fill' => 'none', + 'stroke' => $line_color, + 'stroke-width'=> 2, + ); + + // Trim to remove leading 'LINESTRING(' and trailing ')' + $linesrting = substr($spatial, 11, (strlen($spatial) - 12)); + $points_arr = $this->extractPoints($linesrting, $scale_data); + + $row = ' $val) { + $row .= ' ' . $option . '="' . trim($val) . '"'; + } + $row .= '/>'; + + return $row; + } + + /** + * Prepares JavaScript related to a row in the GIS dataset + * to visualize it with OpenLayers. + * + * @param string $spatial GIS LINESTRING object + * @param int $srid Spatial reference ID + * @param string $label Label for the GIS LINESTRING object + * @param string $line_color Color for the GIS LINESTRING object + * @param array $scale_data Array containing data related to scaling + * + * @return string JavaScript related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsOl($spatial, $srid, $label, $line_color, $scale_data) + { + $style_options = array( + 'strokeColor' => $line_color, + 'strokeWidth' => 2, + 'label' => $label, + 'fontSize' => 10, + ); + if ($srid == 0) { + $srid = 4326; + } + $result = $this->getBoundsForOl($srid, $scale_data); + + // Trim to remove leading 'LINESTRING(' and trailing ')' + $linesrting = substr($spatial, 11, (strlen($spatial) - 12)); + $points_arr = $this->extractPoints($linesrting, null); + + $result .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector(' + . $this->getLineForOpenLayers($points_arr, $srid) + . ', null, ' . json_encode($style_options) . '));'; + return $result; + } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Value for empty points + * + * @return string WKT with the set of parameters passed by the GIS editor + * @access public + */ + public function generateWkt($gis_data, $index, $empty = '') + { + $no_of_points = isset($gis_data[$index]['LINESTRING']['no_of_points']) + ? $gis_data[$index]['LINESTRING']['no_of_points'] : 2; + if ($no_of_points < 2) { + $no_of_points = 2; + } + $wkt = 'LINESTRING('; + for ($i = 0; $i < $no_of_points; $i++) { + $wkt .= ((isset($gis_data[$index]['LINESTRING'][$i]['x']) + && trim($gis_data[$index]['LINESTRING'][$i]['x']) != '') + ? $gis_data[$index]['LINESTRING'][$i]['x'] : $empty) + . ' ' . ((isset($gis_data[$index]['LINESTRING'][$i]['y']) + && trim($gis_data[$index]['LINESTRING'][$i]['y']) != '') + ? $gis_data[$index]['LINESTRING'][$i]['y'] : $empty) .','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value of the GIS column + * @param int $index of the geometry + * + * @return array params for the GIS data editor from the value of the GIS column + * @access public + */ + public function generateParams($value, $index = -1) + { + if ($index == -1) { + $index = 0; + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + } else { + $params[$index]['gis_type'] = 'LINESTRING'; + $wkt = $value; + } + + // Trim to remove leading 'LINESTRING(' and trailing ')' + $linestring = substr($wkt, 11, (strlen($wkt) - 12)); + $points_arr = $this->extractPoints($linestring, null); + + $no_of_points = count($points_arr); + $params[$index]['LINESTRING']['no_of_points'] = $no_of_points; + for ($i = 0; $i < $no_of_points; $i++) { + $params[$index]['LINESTRING'][$i]['x'] = $points_arr[$i][0]; + $params[$index]['LINESTRING'][$i]['y'] = $points_arr[$i][1]; + } + + return $params; + } +} +?> diff --git a/sources/libraries/gis/pma_gis_multilinestring.php b/sources/libraries/gis/pma_gis_multilinestring.php new file mode 100644 index 0000000..f6e8563 --- /dev/null +++ b/sources/libraries/gis/pma_gis_multilinestring.php @@ -0,0 +1,370 @@ +setMinMax($linestring, $min_max); + } + + return $min_max; + } + + /** + * Adds to the PNG image object, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS MULTILINESTRING object + * @param string $label Label for the GIS MULTILINESTRING object + * @param string $line_color Color for the GIS MULTILINESTRING object + * @param array $scale_data Array containing data related to scaling + * @param object $image Image object + * + * @return object the modified image object + * @access public + */ + public function prepareRowAsPng($spatial, $label, $line_color, + $scale_data, $image + ) { + // allocate colors + $black = imagecolorallocate($image, 0, 0, 0); + $red = hexdec(substr($line_color, 1, 2)); + $green = hexdec(substr($line_color, 3, 2)); + $blue = hexdec(substr($line_color, 4, 2)); + $color = imagecolorallocate($image, $red, $green, $blue); + + // Trim to remove leading 'MULTILINESTRING((' and trailing '))' + $multilinestirng = substr($spatial, 17, (strlen($spatial) - 19)); + // Seperate each linestring + $linestirngs = explode("),(", $multilinestirng); + + $first_line = true; + foreach ($linestirngs as $linestring) { + $points_arr = $this->extractPoints($linestring, $scale_data); + foreach ($points_arr as $point) { + if (! isset($temp_point)) { + $temp_point = $point; + } else { + // draw line section + imageline( + $image, $temp_point[0], $temp_point[1], + $point[0], $point[1], $color + ); + $temp_point = $point; + } + } + unset($temp_point); + // print label if applicable + if (isset($label) && trim($label) != '' && $first_line) { + imagestring( + $image, 1, $points_arr[1][0], + $points_arr[1][1], trim($label), $black + ); + } + $first_line = false; + } + return $image; + } + + /** + * Adds to the TCPDF instance, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS MULTILINESTRING object + * @param string $label Label for the GIS MULTILINESTRING object + * @param string $line_color Color for the GIS MULTILINESTRING object + * @param array $scale_data Array containing data related to scaling + * @param TCPDF $pdf TCPDF instance + * + * @return TCPDF the modified TCPDF instance + * @access public + */ + public function prepareRowAsPdf($spatial, $label, $line_color, $scale_data, $pdf) + { + // allocate colors + $red = hexdec(substr($line_color, 1, 2)); + $green = hexdec(substr($line_color, 3, 2)); + $blue = hexdec(substr($line_color, 4, 2)); + $line = array('width' => 1.5, 'color' => array($red, $green, $blue)); + + // Trim to remove leading 'MULTILINESTRING((' and trailing '))' + $multilinestirng = substr($spatial, 17, (strlen($spatial) - 19)); + // Seperate each linestring + $linestirngs = explode("),(", $multilinestirng); + + $first_line = true; + foreach ($linestirngs as $linestring) { + $points_arr = $this->extractPoints($linestring, $scale_data); + foreach ($points_arr as $point) { + if (! isset($temp_point)) { + $temp_point = $point; + } else { + // draw line section + $pdf->Line( + $temp_point[0], $temp_point[1], $point[0], $point[1], $line + ); + $temp_point = $point; + } + } + unset($temp_point); + // print label + if (isset($label) && trim($label) != '' && $first_line) { + $pdf->SetXY($points_arr[1][0], $points_arr[1][1]); + $pdf->SetFontSize(5); + $pdf->Cell(0, 0, trim($label)); + } + $first_line = false; + } + return $pdf; + } + + /** + * Prepares and returns the code related to a row in the GIS dataset as SVG. + * + * @param string $spatial GIS MULTILINESTRING object + * @param string $label Label for the GIS MULTILINESTRING object + * @param string $line_color Color for the GIS MULTILINESTRING object + * @param array $scale_data Array containing data related to scaling + * + * @return string the code related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsSvg($spatial, $label, $line_color, $scale_data) + { + $line_options = array( + 'name' => $label, + 'class' => 'linestring vector', + 'fill' => 'none', + 'stroke' => $line_color, + 'stroke-width'=> 2, + ); + + // Trim to remove leading 'MULTILINESTRING((' and trailing '))' + $multilinestirng = substr($spatial, 17, (strlen($spatial) - 19)); + // Seperate each linestring + $linestirngs = explode("),(", $multilinestirng); + + $row = ''; + foreach ($linestirngs as $linestring) { + $points_arr = $this->extractPoints($linestring, $scale_data); + + $row .= ' $val) { + $row .= ' ' . $option . '="' . trim($val) . '"'; + } + $row .= '/>'; + } + + return $row; + } + + /** + * Prepares JavaScript related to a row in the GIS dataset + * to visualize it with OpenLayers. + * + * @param string $spatial GIS MULTILINESTRING object + * @param int $srid Spatial reference ID + * @param string $label Label for the GIS MULTILINESTRING object + * @param string $line_color Color for the GIS MULTILINESTRING object + * @param array $scale_data Array containing data related to scaling + * + * @return string JavaScript related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsOl($spatial, $srid, $label, $line_color, $scale_data) + { + $style_options = array( + 'strokeColor' => $line_color, + 'strokeWidth' => 2, + 'label' => $label, + 'fontSize' => 10, + ); + if ($srid == 0) { + $srid = 4326; + } + $row = $this->getBoundsForOl($srid, $scale_data); + + // Trim to remove leading 'MULTILINESTRING((' and trailing '))' + $multilinestirng = substr($spatial, 17, (strlen($spatial) - 19)); + // Seperate each linestring + $linestirngs = explode("),(", $multilinestirng); + + $row .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector(' + . 'new OpenLayers.Geometry.MultiLineString(' + . $this->getLineArrayForOpenLayers($linestirngs, $srid) + . '), null, ' . json_encode($style_options) . '));'; + return $row; + } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Value for empty points + * + * @return string WKT with the set of parameters passed by the GIS editor + * @access public + */ + public function generateWkt($gis_data, $index, $empty = '') + { + $data_row = $gis_data[$index]['MULTILINESTRING']; + + $no_of_lines = isset($data_row['no_of_lines']) + ? $data_row['no_of_lines'] : 1; + if ($no_of_lines < 1) { + $no_of_lines = 1; + } + $wkt = 'MULTILINESTRING('; + for ($i = 0; $i < $no_of_lines; $i++) { + $no_of_points = isset($data_row[$i]['no_of_points']) + ? $data_row[$i]['no_of_points'] : 2; + if ($no_of_points < 2) { + $no_of_points = 2; + } + $wkt .= '('; + for ($j = 0; $j < $no_of_points; $j++) { + $wkt .= ((isset($data_row[$i][$j]['x']) + && trim($data_row[$i][$j]['x']) != '') + ? $data_row[$i][$j]['x'] : $empty) + . ' ' . ((isset($data_row[$i][$j]['y']) + && trim($data_row[$i][$j]['y']) != '') + ? $data_row[$i][$j]['y'] : $empty) . ','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= '),'; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Generate the WKT for the data from ESRI shape files. + * + * @param array $row_data GIS data + * + * @return string the WKT for the data from ESRI shape files + * @access public + */ + public function getShape($row_data) + { + $wkt = 'MULTILINESTRING('; + for ($i = 0; $i < $row_data['numparts']; $i++) { + $wkt .= '('; + foreach ($row_data['parts'][$i]['points'] as $point) { + $wkt .= $point['x'] . ' ' . $point['y'] . ','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= '),'; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value Value of the GIS column + * @param int $index Index of the geometry + * + * @return array params for the GIS data editor from the value of the GIS column + * @access public + */ + public function generateParams($value, $index = -1) + { + if ($index == -1) { + $index = 0; + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + } else { + $params[$index]['gis_type'] = 'MULTILINESTRING'; + $wkt = $value; + } + + // Trim to remove leading 'MULTILINESTRING((' and trailing '))' + $multilinestirng = substr($wkt, 17, (strlen($wkt) - 19)); + // Seperate each linestring + $linestirngs = explode("),(", $multilinestirng); + $params[$index]['MULTILINESTRING']['no_of_lines'] = count($linestirngs); + + $j = 0; + foreach ($linestirngs as $linestring) { + $points_arr = $this->extractPoints($linestring, null); + $no_of_points = count($points_arr); + $params[$index]['MULTILINESTRING'][$j]['no_of_points'] = $no_of_points; + for ($i = 0; $i < $no_of_points; $i++) { + $params[$index]['MULTILINESTRING'][$j][$i]['x'] = $points_arr[$i][0]; + $params[$index]['MULTILINESTRING'][$j][$i]['y'] = $points_arr[$i][1]; + } + $j++; + } + return $params; + } +} +?> diff --git a/sources/libraries/gis/pma_gis_multipoint.php b/sources/libraries/gis/pma_gis_multipoint.php new file mode 100644 index 0000000..6ef57dd --- /dev/null +++ b/sources/libraries/gis/pma_gis_multipoint.php @@ -0,0 +1,343 @@ +setMinMax($multipoint, array()); + } + + /** + * Adds to the PNG image object, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS MULTIPOINT object + * @param string $label Label for the GIS MULTIPOINT object + * @param string $point_color Color for the GIS MULTIPOINT object + * @param array $scale_data Array containing data related to scaling + * @param object $image Image object + * + * @return object the modified image object + * @access public + */ + public function prepareRowAsPng($spatial, $label, $point_color, + $scale_data, $image + ) { + // allocate colors + $black = imagecolorallocate($image, 0, 0, 0); + $red = hexdec(substr($point_color, 1, 2)); + $green = hexdec(substr($point_color, 3, 2)); + $blue = hexdec(substr($point_color, 4, 2)); + $color = imagecolorallocate($image, $red, $green, $blue); + + // Trim to remove leading 'MULTIPOINT(' and trailing ')' + $multipoint = substr($spatial, 11, (strlen($spatial) - 12)); + $points_arr = $this->extractPoints($multipoint, $scale_data); + + foreach ($points_arr as $point) { + // draw a small circle to mark the point + if ($point[0] != '' && $point[1] != '') { + imagearc($image, $point[0], $point[1], 7, 7, 0, 360, $color); + } + } + // print label for each point + if ((isset($label) && trim($label) != '') + && ($points_arr[0][0] != '' && $points_arr[0][1] != '') + ) { + imagestring( + $image, 1, $points_arr[0][0], $points_arr[0][1], trim($label), $black + ); + } + return $image; + } + + /** + * Adds to the TCPDF instance, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS MULTIPOINT object + * @param string $label Label for the GIS MULTIPOINT object + * @param string $point_color Color for the GIS MULTIPOINT object + * @param array $scale_data Array containing data related to scaling + * @param TCPDF $pdf TCPDF instance + * + * @return TCPDF the modified TCPDF instance + * @access public + */ + public function prepareRowAsPdf($spatial, $label, $point_color, + $scale_data, $pdf + ) { + // allocate colors + $red = hexdec(substr($point_color, 1, 2)); + $green = hexdec(substr($point_color, 3, 2)); + $blue = hexdec(substr($point_color, 4, 2)); + $line = array('width' => 1.25, 'color' => array($red, $green, $blue)); + + // Trim to remove leading 'MULTIPOINT(' and trailing ')' + $multipoint = substr($spatial, 11, (strlen($spatial) - 12)); + $points_arr = $this->extractPoints($multipoint, $scale_data); + + foreach ($points_arr as $point) { + // draw a small circle to mark the point + if ($point[0] != '' && $point[1] != '') { + $pdf->Circle($point[0], $point[1], 2, 0, 360, 'D', $line); + } + } + // print label for each point + if ((isset($label) && trim($label) != '') + && ($points_arr[0][0] != '' && $points_arr[0][1] != '') + ) { + $pdf->SetXY($points_arr[0][0], $points_arr[0][1]); + $pdf->SetFontSize(5); + $pdf->Cell(0, 0, trim($label)); + } + return $pdf; + } + + /** + * Prepares and returns the code related to a row in the GIS dataset as SVG. + * + * @param string $spatial GIS MULTIPOINT object + * @param string $label Label for the GIS MULTIPOINT object + * @param string $point_color Color for the GIS MULTIPOINT object + * @param array $scale_data Array containing data related to scaling + * + * @return string the code related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsSvg($spatial, $label, $point_color, $scale_data) + { + $point_options = array( + 'name' => $label, + 'class' => 'multipoint vector', + 'fill' => 'white', + 'stroke' => $point_color, + 'stroke-width'=> 2, + ); + + // Trim to remove leading 'MULTIPOINT(' and trailing ')' + $multipoint = substr($spatial, 11, (strlen($spatial) - 12)); + $points_arr = $this->extractPoints($multipoint, $scale_data); + + $row = ''; + foreach ($points_arr as $point) { + if ($point[0] != '' && $point[1] != '') { + $row .= ' $val) { + $row .= ' ' . $option . '="' . trim($val) . '"'; + } + $row .= '/>'; + } + } + + return $row; + } + + /** + * Prepares JavaScript related to a row in the GIS dataset + * to visualize it with OpenLayers. + * + * @param string $spatial GIS MULTIPOINT object + * @param int $srid Spatial reference ID + * @param string $label Label for the GIS MULTIPOINT object + * @param string $point_color Color for the GIS MULTIPOINT object + * @param array $scale_data Array containing data related to scaling + * + * @return string JavaScript related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsOl($spatial, $srid, $label, + $point_color, $scale_data + ) { + $style_options = array( + 'pointRadius' => 3, + 'fillColor' => '#ffffff', + 'strokeColor' => $point_color, + 'strokeWidth' => 2, + 'label' => $label, + 'labelYOffset' => -8, + 'fontSize' => 10, + ); + if ($srid == 0) { + $srid = 4326; + } + $result = $this->getBoundsForOl($srid, $scale_data); + + // Trim to remove leading 'MULTIPOINT(' and trailing ')' + $multipoint = substr($spatial, 11, (strlen($spatial) - 12)); + $points_arr = $this->extractPoints($multipoint, null); + + $result .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector(' + . 'new OpenLayers.Geometry.MultiPoint(' + . $this->getPointsArrayForOpenLayers($points_arr, $srid) + . '), null, ' . json_encode($style_options) . '));'; + return $result; + } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Multipoint does not adhere to this + * + * @return string WKT with the set of parameters passed by the GIS editor + * @access public + */ + public function generateWkt($gis_data, $index, $empty = '') + { + $no_of_points = isset($gis_data[$index]['MULTIPOINT']['no_of_points']) + ? $gis_data[$index]['MULTIPOINT']['no_of_points'] : 1; + if ($no_of_points < 1) { + $no_of_points = 1; + } + $wkt = 'MULTIPOINT('; + for ($i = 0; $i < $no_of_points; $i++) { + $wkt .= ((isset($gis_data[$index]['MULTIPOINT'][$i]['x']) + && trim($gis_data[$index]['MULTIPOINT'][$i]['x']) != '') + ? $gis_data[$index]['MULTIPOINT'][$i]['x'] : '') + . ' ' . ((isset($gis_data[$index]['MULTIPOINT'][$i]['y']) + && trim($gis_data[$index]['MULTIPOINT'][$i]['y']) != '') + ? $gis_data[$index]['MULTIPOINT'][$i]['y'] : '') . ','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Generate the WKT for the data from ESRI shape files. + * + * @param array $row_data GIS data + * + * @return string the WKT for the data from ESRI shape files + * @access public + */ + public function getShape($row_data) + { + $wkt = 'MULTIPOINT('; + for ($i = 0; $i < $row_data['numpoints']; $i++) { + $wkt .= $row_data['points'][$i]['x'] . ' ' + . $row_data['points'][$i]['y'] . ','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value Value of the GIS column + * @param integer $index Index of the geometry + * + * @return array params for the GIS data editor from the value of the GIS column + * @access public + */ + public function generateParams($value, $index = -1) + { + if ($index == -1) { + $index = 0; + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + } else { + $params[$index]['gis_type'] = 'MULTIPOINT'; + $wkt = $value; + } + + // Trim to remove leading 'MULTIPOINT(' and trailing ')' + $points = substr($wkt, 11, (strlen($wkt) - 12)); + $points_arr = $this->extractPoints($points, null); + + $no_of_points = count($points_arr); + $params[$index]['MULTIPOINT']['no_of_points'] = $no_of_points; + for ($i = 0; $i < $no_of_points; $i++) { + $params[$index]['MULTIPOINT'][$i]['x'] = $points_arr[$i][0]; + $params[$index]['MULTIPOINT'][$i]['y'] = $points_arr[$i][1]; + } + + return $params; + } + + /** + * Overidden to make sure that only the points having valid values + * for x and y coordinates are added. + * + * @param array $points_arr x and y coordinates for each point + * @param string $srid spatial reference id + * + * @return string JavaScript for adding an array of points to OpenLayers + * @access protected + */ + protected function getPointsArrayForOpenLayers($points_arr, $srid) + { + $ol_array = 'new Array('; + foreach ($points_arr as $point) { + if ($point[0] != '' && $point[1] != '') { + $ol_array .= $this->getPointForOpenLayers($point, $srid) . ', '; + } + } + if (substr($ol_array, strlen($ol_array) - 2) == ', ') { + $ol_array = substr($ol_array, 0, strlen($ol_array) - 2); + } + $ol_array .= ')'; + + return $ol_array; + } +} +?> diff --git a/sources/libraries/gis/pma_gis_multipolygon.php b/sources/libraries/gis/pma_gis_multipolygon.php new file mode 100644 index 0000000..7afbff3 --- /dev/null +++ b/sources/libraries/gis/pma_gis_multipolygon.php @@ -0,0 +1,527 @@ +setMinMax($ring, $min_max); + } + + return $min_max; + } + + /** + * Adds to the PNG image object, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS MULTIPOLYGON object + * @param string $label Label for the GIS MULTIPOLYGON object + * @param string $fill_color Color for the GIS MULTIPOLYGON object + * @param array $scale_data Array containing data related to scaling + * @param object $image Image object + * + * @return object the modified image object + * @access public + */ + public function prepareRowAsPng($spatial, $label, $fill_color, + $scale_data, $image + ) { + // allocate colors + $black = imagecolorallocate($image, 0, 0, 0); + $red = hexdec(substr($fill_color, 1, 2)); + $green = hexdec(substr($fill_color, 3, 2)); + $blue = hexdec(substr($fill_color, 4, 2)); + $color = imagecolorallocate($image, $red, $green, $blue); + + // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))' + $multipolygon = substr($spatial, 15, (strlen($spatial) - 18)); + // Seperate each polygon + $polygons = explode(")),((", $multipolygon); + + $first_poly = true; + foreach ($polygons as $polygon) { + // If the polygon doesnt have an inner polygon + if (strpos($polygon, "),(") === false) { + $points_arr = $this->extractPoints($polygon, $scale_data, true); + } else { + // Seperate outer and inner polygons + $parts = explode("),(", $polygon); + $outer = $parts[0]; + $inner = array_slice($parts, 1); + + $points_arr = $this->extractPoints($outer, $scale_data, true); + + foreach ($inner as $inner_poly) { + $points_arr = array_merge( + $points_arr, + $this->extractPoints($inner_poly, $scale_data, true) + ); + } + } + // draw polygon + imagefilledpolygon($image, $points_arr, sizeof($points_arr) / 2, $color); + // mark label point if applicable + if (isset($label) && trim($label) != '' && $first_poly) { + $label_point = array($points_arr[2], $points_arr[3]); + } + $first_poly = false; + } + // print label if applicable + if (isset($label_point)) { + imagestring( + $image, 1, $points_arr[2], $points_arr[3], trim($label), $black + ); + } + return $image; + } + + /** + * Adds to the TCPDF instance, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS MULTIPOLYGON object + * @param string $label Label for the GIS MULTIPOLYGON object + * @param string $fill_color Color for the GIS MULTIPOLYGON object + * @param array $scale_data Array containing data related to scaling + * @param TCPDF $pdf TCPDF instance + * + * @return TCPDF the modified TCPDF instance + * @access public + */ + public function prepareRowAsPdf($spatial, $label, $fill_color, $scale_data, $pdf) + { + // allocate colors + $red = hexdec(substr($fill_color, 1, 2)); + $green = hexdec(substr($fill_color, 3, 2)); + $blue = hexdec(substr($fill_color, 4, 2)); + $color = array($red, $green, $blue); + + // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))' + $multipolygon = substr($spatial, 15, (strlen($spatial) - 18)); + // Seperate each polygon + $polygons = explode(")),((", $multipolygon); + + $first_poly = true; + foreach ($polygons as $polygon) { + // If the polygon doesnt have an inner polygon + if (strpos($polygon, "),(") === false) { + $points_arr = $this->extractPoints($polygon, $scale_data, true); + } else { + // Seperate outer and inner polygons + $parts = explode("),(", $polygon); + $outer = $parts[0]; + $inner = array_slice($parts, 1); + + $points_arr = $this->extractPoints($outer, $scale_data, true); + + foreach ($inner as $inner_poly) { + $points_arr = array_merge( + $points_arr, + $this->extractPoints($inner_poly, $scale_data, true) + ); + } + } + // draw polygon + $pdf->Polygon($points_arr, 'F*', array(), $color, true); + // mark label point if applicable + if (isset($label) && trim($label) != '' && $first_poly) { + $label_point = array($points_arr[2], $points_arr[3]); + } + $first_poly = false; + } + + // print label if applicable + if (isset($label_point)) { + $pdf->SetXY($label_point[0], $label_point[1]); + $pdf->SetFontSize(5); + $pdf->Cell(0, 0, trim($label)); + } + return $pdf; + } + + /** + * Prepares and returns the code related to a row in the GIS dataset as SVG. + * + * @param string $spatial GIS MULTIPOLYGON object + * @param string $label Label for the GIS MULTIPOLYGON object + * @param string $fill_color Color for the GIS MULTIPOLYGON object + * @param array $scale_data Array containing data related to scaling + * + * @return string the code related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsSvg($spatial, $label, $fill_color, $scale_data) + { + $polygon_options = array( + 'name' => $label, + 'class' => 'multipolygon vector', + 'stroke' => 'black', + 'stroke-width'=> 0.5, + 'fill' => $fill_color, + 'fill-rule' => 'evenodd', + 'fill-opacity'=> 0.8, + ); + + $row = ''; + + // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))' + $multipolygon = substr($spatial, 15, (strlen($spatial) - 18)); + // Seperate each polygon + $polygons = explode(")),((", $multipolygon); + + foreach ($polygons as $polygon) { + $row .= '_drawPath($polygon, $scale_data); + } else { + // Seperate outer and inner polygons + $parts = explode("),(", $polygon); + $outer = $parts[0]; + $inner = array_slice($parts, 1); + + $row .= $this->_drawPath($outer, $scale_data); + + foreach ($inner as $inner_poly) { + $row .= $this->_drawPath($inner_poly, $scale_data); + } + } + $polygon_options['id'] = $label . rand(); + $row .= '"'; + foreach ($polygon_options as $option => $val) { + $row .= ' ' . $option . '="' . trim($val) . '"'; + } + $row .= '/>'; + } + + return $row; + } + + /** + * Prepares JavaScript related to a row in the GIS dataset + * to visualize it with OpenLayers. + * + * @param string $spatial GIS MULTIPOLYGON object + * @param int $srid Spatial reference ID + * @param string $label Label for the GIS MULTIPOLYGON object + * @param string $fill_color Color for the GIS MULTIPOLYGON object + * @param array $scale_data Array containing data related to scaling + * + * @return string JavaScript related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsOl($spatial, $srid, $label, $fill_color, $scale_data) + { + $style_options = array( + 'strokeColor' => '#000000', + 'strokeWidth' => 0.5, + 'fillColor' => $fill_color, + 'fillOpacity' => 0.8, + 'label' => $label, + 'fontSize' => 10, + ); + if ($srid == 0) { + $srid = 4326; + } + $row = $this->getBoundsForOl($srid, $scale_data); + + // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))' + $multipolygon = substr($spatial, 15, (strlen($spatial) - 18)); + // Seperate each polygon + $polygons = explode(")),((", $multipolygon); + + $row .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector(' + . 'new OpenLayers.Geometry.MultiPolygon(' + . $this->getPolygonArrayForOpenLayers($polygons, $srid) + . '), null, ' . json_encode($style_options) . '));'; + return $row; + } + + /** + * Draws a ring of the polygon using SVG path element. + * + * @param string $polygon The ring + * @param array $scale_data Array containing data related to scaling + * + * @return string the code to draw the ring + * @access private + */ + private function _drawPath($polygon, $scale_data) + { + $points_arr = $this->extractPoints($polygon, $scale_data); + + $row = ' M ' . $points_arr[0][0] . ', ' . $points_arr[0][1]; + $other_points = array_slice($points_arr, 1, count($points_arr) - 2); + foreach ($other_points as $point) { + $row .= ' L ' . $point[0] . ', ' . $point[1]; + } + $row .= ' Z '; + + return $row; + } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Value for empty points + * + * @return string WKT with the set of parameters passed by the GIS editor + * @access public + */ + public function generateWkt($gis_data, $index, $empty = '') + { + $data_row = $gis_data[$index]['MULTIPOLYGON']; + + $no_of_polygons = isset($data_row['no_of_polygons']) + ? $data_row['no_of_polygons'] : 1; + if ($no_of_polygons < 1) { + $no_of_polygons = 1; + } + + $wkt = 'MULTIPOLYGON('; + for ($k = 0; $k < $no_of_polygons; $k++) { + $no_of_lines = isset($data_row[$k]['no_of_lines']) + ? $data_row[$k]['no_of_lines'] : 1; + if ($no_of_lines < 1) { + $no_of_lines = 1; + } + $wkt .= '('; + for ($i = 0; $i < $no_of_lines; $i++) { + $no_of_points = isset($data_row[$k][$i]['no_of_points']) + ? $data_row[$k][$i]['no_of_points'] : 4; + if ($no_of_points < 4) { + $no_of_points = 4; + } + $wkt .= '('; + for ($j = 0; $j < $no_of_points; $j++) { + $wkt .= ((isset($data_row[$k][$i][$j]['x']) + && trim($data_row[$k][$i][$j]['x']) != '') + ? $data_row[$k][$i][$j]['x'] : $empty) + . ' ' . ((isset($data_row[$k][$i][$j]['y']) + && trim($data_row[$k][$i][$j]['y']) != '') + ? $data_row[$k][$i][$j]['y'] : $empty) .','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= '),'; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= '),'; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Generate the WKT for the data from ESRI shape files. + * + * @param array $row_data GIS data + * + * @return string the WKT for the data from ESRI shape files + * @access public + */ + public function getShape($row_data) + { + // Determines whether each line ring is an inner ring or an outer ring. + // If it's an inner ring get a point on the surface which can be used to + // correctly classify inner rings to their respective outer rings. + include_once './libraries/gis/pma_gis_polygon.php'; + foreach ($row_data['parts'] as $i => $ring) { + $row_data['parts'][$i]['isOuter'] + = PMA_GIS_Polygon::isOuterRing($ring['points']); + } + + // Find points on surface for inner rings + foreach ($row_data['parts'] as $i => $ring) { + if (! $ring['isOuter']) { + $row_data['parts'][$i]['pointOnSurface'] + = PMA_GIS_Polygon::getPointOnSurface($ring['points']); + } + } + + // Classify inner rings to their respective outer rings. + foreach ($row_data['parts'] as $j => $ring1) { + if (! $ring1['isOuter']) { + foreach ($row_data['parts'] as $k => $ring2) { + if ($ring2['isOuter']) { + // If the pointOnSurface of the inner ring + // is also inside the outer ring + if (PMA_GIS_Polygon::isPointInsidePolygon( + $ring1['pointOnSurface'], $ring2['points'] + )) { + if (! isset($ring2['inner'])) { + $row_data['parts'][$k]['inner'] = array(); + } + $row_data['parts'][$k]['inner'][] = $j; + } + } + } + } + } + + $wkt = 'MULTIPOLYGON('; + // for each polygon + foreach ($row_data['parts'] as $ring) { + if ($ring['isOuter']) { + $wkt .= '('; // start of polygon + + $wkt .= '('; // start of outer ring + foreach ($ring['points'] as $point) { + $wkt .= $point['x'] . ' ' . $point['y'] . ','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; // end of outer ring + + // inner rings if any + if (isset($ring['inner'])) { + foreach ($ring['inner'] as $j) { + $wkt .= ',('; // start of inner ring + foreach ($row_data['parts'][$j]['points'] as $innerPoint) { + $wkt .= $innerPoint['x'] . ' ' . $innerPoint['y'] . ','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; // end of inner ring + } + } + + $wkt .= '),'; // end of polygon + } + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + + $wkt .= ')'; // end of multipolygon + return $wkt; + } + + /** + * Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value Value of the GIS column + * @param int $index Index of the geometry + * + * @return array params for the GIS data editor from the value of the GIS column + * @access public + */ + public function generateParams($value, $index = -1) + { + if ($index == -1) { + $index = 0; + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + } else { + $params[$index]['gis_type'] = 'MULTIPOLYGON'; + $wkt = $value; + } + + // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))' + $multipolygon = substr($wkt, 15, (strlen($wkt) - 18)); + // Seperate each polygon + $polygons = explode(")),((", $multipolygon); + + $param_row =& $params[$index]['MULTIPOLYGON']; + $param_row['no_of_polygons'] = count($polygons); + + $k = 0; + foreach ($polygons as $polygon) { + // If the polygon doesnt have an inner polygon + if (strpos($polygon, "),(") === false) { + $param_row[$k]['no_of_lines'] = 1; + $points_arr = $this->extractPoints($polygon, null); + $no_of_points = count($points_arr); + $param_row[$k][0]['no_of_points'] = $no_of_points; + for ($i = 0; $i < $no_of_points; $i++) { + $param_row[$k][0][$i]['x'] = $points_arr[$i][0]; + $param_row[$k][0][$i]['y'] = $points_arr[$i][1]; + } + } else { + // Seperate outer and inner polygons + $parts = explode("),(", $polygon); + $param_row[$k]['no_of_lines'] = count($parts); + $j = 0; + foreach ($parts as $ring) { + $points_arr = $this->extractPoints($ring, null); + $no_of_points = count($points_arr); + $param_row[$k][$j]['no_of_points'] = $no_of_points; + for ($i = 0; $i < $no_of_points; $i++) { + $param_row[$k][$j][$i]['x'] = $points_arr[$i][0]; + $param_row[$k][$j][$i]['y'] = $points_arr[$i][1]; + } + $j++; + } + } + $k++; + } + return $params; + } +} +?> diff --git a/sources/libraries/gis/pma_gis_point.php b/sources/libraries/gis/pma_gis_point.php new file mode 100644 index 0000000..e434e0c --- /dev/null +++ b/sources/libraries/gis/pma_gis_point.php @@ -0,0 +1,294 @@ +setMinMax($point, array()); + } + + /** + * Adds to the PNG image object, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS POINT object + * @param string $label Label for the GIS POINT object + * @param string $point_color Color for the GIS POINT object + * @param array $scale_data Array containing data related to scaling + * @param object $image Image object + * + * @return object the modified image object + * @access public + */ + public function prepareRowAsPng($spatial, $label, $point_color, + $scale_data, $image + ) { + // allocate colors + $black = imagecolorallocate($image, 0, 0, 0); + $red = hexdec(substr($point_color, 1, 2)); + $green = hexdec(substr($point_color, 3, 2)); + $blue = hexdec(substr($point_color, 4, 2)); + $color = imagecolorallocate($image, $red, $green, $blue); + + // Trim to remove leading 'POINT(' and trailing ')' + $point = substr($spatial, 6, (strlen($spatial) - 7)); + $points_arr = $this->extractPoints($point, $scale_data); + + // draw a small circle to mark the point + if ($points_arr[0][0] != '' && $points_arr[0][1] != '') { + imagearc( + $image, $points_arr[0][0], $points_arr[0][1], 7, 7, 0, 360, $color + ); + // print label if applicable + if (isset($label) && trim($label) != '') { + imagestring( + $image, 1, $points_arr[0][0], + $points_arr[0][1], trim($label), $black + ); + } + } + return $image; + } + + /** + * Adds to the TCPDF instance, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS POINT object + * @param string $label Label for the GIS POINT object + * @param string $point_color Color for the GIS POINT object + * @param array $scale_data Array containing data related to scaling + * @param TCPDF $pdf TCPDF instance + * + * @return TCPDF the modified TCPDF instance + * @access public + */ + public function prepareRowAsPdf($spatial, $label, $point_color, + $scale_data, $pdf + ) { + // allocate colors + $red = hexdec(substr($point_color, 1, 2)); + $green = hexdec(substr($point_color, 3, 2)); + $blue = hexdec(substr($point_color, 4, 2)); + $line = array('width' => 1.25, 'color' => array($red, $green, $blue)); + + // Trim to remove leading 'POINT(' and trailing ')' + $point = substr($spatial, 6, (strlen($spatial) - 7)); + $points_arr = $this->extractPoints($point, $scale_data); + + // draw a small circle to mark the point + if ($points_arr[0][0] != '' && $points_arr[0][1] != '') { + $pdf->Circle( + $points_arr[0][0], $points_arr[0][1], 2, 0, 360, 'D', $line + ); + // print label if applicable + if (isset($label) && trim($label) != '') { + $pdf->SetXY($points_arr[0][0], $points_arr[0][1]); + $pdf->SetFontSize(5); + $pdf->Cell(0, 0, trim($label)); + } + } + return $pdf; + } + + /** + * Prepares and returns the code related to a row in the GIS dataset as SVG. + * + * @param string $spatial GIS POINT object + * @param string $label Label for the GIS POINT object + * @param string $point_color Color for the GIS POINT object + * @param array $scale_data Array containing data related to scaling + * + * @return string the code related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsSvg($spatial, $label, $point_color, $scale_data) + { + $point_options = array( + 'name' => $label, + 'id' => $label . rand(), + 'class' => 'point vector', + 'fill' => 'white', + 'stroke' => $point_color, + 'stroke-width'=> 2, + ); + + // Trim to remove leading 'POINT(' and trailing ')' + $point = substr($spatial, 6, (strlen($spatial) - 7)); + $points_arr = $this->extractPoints($point, $scale_data); + + $row = ''; + if ($points_arr[0][0] != '' && $points_arr[0][1] != '') { + $row .= ' $val) { + $row .= ' ' . $option . '="' . trim($val) . '"'; + } + $row .= '/>'; + } + + return $row; + } + + /** + * Prepares JavaScript related to a row in the GIS dataset + * to visualize it with OpenLayers. + * + * @param string $spatial GIS POINT object + * @param int $srid Spatial reference ID + * @param string $label Label for the GIS POINT object + * @param string $point_color Color for the GIS POINT object + * @param array $scale_data Array containing data related to scaling + * + * @return string JavaScript related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsOl($spatial, $srid, $label, + $point_color, $scale_data + ) { + $style_options = array( + 'pointRadius' => 3, + 'fillColor' => '#ffffff', + 'strokeColor' => $point_color, + 'strokeWidth' => 2, + 'label' => $label, + 'labelYOffset' => -8, + 'fontSize' => 10, + ); + if ($srid == 0) { + $srid = 4326; + } + $result = $this->getBoundsForOl($srid, $scale_data); + + // Trim to remove leading 'POINT(' and trailing ')' + $point = substr($spatial, 6, (strlen($spatial) - 7)); + $points_arr = $this->extractPoints($point, null); + + if ($points_arr[0][0] != '' && $points_arr[0][1] != '') { + $result .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector(' + . $this->getPointForOpenLayers($points_arr[0], $srid). ', null, ' + . json_encode($style_options) . '));'; + } + return $result; + } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Point deos not adhere to this parameter + * + * @return string WKT with the set of parameters passed by the GIS editor + * @access public + */ + public function generateWkt($gis_data, $index, $empty = '') + { + return 'POINT(' + . ((isset($gis_data[$index]['POINT']['x']) + && trim($gis_data[$index]['POINT']['x']) != '') + ? $gis_data[$index]['POINT']['x'] : '') + . ' ' + . ((isset($gis_data[$index]['POINT']['y']) + && trim($gis_data[$index]['POINT']['y']) != '') + ? $gis_data[$index]['POINT']['y'] : '') . ')'; + } + + /** + * Generate the WKT for the data from ESRI shape files. + * + * @param array $row_data GIS data + * + * @return string the WKT for the data from ESRI shape files + * @access public + */ + public function getShape($row_data) + { + return 'POINT(' . (isset($row_data['x']) ? $row_data['x'] : '') + . ' ' . (isset($row_data['y']) ? $row_data['y'] : '') . ')'; + } + + /** + * Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value of the GIS column + * @param int $index of the geometry + * + * @return array params for the GIS data editor from the value of the GIS column + * @access public + */ + public function generateParams($value, $index = -1) + { + if ($index == -1) { + $index = 0; + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + } else { + $params[$index]['gis_type'] = 'POINT'; + $wkt = $value; + } + + // Trim to remove leading 'POINT(' and trailing ')' + $point = substr($wkt, 6, (strlen($wkt) - 7)); + $points_arr = $this->extractPoints($point, null); + + $params[$index]['POINT']['x'] = $points_arr[0][0]; + $params[$index]['POINT']['y'] = $points_arr[0][1]; + + return $params; + } +} +?> diff --git a/sources/libraries/gis/pma_gis_polygon.php b/sources/libraries/gis/pma_gis_polygon.php new file mode 100644 index 0000000..215625e --- /dev/null +++ b/sources/libraries/gis/pma_gis_polygon.php @@ -0,0 +1,549 @@ +setMinMax($ring, array()); + } + + /** + * Adds to the PNG image object, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS POLYGON object + * @param string $label Label for the GIS POLYGON object + * @param string $fill_color Color for the GIS POLYGON object + * @param array $scale_data Array containing data related to scaling + * @param object $image Image object + * + * @return object the modified image object + * @access public + */ + public function prepareRowAsPng($spatial, $label, $fill_color, + $scale_data, $image + ) { + // allocate colors + $black = imagecolorallocate($image, 0, 0, 0); + $red = hexdec(substr($fill_color, 1, 2)); + $green = hexdec(substr($fill_color, 3, 2)); + $blue = hexdec(substr($fill_color, 4, 2)); + $color = imagecolorallocate($image, $red, $green, $blue); + + // Trim to remove leading 'POLYGON((' and trailing '))' + $polygon = substr($spatial, 9, (strlen($spatial) - 11)); + + // If the polygon doesnt have an inner polygon + if (strpos($polygon, "),(") === false) { + $points_arr = $this->extractPoints($polygon, $scale_data, true); + } else { + // Seperate outer and inner polygons + $parts = explode("),(", $polygon); + $outer = $parts[0]; + $inner = array_slice($parts, 1); + + $points_arr = $this->extractPoints($outer, $scale_data, true); + + foreach ($inner as $inner_poly) { + $points_arr = array_merge( + $points_arr, $this->extractPoints($inner_poly, $scale_data, true) + ); + } + } + + // draw polygon + imagefilledpolygon($image, $points_arr, sizeof($points_arr) / 2, $color); + // print label if applicable + if (isset($label) && trim($label) != '') { + imagestring( + $image, 1, $points_arr[2], $points_arr[3], trim($label), $black + ); + } + return $image; + } + + /** + * Adds to the TCPDF instance, the data related to a row in the GIS dataset. + * + * @param string $spatial GIS POLYGON object + * @param string $label Label for the GIS POLYGON object + * @param string $fill_color Color for the GIS POLYGON object + * @param array $scale_data Array containing data related to scaling + * @param TCPDF $pdf TCPDF instance + * + * @return TCPDF the modified TCPDF instance + * @access public + */ + public function prepareRowAsPdf($spatial, $label, $fill_color, $scale_data, $pdf) + { + // allocate colors + $red = hexdec(substr($fill_color, 1, 2)); + $green = hexdec(substr($fill_color, 3, 2)); + $blue = hexdec(substr($fill_color, 4, 2)); + $color = array($red, $green, $blue); + + // Trim to remove leading 'POLYGON((' and trailing '))' + $polygon = substr($spatial, 9, (strlen($spatial) - 11)); + + // If the polygon doesnt have an inner polygon + if (strpos($polygon, "),(") === false) { + $points_arr = $this->extractPoints($polygon, $scale_data, true); + } else { + // Seperate outer and inner polygons + $parts = explode("),(", $polygon); + $outer = $parts[0]; + $inner = array_slice($parts, 1); + + $points_arr = $this->extractPoints($outer, $scale_data, true); + + foreach ($inner as $inner_poly) { + $points_arr = array_merge( + $points_arr, $this->extractPoints($inner_poly, $scale_data, true) + ); + } + } + + // draw polygon + $pdf->Polygon($points_arr, 'F*', array(), $color, true); + // print label if applicable + if (isset($label) && trim($label) != '') { + $pdf->SetXY($points_arr[2], $points_arr[3]); + $pdf->SetFontSize(5); + $pdf->Cell(0, 0, trim($label)); + } + return $pdf; + } + + /** + * Prepares and returns the code related to a row in the GIS dataset as SVG. + * + * @param string $spatial GIS POLYGON object + * @param string $label Label for the GIS POLYGON object + * @param string $fill_color Color for the GIS POLYGON object + * @param array $scale_data Array containing data related to scaling + * + * @return string the code related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsSvg($spatial, $label, $fill_color, $scale_data) + { + $polygon_options = array( + 'name' => $label, + 'id' => $label . rand(), + 'class' => 'polygon vector', + 'stroke' => 'black', + 'stroke-width'=> 0.5, + 'fill' => $fill_color, + 'fill-rule' => 'evenodd', + 'fill-opacity'=> 0.8, + ); + + // Trim to remove leading 'POLYGON((' and trailing '))' + $polygon = substr($spatial, 9, (strlen($spatial) - 11)); + + $row = '_drawPath($polygon, $scale_data); + } else { + // Seperate outer and inner polygons + $parts = explode("),(", $polygon); + $outer = $parts[0]; + $inner = array_slice($parts, 1); + + $row .= $this->_drawPath($outer, $scale_data); + + foreach ($inner as $inner_poly) { + $row .= $this->_drawPath($inner_poly, $scale_data); + } + } + + $row .= '"'; + foreach ($polygon_options as $option => $val) { + $row .= ' ' . $option . '="' . trim($val) . '"'; + } + $row .= '/>'; + return $row; + } + + /** + * Prepares JavaScript related to a row in the GIS dataset + * to visualize it with OpenLayers. + * + * @param string $spatial GIS POLYGON object + * @param int $srid Spatial reference ID + * @param string $label Label for the GIS POLYGON object + * @param string $fill_color Color for the GIS POLYGON object + * @param array $scale_data Array containing data related to scaling + * + * @return string JavaScript related to a row in the GIS dataset + * @access public + */ + public function prepareRowAsOl($spatial, $srid, $label, $fill_color, $scale_data) + { + $style_options = array( + 'strokeColor' => '#000000', + 'strokeWidth' => 0.5, + 'fillColor' => $fill_color, + 'fillOpacity' => 0.8, + 'label' => $label, + 'fontSize' => 10, + ); + if ($srid == 0) { + $srid = 4326; + } + $row = $this->getBoundsForOl($srid, $scale_data); + + // Trim to remove leading 'POLYGON((' and trailing '))' + $polygon = substr($spatial, 9, (strlen($spatial) - 11)); + + // Seperate outer and inner polygons + $parts = explode("),(", $polygon); + $row .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector(' + . $this->getPolygonForOpenLayers($parts, $srid) + . ', null, ' . json_encode($style_options) . '));'; + return $row; + } + + /** + * Draws a ring of the polygon using SVG path element. + * + * @param string $polygon The ring + * @param array $scale_data Array containing data related to scaling + * + * @return string the code to draw the ring + * @access private + */ + private function _drawPath($polygon, $scale_data) + { + $points_arr = $this->extractPoints($polygon, $scale_data); + + $row = ' M ' . $points_arr[0][0] . ', ' . $points_arr[0][1]; + $other_points = array_slice($points_arr, 1, count($points_arr) - 2); + foreach ($other_points as $point) { + $row .= ' L ' . $point[0] . ', ' . $point[1]; + } + $row .= ' Z '; + + return $row; + } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Value for empty points + * + * @return string WKT with the set of parameters passed by the GIS editor + * @access public + */ + public function generateWkt($gis_data, $index, $empty = '') + { + $no_of_lines = isset($gis_data[$index]['POLYGON']['no_of_lines']) + ? $gis_data[$index]['POLYGON']['no_of_lines'] : 1; + if ($no_of_lines < 1) { + $no_of_lines = 1; + } + $wkt = 'POLYGON('; + for ($i = 0; $i < $no_of_lines; $i++) { + $no_of_points = isset($gis_data[$index]['POLYGON'][$i]['no_of_points']) + ? $gis_data[$index]['POLYGON'][$i]['no_of_points'] : 4; + if ($no_of_points < 4) { + $no_of_points = 4; + } + $wkt .= '('; + for ($j = 0; $j < $no_of_points; $j++) { + $wkt .= ((isset($gis_data[$index]['POLYGON'][$i][$j]['x']) + && trim($gis_data[$index]['POLYGON'][$i][$j]['x']) != '') + ? $gis_data[$index]['POLYGON'][$i][$j]['x'] : $empty) + . ' ' . ((isset($gis_data[$index]['POLYGON'][$i][$j]['y']) + && trim($gis_data[$index]['POLYGON'][$i][$j]['y']) != '') + ? $gis_data[$index]['POLYGON'][$i][$j]['y'] : $empty) .','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= '),'; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Calculates the area of a closed simple polygon. + * + * @param array $ring array of points forming the ring + * + * @return float the area of a closed simple polygon + * @access public + * @static + */ + public static function area($ring) + { + + $no_of_points = count($ring); + + // If the last point is same as the first point ignore it + $last = count($ring) - 1; + if (($ring[0]['x'] == $ring[$last]['x']) + && ($ring[0]['y'] == $ring[$last]['y']) + ) { + $no_of_points--; + } + + // _n-1 + // A = _1_ \ (X(i) * Y(i+1)) - (Y(i) * X(i+1)) + // 2 /__ + // i=0 + $area = 0; + for ($i = 0; $i < $no_of_points; $i++) { + $j = ($i + 1) % $no_of_points; + $area += $ring[$i]['x'] * $ring[$j]['y']; + $area -= $ring[$i]['y'] * $ring[$j]['x']; + } + $area /= 2.0; + + return $area; + } + + /** + * Determines whether a set of points represents an outer ring. + * If points are in clockwise orientation then, they form an outer ring. + * + * @param array $ring array of points forming the ring + * + * @return bool whether a set of points represents an outer ring + * @access public + * @static + */ + public static function isOuterRing($ring) + { + // If area is negative then it's in clockwise orientation, + // i.e. it's an outer ring + if (PMA_GIS_Polygon::area($ring) < 0) { + return true; + } + return false; + } + + /** + * Determines whether a given point is inside a given polygon. + * + * @param array $point x, y coordinates of the point + * @param array $polygon array of points forming the ring + * + * @return bool whether a given point is inside a given polygon + * @access public + * @static + */ + public static function isPointInsidePolygon($point, $polygon) + { + // If first point is repeated at the end remove it + $last = count($polygon) - 1; + if (($polygon[0]['x'] == $polygon[$last]['x']) + && ($polygon[0]['y'] == $polygon[$last]['y']) + ) { + $polygon = array_slice($polygon, 0, $last); + } + + $no_of_points = count($polygon); + $counter = 0; + + // Use ray casting algorithm + $p1 = $polygon[0]; + for ($i = 1; $i <= $no_of_points; $i++) { + $p2 = $polygon[$i % $no_of_points]; + if ($point['y'] > min(array($p1['y'], $p2['y']))) { + if ($point['y'] <= max(array($p1['y'], $p2['y']))) { + if ($point['x'] <= max(array($p1['x'], $p2['x']))) { + if ($p1['y'] != $p2['y']) { + $xinters = ($point['y'] - $p1['y']) + * ($p2['x'] - $p1['x']) + / ($p2['y'] - $p1['y']) + $p1['x']; + if ($p1['x'] == $p2['x'] || $point['x'] <= $xinters) { + $counter++; + } + } + } + } + } + $p1 = $p2; + } + + if ($counter % 2 == 0) { + return false; + } else { + return true; + } + } + + /** + * Returns a point that is guaranteed to be on the surface of the ring. + * (for simple closed rings) + * + * @param array $ring array of points forming the ring + * + * @return array|void a point on the surface of the ring + * @access public + * @static + */ + public static function getPointOnSurface($ring) + { + // Find two consecutive distinct points. + for ($i = 0; $i < count($ring) - 1; $i++) { + if ($ring[$i]['y'] != $ring[$i + 1]['y']) { + $x0 = $ring[$i]['x']; + $x1 = $ring[$i + 1]['x']; + $y0 = $ring[$i]['y']; + $y1 = $ring[$i + 1]['y']; + break; + } + } + + if (! isset($x0)) { + return false; + } + + // Find the mid point + $x2 = ($x0 + $x1) / 2; + $y2 = ($y0 + $y1) / 2; + + // Always keep $epsilon < 1 to go with the reduction logic down here + $epsilon = 0.1; + $denominator = sqrt( + PMA_Util::pow(($y1 - $y0), 2) + + PMA_Util::pow(($x0 - $x1), 2) + ); + $pointA = array(); $pointB = array(); + + while (true) { + // Get the points on either sides of the line + // with a distance of epsilon to the mid point + $pointA['x'] = $x2 + ($epsilon * ($y1 - $y0)) / $denominator; + $pointA['y'] = $y2 + ($pointA['x'] - $x2) * ($x0 - $x1) / ($y1 - $y0); + + $pointB['x'] = $x2 + ($epsilon * ($y1 - $y0)) / (0 - $denominator); + $pointB['y'] = $y2 + ($pointB['x'] - $x2) * ($x0 - $x1) / ($y1 - $y0); + + // One of the points should be inside the polygon, + // unless epcilon chosen is too large + if (PMA_GIS_Polygon::isPointInsidePolygon($pointA, $ring)) { + return $pointA; + } elseif (PMA_GIS_Polygon::isPointInsidePolygon($pointB, $ring)) { + return $pointB; + } else { + //If both are outside the polygon reduce the epsilon and + //recalculate the points(reduce exponentially for faster convergance) + $epsilon = PMA_Util::pow($epsilon, 2); + if ($epsilon == 0) { + return false; + } + } + + } + } + + /** Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value Value of the GIS column + * @param int $index Index of the geometry + * + * @return array params for the GIS data editor from the value of the GIS column + * @access public + */ + public function generateParams($value, $index = -1) + { + if ($index == -1) { + $index = 0; + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + } else { + $params[$index]['gis_type'] = 'POLYGON'; + $wkt = $value; + } + + // Trim to remove leading 'POLYGON((' and trailing '))' + $polygon = substr($wkt, 9, (strlen($wkt) - 11)); + // Seperate each linestring + $linerings = explode("),(", $polygon); + $params[$index]['POLYGON']['no_of_lines'] = count($linerings); + + $j = 0; + foreach ($linerings as $linering) { + $points_arr = $this->extractPoints($linering, null); + $no_of_points = count($points_arr); + $params[$index]['POLYGON'][$j]['no_of_points'] = $no_of_points; + for ($i = 0; $i < $no_of_points; $i++) { + $params[$index]['POLYGON'][$j][$i]['x'] = $points_arr[$i][0]; + $params[$index]['POLYGON'][$j][$i]['y'] = $points_arr[$i][1]; + } + $j++; + } + return $params; + } +} +?> diff --git a/sources/libraries/gis/pma_gis_visualization.php b/sources/libraries/gis/pma_gis_visualization.php new file mode 100644 index 0000000..85f0e28 --- /dev/null +++ b/sources/libraries/gis/pma_gis_visualization.php @@ -0,0 +1,503 @@ + array( + '#B02EE0', + '#E0642E', + '#E0D62E', + '#2E97E0', + '#BCE02E', + '#E02E75', + '#5CE02E', + '#E0B02E', + '#0022E0', + '#726CB1', + '#481A36', + '#BAC658', + '#127224', + '#825119', + '#238C74', + '#4C489B', + '#87C9BF', + ), + + // The width of the GIS visualization. + 'width' => 600, + + // The height of the GIS visualization. + 'height' => 450, + ); + + /** + * @var array Options that the user has specified. + */ + private $_userSpecifiedSettings = null; + + /** + * Returns the settings array + * + * @return array the settings array + * @access public + */ + public function getSettings() + { + return $this->_settings; + } + + /** + * Constructor. Stores user specified options. + * + * @param array $data Data for the visualization + * @param array $options Users specified options + * + * @access public + */ + public function __construct($data, $options) + { + $this->_userSpecifiedSettings = $options; + $this->_data = $data; + } + + /** + * All the variable initialization, options handling has to be done here. + * + * @return void + * @access protected + */ + protected function init() + { + $this->_handleOptions(); + } + + /** + * A function which handles passed parameters. Useful if desired + * chart needs to be a little bit different from the default one. + * + * @return void + * @access private + */ + private function _handleOptions() + { + if (! is_null($this->_userSpecifiedSettings)) { + $this->_settings = array_merge( + $this->_settings, + $this->_userSpecifiedSettings + ); + } + } + + /** + * Sanitizes the file name. + * + * @param string $file_name file name + * @param string $ext extension of the file + * + * @return string the sanitized file name + * @access private + */ + private function _sanitizeName($file_name, $ext) + { + $file_name = PMA_sanitizeFilename($file_name); + + // Check if the user already added extension; + // get the substring where the extension would be if it was included + $extension_start_pos = strlen($file_name) - strlen($ext) - 1; + $user_extension = substr( + $file_name, $extension_start_pos, strlen($file_name) + ); + $required_extension = "." . $ext; + if (strtolower($user_extension) != $required_extension) { + $file_name .= $required_extension; + } + return $file_name; + } + + /** + * Handles common tasks of writing the visualization to file for various formats. + * + * @param string $file_name file name + * @param string $type mime type + * @param string $ext extension of the file + * + * @return void + * @access private + */ + private function _toFile($file_name, $type, $ext) + { + $file_name = $this->_sanitizeName($file_name, $ext); + PMA_downloadHeader($file_name, $type); + } + + /** + * Generate the visualization in SVG format. + * + * @return string the generated image resource + * @access private + */ + private function _svg() + { + $this->init(); + + $output = '' . "\n"; + $output .= ''; + $output .= ''; + + $scale_data = $this->_scaleDataSet($this->_data); + $output .= $this->_prepareDataSet($this->_data, $scale_data, 'svg', ''); + + $output .= ''; + $output .= ''; + + return $output; + } + + /** + * Get the visualization as a SVG. + * + * @return string the visualization as a SVG + * @access public + */ + public function asSVG() + { + $output = $this->_svg(); + return $output; + } + + /** + * Saves as a SVG image to a file. + * + * @param string $file_name File name + * + * @return void + * @access public + */ + public function toFileAsSvg($file_name) + { + $img = $this->_svg(); + $this->_toFile($file_name, 'image/svg+xml', 'svg'); + echo($img); + } + + /** + * Generate the visualization in PNG format. + * + * @return resource the generated image resource + * @access private + */ + private function _png() + { + $this->init(); + + // create image + $image = imagecreatetruecolor( + $this->_settings['width'], + $this->_settings['height'] + ); + + // fill the background + $bg = imagecolorallocate($image, 229, 229, 229); + imagefilledrectangle( + $image, 0, 0, $this->_settings['width'] - 1, + $this->_settings['height'] - 1, $bg + ); + + $scale_data = $this->_scaleDataSet($this->_data); + $image = $this->_prepareDataSet($this->_data, $scale_data, 'png', $image); + + return $image; + } + + /** + * Get the visualization as a PNG. + * + * @return string the visualization as a PNG + * @access public + */ + public function asPng() + { + $img = $this->_png(); + + // render and save it to variable + ob_start(); + imagepng($img, null, 9, PNG_ALL_FILTERS); + imagedestroy($img); + $output = ob_get_contents(); + ob_end_clean(); + + // base64 encode + $encoded = base64_encode($output); + return ''; + } + + /** + * Saves as a PNG image to a file. + * + * @param string $file_name File name + * + * @return void + * @access public + */ + public function toFileAsPng($file_name) + { + $img = $this->_png(); + $this->_toFile($file_name, 'image/png', 'png'); + imagepng($img, null, 9, PNG_ALL_FILTERS); + imagedestroy($img); + } + + /** + * Get the code for visualization with OpenLayers. + * + * @return string the code for visualization with OpenLayers + * @access public + */ + public function asOl() + { + $this->init(); + $scale_data = $this->_scaleDataSet($this->_data); + $output + = 'var options = {' + . 'projection: new OpenLayers.Projection("EPSG:900913"),' + . 'displayProjection: new OpenLayers.Projection("EPSG:4326"),' + . 'units: "m",' + . 'numZoomLevels: 18,' + . 'maxResolution: 156543.0339,' + . 'maxExtent: new OpenLayers.Bounds(' + . '-20037508, -20037508, 20037508, 20037508),' + . 'restrictedExtent: new OpenLayers.Bounds(' + . '-20037508, -20037508, 20037508, 20037508)' + . '};' + . 'var map = new OpenLayers.Map("openlayersmap", options);' + . 'var layerNone = new OpenLayers.Layer.Boxes(' + . '"None", {isBaseLayer: true});' + . 'var layerMapnik = new OpenLayers.Layer.OSM.Mapnik("Mapnik");' + . 'var layerCycleMap = new OpenLayers.Layer.OSM.CycleMap("CycleMap");' + . 'map.addLayers([layerMapnik,layerCycleMap,layerNone]);' + . 'var vectorLayer = new OpenLayers.Layer.Vector("Data");' + . 'var bound;'; + $output .= $this->_prepareDataSet($this->_data, $scale_data, 'ol', ''); + $output .= + 'map.addLayer(vectorLayer);' + . 'map.zoomToExtent(bound);' + . 'if (map.getZoom() < 2) {' + . 'map.zoomTo(2);' + . '}' + . 'map.addControl(new OpenLayers.Control.LayerSwitcher());' + . 'map.addControl(new OpenLayers.Control.MousePosition());'; + return $output; + } + + /** + * Saves as a PDF to a file. + * + * @param string $file_name File name + * + * @return void + * @access public + */ + public function toFileAsPdf($file_name) + { + $this->init(); + + include_once './libraries/tcpdf/tcpdf.php'; + + // create pdf + $pdf = new TCPDF( + '', 'pt', $GLOBALS['cfg']['PDFDefaultPageSize'], true, 'UTF-8', false + ); + + // disable header and footer + $pdf->setPrintHeader(false); + $pdf->setPrintFooter(false); + + //set auto page breaks + $pdf->SetAutoPageBreak(false); + + // add a page + $pdf->AddPage(); + + $scale_data = $this->_scaleDataSet($this->_data); + $pdf = $this->_prepareDataSet($this->_data, $scale_data, 'pdf', $pdf); + + // sanitize file name + $file_name = $this->_sanitizeName($file_name, 'pdf'); + $pdf->Output($file_name, 'D'); + } + + /** + * Calculates the scale, horizontal and vertical offset that should be used. + * + * @param array $data Row data + * + * @return array an array containing the scale, x and y offsets + * @access private + */ + private function _scaleDataSet($data) + { + $min_max = array(); + $border = 15; + // effective width and height of the plot + $plot_width = $this->_settings['width'] - 2 * $border; + $plot_height = $this->_settings['height'] - 2 * $border; + + foreach ($data as $row) { + + // Figure out the data type + $ref_data = $row[$this->_settings['spatialColumn']]; + $type_pos = stripos($ref_data, '('); + $type = substr($ref_data, 0, $type_pos); + + $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } + $scale_data = $gis_obj->scaleRow( + $row[$this->_settings['spatialColumn']] + ); + + // Upadate minimum/maximum values for x and y cordinates. + $c_maxX = (float) $scale_data['maxX']; + if (! isset($min_max['maxX']) || $c_maxX > $min_max['maxX']) { + $min_max['maxX'] = $c_maxX; + } + + $c_minX = (float) $scale_data['minX']; + if (! isset($min_max['minX']) || $c_minX < $min_max['minX']) { + $min_max['minX'] = $c_minX; + } + + $c_maxY = (float) $scale_data['maxY']; + if (! isset($min_max['maxY']) || $c_maxY > $min_max['maxY']) { + $min_max['maxY'] = $c_maxY; + } + + $c_minY = (float) $scale_data['minY']; + if (! isset($min_max['minY']) || $c_minY < $min_max['minY']) { + $min_max['minY'] = $c_minY; + } + } + + // scale the visualization + $x_ratio = ($min_max['maxX'] - $min_max['minX']) / $plot_width; + $y_ratio = ($min_max['maxY'] - $min_max['minY']) / $plot_height; + $ratio = ($x_ratio > $y_ratio) ? $x_ratio : $y_ratio; + + $scale = ($ratio != 0) ? (1 / $ratio) : 1; + + if ($x_ratio < $y_ratio) { + // center horizontally + $x = ($min_max['maxX'] + $min_max['minX'] - $plot_width / $scale) / 2; + // fit vertically + $y = $min_max['minY'] - ($border / $scale); + } else { + // fit horizontally + $x = $min_max['minX'] - ($border / $scale); + // center vertically + $y =($min_max['maxY'] + $min_max['minY'] - $plot_height / $scale) / 2; + } + + return array( + 'scale' => $scale, + 'x' => $x, + 'y' => $y, + 'minX' => $min_max['minX'], + 'maxX' => $min_max['maxX'], + 'minY' => $min_max['minY'], + 'maxY' => $min_max['maxY'], + 'height' => $this->_settings['height'], + ); + } + + /** + * Prepares and return the dataset as needed by the visualization. + * + * @param array $data Raw data + * @param array $scale_data Data related to scaling + * @param string $format Format of the visulaization + * @param object $results Image object in the case of png + * TCPDF object in the case of pdf + * + * @return mixed the formatted array of data + * @access private + */ + private function _prepareDataSet($data, $scale_data, $format, $results) + { + $color_number = 0; + + // loop through the rows + foreach ($data as $row) { + $index = $color_number % sizeof($this->_settings['colors']); + + // Figure out the data type + $ref_data = $row[$this->_settings['spatialColumn']]; + $type_pos = stripos($ref_data, '('); + $type = substr($ref_data, 0, $type_pos); + + $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } + $label = ''; + if (isset($this->_settings['labelColumn']) + && isset($row[$this->_settings['labelColumn']]) + ) { + $label = $row[$this->_settings['labelColumn']]; + } + + if ($format == 'svg') { + $results .= $gis_obj->prepareRowAsSvg( + $row[$this->_settings['spatialColumn']], $label, + $this->_settings['colors'][$index], $scale_data + ); + } elseif ($format == 'png') { + $results = $gis_obj->prepareRowAsPng( + $row[$this->_settings['spatialColumn']], $label, + $this->_settings['colors'][$index], $scale_data, $results + ); + } elseif ($format == 'pdf') { + $results = $gis_obj->prepareRowAsPdf( + $row[$this->_settings['spatialColumn']], $label, + $this->_settings['colors'][$index], $scale_data, $results + ); + } elseif ($format == 'ol') { + $results .= $gis_obj->prepareRowAsOl( + $row[$this->_settings['spatialColumn']], $row['srid'], + $label, $this->_settings['colors'][$index], $scale_data + ); + } + $color_number++; + } + return $results; + } +} +?> diff --git a/sources/libraries/iconv_wrapper.lib.php b/sources/libraries/iconv_wrapper.lib.php new file mode 100644 index 0000000..bc822e6 --- /dev/null +++ b/sources/libraries/iconv_wrapper.lib.php @@ -0,0 +1,120 @@ + "ISO8859-[1-9]" according to + // http://publibn.boulder.ibm.com/doc_link/en_US/ + // a_doc_lib/libs/basetrf2/setlocale.htm + 'iso-8859-1' => 'ISO8859-1', + 'iso-8859-2' => 'ISO8859-2', + 'iso-8859-3' => 'ISO8859-3', + 'iso-8859-4' => 'ISO8859-4', + 'iso-8859-5' => 'ISO8859-5', + 'iso-8859-6' => 'ISO8859-6', + 'iso-8859-7' => 'ISO8859-7', + 'iso-8859-8' => 'ISO8859-8', + 'iso-8859-9' => 'ISO8859-9', + + // "big5" --> "IBM-eucTW" according to + // http://kadesh.cepba.upc.es/mancpp/classref/ref/ITranscoder_DSC.htm + 'big5' => 'IBM-eucTW', + + // Other mappings corresponding to the phpMyAdmin dropdown box when using the + // charset conversion feature + 'euc-jp' => 'IBM-eucJP', + 'koi8-r' => 'IBM-eucKR', + 'ks_c_5601-1987' => 'KSC5601.1987-0', + 'tis-620' => 'TIS-620', + 'utf-8' => 'UTF-8' +); + +/** + * Wrapper around IBM AIX iconv(), whose character set naming differs + * from the GNU version of iconv(). + * + * @param string $in_charset input character set + * @param string $out_charset output character set + * @param string $str the string to convert + * + * @return mixed converted string or false on failure + * + * @access public + * + */ +function PMA_convertAIXIconv($in_charset, $out_charset, $str) +{ + list($in_charset, $out_charset) = PMA_convertAIXMapCharsets( + $in_charset, $out_charset + ); + // Call iconv() with the possibly modified parameters + return iconv($in_charset, $out_charset, $str); +} // end of the "PMA_convertAIXIconv()" function + +/** + * Maps input and output character set names to corresponding AIX ones + * + * @param string $in_charset input character set + * @param string $out_charset output character set + * + * @return array array of mapped input and output character set names + */ +function PMA_convertAIXMapCharsets($in_charset, $out_charset) +{ + global $gnu_iconv_to_aix_iconv_codepage_map; + + // Check for transliteration argument at the end of output character set name + $translit_search = strpos(strtolower($out_charset), '//translit'); + $using_translit = (!($translit_search === false)); + + // Extract "plain" output character set name + // (without any transliteration argument) + $out_charset_plain = ($using_translit + ? substr($out_charset, 0, $translit_search) + : $out_charset); + + // Transform name of input character set (if found) + $in_charset_exisits = array_key_exists( + strtolower($in_charset), + $gnu_iconv_to_aix_iconv_codepage_map + ); + if ($in_charset_exisits) { + $in_charset = $gnu_iconv_to_aix_iconv_codepage_map[strtolower($in_charset)]; + } + + // Transform name of "plain" output character set (if found) + $out_charset_plain_exists = array_key_exists( + strtolower($out_charset_plain), + $gnu_iconv_to_aix_iconv_codepage_map + ); + if ($out_charset_plain_exists) { + $out_charset_plain = $gnu_iconv_to_aix_iconv_codepage_map[ + strtolower($out_charset_plain)]; + } + + // Add transliteration argument again (exactly as specified by user) if used + // Build the output character set name that we will use + $out_charset = ($using_translit + ? $out_charset_plain . substr($out_charset, $translit_search) + : $out_charset_plain); + + // NOTE: Transliteration not supported; we will use the "plain" + // output character set name + $out_charset = $out_charset_plain; + + return array($in_charset, $out_charset); +} + +?> diff --git a/sources/libraries/import.lib.php b/sources/libraries/import.lib.php new file mode 100644 index 0000000..adf2233 --- /dev/null +++ b/sources/libraries/import.lib.php @@ -0,0 +1,1282 @@ + ($maximum_time - 5)) { + $timeout_passed = true; + return true; + } else { + return false; + } +} + +/** + * Detects what compression filse uses + * + * @param string $filepath filename to check + * + * @return string MIME type of compression, none for none + * @access public + */ +function PMA_detectCompression($filepath) +{ + $file = @fopen($filepath, 'rb'); + if (! $file) { + return false; + } + $test = fread($file, 4); + $len = strlen($test); + fclose($file); + if ($len >= 2 && $test[0] == chr(31) && $test[1] == chr(139)) { + return 'application/gzip'; + } + if ($len >= 3 && substr($test, 0, 3) == 'BZh') { + return 'application/bzip2'; + } + if ($len >= 4 && $test == "PK\003\004") { + return 'application/zip'; + } + return 'none'; +} + +/** + * Runs query inside import buffer. This is needed to allow displaying + * of last SELECT, SHOW or HANDLER results and similar nice stuff. + * + * @param string $sql query to run + * @param string $full query to display, this might be commented + * @param bool $controluser whether to use control user for queries + * @param array &$sql_data SQL parse data storage + * + * @return void + * @access public + */ +function PMA_importRunQuery($sql = '', $full = '', $controluser = false, + &$sql_data = array() +) { + global $import_run_buffer, $go_sql, $complete_query, $display_query, + $sql_query, $my_die, $error, $reload, + $last_query_with_results, $result, $msg, + $skip_queries, $executed_queries, $max_sql_len, $read_multiply, + $cfg, $sql_query_disabled, $db, $run_query, $is_superuser; + $read_multiply = 1; + if (isset($import_run_buffer)) { + // Should we skip something? + if ($skip_queries > 0) { + $skip_queries--; + } else { + if (! empty($import_run_buffer['sql']) + && trim($import_run_buffer['sql']) != '' + ) { + + // USE query changes the database, son need to track + // while running multiple queries + $is_use_query + = (stripos($import_run_buffer['sql'], "use ") !== false) + ? true + : false; + + $max_sql_len = max($max_sql_len, strlen($import_run_buffer['sql'])); + if (! $sql_query_disabled) { + $sql_query .= $import_run_buffer['full']; + } + if (! $cfg['AllowUserDropDatabase'] + && ! $is_superuser + && preg_match('@^[[:space:]]*DROP[[:space:]]+(IF EXISTS[[:space:]]+)?DATABASE @i', $import_run_buffer['sql']) + ) { + $GLOBALS['message'] = PMA_Message::error(__('"DROP DATABASE" statements are disabled.')); + $error = true; + } else { + + $executed_queries++; + + if ($run_query + && $GLOBALS['finished'] + && empty($sql) + && ! $error + && ((! empty($import_run_buffer['sql']) + && preg_match('/^[\s]*(SELECT|SHOW|HANDLER)/i', $import_run_buffer['sql'])) + || ($executed_queries == 1)) + ) { + $go_sql = true; + if (! $sql_query_disabled) { + $complete_query = $sql_query; + $display_query = $sql_query; + } else { + $complete_query = ''; + $display_query = ''; + } + $sql_query = $import_run_buffer['sql']; + $sql_data['valid_sql'][] = $import_run_buffer['sql']; + if (! isset($sql_data['valid_queries'])) { + $sql_data['valid_queries'] = 0; + } + $sql_data['valid_queries']++; + + // If a 'USE ' SQL-clause was found, + // set our current $db to the new one + list($db, $reload) = PMA_lookForUse( + $import_run_buffer['sql'], + $db, + $reload + ); + } elseif ($run_query) { + + if ($controluser) { + $result = PMA_queryAsControlUser( + $import_run_buffer['sql'] + ); + } else { + $result = $GLOBALS['dbi']->tryQuery($import_run_buffer['sql']); + } + + $msg = '# '; + if ($result === false) { // execution failed + if (! isset($my_die)) { + $my_die = array(); + } + $my_die[] = array( + 'sql' => $import_run_buffer['full'], + 'error' => $GLOBALS['dbi']->getError() + ); + + $msg .= __('Error'); + + if (! $cfg['IgnoreMultiSubmitErrors']) { + $error = true; + return; + } + } else { + $a_num_rows = (int)@$GLOBALS['dbi']->numRows($result); + $a_aff_rows = (int)@$GLOBALS['dbi']->affectedRows(); + if ($a_num_rows > 0) { + $msg .= __('Rows'). ': ' . $a_num_rows; + $last_query_with_results = $import_run_buffer['sql']; + } elseif ($a_aff_rows > 0) { + $message = PMA_Message::getMessageForAffectedRows($a_aff_rows); + $msg .= $message->getMessage(); + } else { + $msg .= __('MySQL returned an empty result set (i.e. zero rows).'); + } + + if (($a_num_rows > 0) || $is_use_query) { + $sql_data['valid_sql'][] = $import_run_buffer['sql']; + if (! isset($sql_data['valid_queries'])) { + $sql_data['valid_queries'] = 0; + } + $sql_data['valid_queries']++; + } + + } + if (! $sql_query_disabled) { + $sql_query .= $msg . "\n"; + } + + // If a 'USE ' SQL-clause was found and the query + // succeeded, set our current $db to the new one + if ($result != false) { + list($db, $reload) = PMA_lookForUse( + $import_run_buffer['sql'], + $db, + $reload + ); + } + + if ($result != false + && preg_match('@^[\s]*(DROP|CREATE)[\s]+(IF EXISTS[[:space:]]+)?(TABLE|DATABASE)[[:space:]]+(.+)@im', $import_run_buffer['sql']) + ) { + $reload = true; + } + } // end run query + } // end if not DROP DATABASE + // end non empty query + } elseif (! empty($import_run_buffer['full'])) { + if ($go_sql) { + $complete_query .= $import_run_buffer['full']; + $display_query .= $import_run_buffer['full']; + } else { + if (! $sql_query_disabled) { + $sql_query .= $import_run_buffer['full']; + } + } + } + // check length of query unless we decided to pass it to sql.php + // (if $run_query is false, we are just displaying so show + // the complete query in the textarea) + if (! $go_sql && $run_query) { + if (! empty($sql_query)) { + if (strlen($sql_query) > 50000 + || $executed_queries > 50 + || $max_sql_len > 1000 + ) { + $sql_query = ''; + $sql_query_disabled = true; + } + } + } + } // end do query (no skip) + } // end buffer exists + + // Do we have something to push into buffer? + if (! empty($sql) || ! empty($full)) { + $import_run_buffer = array('sql' => $sql, 'full' => $full); + } else { + unset($GLOBALS['import_run_buffer']); + } +} + +/** + * Looks for the presence of USE to possibly change current db + * + * @param string $buffer buffer to examine + * @param string $db current db + * @param bool $reload reload + * + * @return array (current or new db, whether to reload) + * @access public + */ +function PMA_lookForUse($buffer, $db, $reload) +{ + if (preg_match('@^[\s]*USE[[:space:]]+([\S]+)@i', $buffer, $match)) { + $db = trim($match[1]); + $db = trim($db, ';'); // for example, USE abc; + + // $db must not contain the escape characters generated by backquote() + // ( used in PMA_buildSQL() as: backquote($db_name), and then called + // in PMA_importRunQuery() which in turn calls PMA_lookForUse() ) + $db = PMA_Util::unQuote($db); + + $reload = true; + } + return(array($db, $reload)); +} + + +/** + * Returns next part of imported file/buffer + * + * @param int $size size of buffer to read + * (this is maximal size function will return) + * + * @return string part of file/buffer + * @access public + */ +function PMA_importGetNextChunk($size = 32768) +{ + global $compression, $import_handle, $charset_conversion, $charset_of_file, + $read_multiply; + + // Add some progression while reading large amount of data + if ($read_multiply <= 8) { + $size *= $read_multiply; + } else { + $size *= 8; + } + $read_multiply++; + + // We can not read too much + if ($size > $GLOBALS['read_limit']) { + $size = $GLOBALS['read_limit']; + } + + if (PMA_checkTimeout()) { + return false; + } + if ($GLOBALS['finished']) { + return true; + } + + if ($GLOBALS['import_file'] == 'none') { + // Well this is not yet supported and tested, + // but should return content of textarea + if (strlen($GLOBALS['import_text']) < $size) { + $GLOBALS['finished'] = true; + return $GLOBALS['import_text']; + } else { + $r = substr($GLOBALS['import_text'], 0, $size); + $GLOBALS['offset'] += $size; + $GLOBALS['import_text'] = substr($GLOBALS['import_text'], $size); + return $r; + } + } + + switch ($compression) { + case 'application/bzip2': + $result = bzread($import_handle, $size); + $GLOBALS['finished'] = feof($import_handle); + break; + case 'application/gzip': + $result = gzread($import_handle, $size); + $GLOBALS['finished'] = feof($import_handle); + break; + case 'application/zip': + $result = substr($GLOBALS['import_text'], 0, $size); + $GLOBALS['import_text'] = substr($GLOBALS['import_text'], $size); + $GLOBALS['finished'] = empty($GLOBALS['import_text']); + break; + case 'none': + $result = fread($import_handle, $size); + $GLOBALS['finished'] = feof($import_handle); + break; + } + $GLOBALS['offset'] += $size; + + if ($charset_conversion) { + return PMA_convertString($charset_of_file, 'utf-8', $result); + } else { + /** + * Skip possible byte order marks (I do not think we need more + * charsets, but feel free to add more, you can use wikipedia for + * reference: ) + * + * @todo BOM could be used for charset autodetection + */ + if ($GLOBALS['offset'] == $size) { + // UTF-8 + if (strncmp($result, "\xEF\xBB\xBF", 3) == 0) { + $result = substr($result, 3); + // UTF-16 BE, LE + } elseif (strncmp($result, "\xFE\xFF", 2) == 0 + || strncmp($result, "\xFF\xFE", 2) == 0 + ) { + $result = substr($result, 2); + } + } + return $result; + } +} + +/** + * Returns the "Excel" column name (i.e. 1 = "A", 26 = "Z", 27 = "AA", etc.) + * + * This functions uses recursion to build the Excel column name. + * + * The column number (1-26) is converted to the responding + * ASCII character (A-Z) and returned. + * + * If the column number is bigger than 26 (= num of letters in alfabet), + * an extra character needs to be added. To find this extra character, + * the number is divided by 26 and this value is passed to another instance + * of the same function (hence recursion). In that new instance the number is + * evaluated again, and if it is still bigger than 26, it is divided again + * and passed to another instance of the same function. This continues until + * the number is smaller than 26. Then the last called function returns + * the corresponding ASCII character to the function that called it. + * Each time a called function ends an extra character is added to the column name. + * When the first function is reached, the last character is addded and the complete + * column name is returned. + * + * @param int $num the column number + * + * @return string The column's "Excel" name + * @access public + */ +function PMA_getColumnAlphaName($num) +{ + $A = 65; // ASCII value for capital "A" + $col_name = ""; + + if ($num > 26) { + $div = (int)($num / 26); + $remain = (int)($num % 26); + + // subtract 1 of divided value in case the modulus is 0, + // this is necessary because A-Z has no 'zero' + if ($remain == 0) { + $div--; + } + + // recursive function call + $col_name = PMA_getColumnAlphaName($div); + // use modulus as new column number + $num = $remain; + } + + if ($num == 0) { + // use 'Z' if column number is 0, + // this is necessary because A-Z has no 'zero' + $col_name .= chr(($A + 26) - 1); + } else { + // convert column number to ASCII character + $col_name .= chr(($A + $num) - 1); + } + + return $col_name; +} + +/** + * Returns the column number based on the Excel name. + * So "A" = 1, "Z" = 26, "AA" = 27, etc. + * + * Basicly this is a base26 (A-Z) to base10 (0-9) conversion. + * It iterates through all characters in the column name and + * calculates the corresponding value, based on character value + * (A = 1, ..., Z = 26) and position in the string. + * + * @param string $name column name(i.e. "A", or "BC", etc.) + * + * @return int The column number + * @access public + */ +function PMA_getColumnNumberFromName($name) +{ + if (! empty($name)) { + $name = strtoupper($name); + $num_chars = strlen($name); + $column_number = 0; + for ($i = 0; $i < $num_chars; ++$i) { + // read string from back to front + $char_pos = ($num_chars - 1) - $i; + + // convert capital character to ASCII value + // and subtract 64 to get corresponding decimal value + // ASCII value of "A" is 65, "B" is 66, etc. + // Decimal equivalent of "A" is 1, "B" is 2, etc. + $number = (ord($name[$char_pos]) - 64); + + // base26 to base10 conversion : multiply each number + // with corresponding value of the position, in this case + // $i=0 : 1; $i=1 : 26; $i=2 : 676; ... + $column_number += $number * PMA_Util::pow(26, $i); + } + return $column_number; + } else { + return 0; + } +} + +/** + * Constants definitions + */ + +/* MySQL type defs */ +define("NONE", 0); +define("VARCHAR", 1); +define("INT", 2); +define("DECIMAL", 3); +define("BIGINT", 4); +define("GEOMETRY", 5); + +/* Decimal size defs */ +define("M", 0); +define("D", 1); +define("FULL", 2); + +/* Table array defs */ +define("TBL_NAME", 0); +define("COL_NAMES", 1); +define("ROWS", 2); + +/* Analysis array defs */ +define("TYPES", 0); +define("SIZES", 1); +define("FORMATTEDSQL", 2); + +/** + * Obtains the precision (total # of digits) from a size of type decimal + * + * @param string $last_cumulative_size + * + * @return int Precision of the given decimal size notation + * @access public + */ +function PMA_getM($last_cumulative_size) +{ + return (int)substr($last_cumulative_size, 0, strpos($last_cumulative_size, ",")); +} + +/** + * Obtains the scale (# of digits to the right of the decimal point) + * from a size of type decimal + * + * @param string $last_cumulative_size + * + * @return int Scale of the given decimal size notation + * @access public + */ +function PMA_getD($last_cumulative_size) +{ + return (int) substr( + $last_cumulative_size, + (strpos($last_cumulative_size, ",") + 1), + (strlen($last_cumulative_size) - strpos($last_cumulative_size, ",")) + ); +} + +/** + * Obtains the decimal size of a given cell + * + * @param string $cell cell content + * + * @return array Contains the precision, scale, and full size + * representation of the given decimal cell + * @access public + */ +function PMA_getDecimalSize($cell) +{ + $curr_size = strlen((string)$cell); + $decPos = strpos($cell, "."); + $decPrecision = ($curr_size - 1) - $decPos; + + $m = $curr_size - 1; + $d = $decPrecision; + + return array($m, $d, ($m . "," . $d)); +} + +/** + * Obtains the size of the given cell + * + * @param string $last_cumulative_size Last cumulative column size + * @param int $last_cumulative_type Last cumulative column type + * (NONE or VARCHAR or DECIMAL or INT or BIGINT) + * @param int $curr_type Type of the current cell + * (NONE or VARCHAR or DECIMAL or INT or BIGINT) + * @param string $cell The current cell + * + * @return string Size of the given cell in the type-appropriate format + * @access public + * + * @todo Handle the error cases more elegantly + */ +function PMA_detectSize($last_cumulative_size, $last_cumulative_type, + $curr_type, $cell +) { + $curr_size = strlen((string)$cell); + + /** + * If the cell is NULL, don't treat it as a varchar + */ + if (! strcmp('NULL', $cell)) { + return $last_cumulative_size; + } elseif ($curr_type == VARCHAR) { + /** + * What to do if the current cell is of type VARCHAR + */ + /** + * The last cumulative type was VARCHAR + */ + if ($last_cumulative_type == VARCHAR) { + if ($curr_size >= $last_cumulative_size) { + return $curr_size; + } else { + return $last_cumulative_size; + } + } elseif ($last_cumulative_type == DECIMAL) { + /** + * The last cumulative type was DECIMAL + */ + $oldM = PMA_getM($last_cumulative_size); + + if ($curr_size >= $oldM) { + return $curr_size; + } else { + return $oldM; + } + } elseif ($last_cumulative_type == BIGINT || $last_cumulative_type == INT) { + /** + * The last cumulative type was BIGINT or INT + */ + if ($curr_size >= $last_cumulative_size) { + return $curr_size; + } else { + return $last_cumulative_size; + } + } elseif (! isset($last_cumulative_type) || $last_cumulative_type == NONE) { + /** + * This is the first row to be analyzed + */ + return $curr_size; + } else { + /** + * An error has DEFINITELY occurred + */ + /** + * TODO: Handle this MUCH more elegantly + */ + + return -1; + } + } elseif ($curr_type == DECIMAL) { + /** + * What to do if the current cell is of type DECIMAL + */ + /** + * The last cumulative type was VARCHAR + */ + if ($last_cumulative_type == VARCHAR) { + /* Convert $last_cumulative_size from varchar to decimal format */ + $size = PMA_getDecimalSize($cell); + + if ($size[M] >= $last_cumulative_size) { + return $size[M]; + } else { + return $last_cumulative_size; + } + } elseif ($last_cumulative_type == DECIMAL) { + /** + * The last cumulative type was DECIMAL + */ + $size = PMA_getDecimalSize($cell); + + $oldM = PMA_getM($last_cumulative_size); + $oldD = PMA_getD($last_cumulative_size); + + /* New val if M or D is greater than current largest */ + if ($size[M] > $oldM || $size[D] > $oldD) { + /* Take the largest of both types */ + return (string) ((($size[M] > $oldM) ? $size[M] : $oldM) + . "," . (($size[D] > $oldD) ? $size[D] : $oldD)); + } else { + return $last_cumulative_size; + } + } elseif ($last_cumulative_type == BIGINT || $last_cumulative_type == INT) { + /** + * The last cumulative type was BIGINT or INT + */ + /* Convert $last_cumulative_size from int to decimal format */ + $size = PMA_getDecimalSize($cell); + + if ($size[M] >= $last_cumulative_size) { + return $size[FULL]; + } else { + return ($last_cumulative_size.",".$size[D]); + } + } elseif (! isset($last_cumulative_type) || $last_cumulative_type == NONE) { + /** + * This is the first row to be analyzed + */ + /* First row of the column */ + $size = PMA_getDecimalSize($cell); + + return $size[FULL]; + } else { + /** + * An error has DEFINITELY occurred + */ + /** + * TODO: Handle this MUCH more elegantly + */ + + return -1; + } + } elseif ($curr_type == BIGINT || $curr_type == INT) { + /** + * What to do if the current cell is of type BIGINT or INT + */ + /** + * The last cumulative type was VARCHAR + */ + if ($last_cumulative_type == VARCHAR) { + if ($curr_size >= $last_cumulative_size) { + return $curr_size; + } else { + return $last_cumulative_size; + } + } elseif ($last_cumulative_type == DECIMAL) { + /** + * The last cumulative type was DECIMAL + */ + $oldM = PMA_getM($last_cumulative_size); + $oldD = PMA_getD($last_cumulative_size); + $oldInt = $oldM - $oldD; + $newInt = strlen((string)$cell); + + /* See which has the larger integer length */ + if ($oldInt >= $newInt) { + /* Use old decimal size */ + return $last_cumulative_size; + } else { + /* Use $newInt + $oldD as new M */ + return (($newInt + $oldD) . "," . $oldD); + } + } elseif ($last_cumulative_type == BIGINT || $last_cumulative_type == INT) { + /** + * The last cumulative type was BIGINT or INT + */ + if ($curr_size >= $last_cumulative_size) { + return $curr_size; + } else { + return $last_cumulative_size; + } + } elseif (! isset($last_cumulative_type) || $last_cumulative_type == NONE) { + /** + * This is the first row to be analyzed + */ + return $curr_size; + } else { + /** + * An error has DEFINITELY occurred + */ + /** + * TODO: Handle this MUCH more elegantly + */ + + return -1; + } + } else { + /** + * An error has DEFINITELY occurred + */ + /** + * TODO: Handle this MUCH more elegantly + */ + + return -1; + } +} + +/** + * Determines what MySQL type a cell is + * + * @param int $last_cumulative_type Last cumulative column type + * (VARCHAR or INT or BIGINT or DECIMAL or NONE) + * @param string $cell String representation of the cell for which + * a best-fit type is to be determined + * + * @return int The MySQL type representation + * (VARCHAR or INT or BIGINT or DECIMAL or NONE) + * @access public + */ +function PMA_detectType($last_cumulative_type, $cell) +{ + /** + * If numeric, determine if decimal, int or bigint + * Else, we call it varchar for simplicity + */ + + if (! strcmp('NULL', $cell)) { + if ($last_cumulative_type === null || $last_cumulative_type == NONE) { + return NONE; + } else { + return $last_cumulative_type; + } + } elseif (is_numeric($cell)) { + if ($cell == (string)(float)$cell + && strpos($cell, ".") !== false + && substr_count($cell, ".") == 1 + ) { + return DECIMAL; + } else { + if (abs($cell) > 2147483647) { + return BIGINT; + } else { + return INT; + } + } + } else { + return VARCHAR; + } +} + +/** + * Determines if the column types are int, decimal, or string + * + * @param array &$table array(string $table_name, array $col_names, array $rows) + * + * @return array array(array $types, array $sizes) + * @access public + * + * @link http://wiki.phpmyadmin.net/pma/Import + * + * @todo Handle the error case more elegantly + */ +function PMA_analyzeTable(&$table) +{ + /* Get number of rows in table */ + $numRows = count($table[ROWS]); + /* Get number of columns */ + $numCols = count($table[COL_NAMES]); + /* Current type for each column */ + $types = array(); + $sizes = array(); + + /* Initialize $sizes to all 0's */ + for ($i = 0; $i < $numCols; ++$i) { + $sizes[$i] = 0; + } + + /* Initialize $types to NONE */ + for ($i = 0; $i < $numCols; ++$i) { + $types[$i] = NONE; + } + + /* Temp vars */ + $curr_type = NONE; + + /* If the passed array is not of the correct form, do not process it */ + if (is_array($table) + && ! is_array($table[TBL_NAME]) + && is_array($table[COL_NAMES]) + && is_array($table[ROWS]) + ) { + /* Analyze each column */ + for ($i = 0; $i < $numCols; ++$i) { + /* Analyze the column in each row */ + for ($j = 0; $j < $numRows; ++$j) { + /* Determine type of the current cell */ + $curr_type = PMA_detectType($types[$i], $table[ROWS][$j][$i]); + /* Determine size of the current cell */ + $sizes[$i] = PMA_detectSize( + $sizes[$i], + $types[$i], + $curr_type, + $table[ROWS][$j][$i] + ); + + /** + * If a type for this column has already been declared, + * only alter it if it was a number and a varchar was found + */ + if ($curr_type != NONE) { + if ($curr_type == VARCHAR) { + $types[$i] = VARCHAR; + } else if ($curr_type == DECIMAL) { + if ($types[$i] != VARCHAR) { + $types[$i] = DECIMAL; + } + } else if ($curr_type == BIGINT) { + if ($types[$i] != VARCHAR && $types[$i] != DECIMAL) { + $types[$i] = BIGINT; + } + } else if ($curr_type == INT) { + if ($types[$i] != VARCHAR + && $types[$i] != DECIMAL + && $types[$i] != BIGINT + ) { + $types[$i] = INT; + } + } + } + } + } + + /* Check to ensure that all types are valid */ + $len = count($types); + for ($n = 0; $n < $len; ++$n) { + if (! strcmp(NONE, $types[$n])) { + $types[$n] = VARCHAR; + $sizes[$n] = '10'; + } + } + + return array($types, $sizes); + } else { + /** + * TODO: Handle this better + */ + + return false; + } +} + +/* Needed to quell the beast that is PMA_Message */ +$import_notice = null; + +/** + * Builds and executes SQL statements to create the database and tables + * as necessary, as well as insert all the data. + * + * @param string $db_name Name of the database + * @param array &$tables Array of tables for the specified database + * @param array &$analyses Analyses of the tables + * @param array &$additional_sql Additional SQL statements to be executed + * @param array $options Associative array of options + * + * @return void + * @access public + * + * @link http://wiki.phpmyadmin.net/pma/Import + */ +function PMA_buildSQL($db_name, &$tables, &$analyses = null, + &$additional_sql = null, $options = null +) { + /* Take care of the options */ + if (isset($options['db_collation'])&& ! is_null($options['db_collation'])) { + $collation = $options['db_collation']; + } else { + $collation = "utf8_general_ci"; + } + + if (isset($options['db_charset']) && ! is_null($options['db_charset'])) { + $charset = $options['db_charset']; + } else { + $charset = "utf8"; + } + + if (isset($options['create_db'])) { + $create_db = $options['create_db']; + } else { + $create_db = true; + } + + /* Create SQL code to handle the database */ + $sql = array(); + + if ($create_db) { + if (PMA_DRIZZLE) { + $sql[] = "CREATE DATABASE IF NOT EXISTS " . PMA_Util::backquote($db_name) + . " COLLATE " . $collation; + } else { + $sql[] = "CREATE DATABASE IF NOT EXISTS " . PMA_Util::backquote($db_name) + . " DEFAULT CHARACTER SET " . $charset . " COLLATE " . $collation; + } + } + + /** + * The calling plug-in should include this statement, + * if necessary, in the $additional_sql parameter + * + * $sql[] = "USE " . backquote($db_name); + */ + + /* Execute the SQL statements create above */ + $sql_len = count($sql); + for ($i = 0; $i < $sql_len; ++$i) { + PMA_importRunQuery($sql[$i], $sql[$i]); + } + + /* No longer needed */ + unset($sql); + + /* Run the $additional_sql statements supplied by the caller plug-in */ + if ($additional_sql != null) { + /* Clean the SQL first */ + $additional_sql_len = count($additional_sql); + + /** + * Only match tables for now, because CREATE IF NOT EXISTS + * syntax is lacking or nonexisting for views, triggers, + * functions, and procedures. + * + * See: http://bugs.mysql.com/bug.php?id=15287 + * + * To the best of my knowledge this is still an issue. + * + * $pattern = 'CREATE (TABLE|VIEW|TRIGGER|FUNCTION|PROCEDURE)'; + */ + $pattern = '/CREATE [^`]*(TABLE)/'; + $replacement = 'CREATE \\1 IF NOT EXISTS'; + + /* Change CREATE statements to CREATE IF NOT EXISTS to support + * inserting into existing structures + */ + for ($i = 0; $i < $additional_sql_len; ++$i) { + $additional_sql[$i] = preg_replace( + $pattern, + $replacement, + $additional_sql[$i] + ); + /* Execute the resulting statements */ + PMA_importRunQuery($additional_sql[$i], $additional_sql[$i]); + } + } + + if ($analyses != null) { + $type_array = array( + NONE => "NULL", + VARCHAR => "varchar", + INT => "int", + DECIMAL => "decimal", + BIGINT => "bigint", + GEOMETRY => 'geometry' + ); + + /* TODO: Do more checking here to make sure they really are matched */ + if (count($tables) != count($analyses)) { + exit(); + } + + /* Create SQL code to create the tables */ + $tempSQLStr = ""; + $num_tables = count($tables); + for ($i = 0; $i < $num_tables; ++$i) { + $num_cols = count($tables[$i][COL_NAMES]); + $tempSQLStr = "CREATE TABLE IF NOT EXISTS " + . PMA_Util::backquote($db_name) + . '.' . PMA_Util::backquote($tables[$i][TBL_NAME]) . " ("; + for ($j = 0; $j < $num_cols; ++$j) { + $size = $analyses[$i][SIZES][$j]; + if ((int)$size == 0) { + $size = 10; + } + + $tempSQLStr .= PMA_Util::backquote($tables[$i][COL_NAMES][$j]) . " " + . $type_array[$analyses[$i][TYPES][$j]]; + if ($analyses[$i][TYPES][$j] != GEOMETRY) { + $tempSQLStr .= "(" . $size . ")"; + } + + if ($j != (count($tables[$i][COL_NAMES]) - 1)) { + $tempSQLStr .= ", "; + } + } + $tempSQLStr .= ")" + . (PMA_DRIZZLE ? "" : " DEFAULT CHARACTER SET " . $charset) + . " COLLATE " . $collation . ";"; + + /** + * Each SQL statement is executed immediately + * after it is formed so that we don't have + * to store them in a (possibly large) buffer + */ + PMA_importRunQuery($tempSQLStr, $tempSQLStr); + } + } + + /** + * Create the SQL statements to insert all the data + * + * Only one insert query is formed for each table + */ + $tempSQLStr = ""; + $col_count = 0; + $num_tables = count($tables); + for ($i = 0; $i < $num_tables; ++$i) { + $num_cols = count($tables[$i][COL_NAMES]); + $num_rows = count($tables[$i][ROWS]); + + $tempSQLStr = "INSERT INTO " . PMA_Util::backquote($db_name) . '.' + . PMA_Util::backquote($tables[$i][TBL_NAME]) . " ("; + + for ($m = 0; $m < $num_cols; ++$m) { + $tempSQLStr .= PMA_Util::backquote($tables[$i][COL_NAMES][$m]); + + if ($m != ($num_cols - 1)) { + $tempSQLStr .= ", "; + } + } + + $tempSQLStr .= ") VALUES "; + + for ($j = 0; $j < $num_rows; ++$j) { + $tempSQLStr .= "("; + + for ($k = 0; $k < $num_cols; ++$k) { + // If fully formatted SQL, no need to enclose + // with aphostrophes, add shalshes etc. + if ($analyses != null + && isset($analyses[$i][FORMATTEDSQL][$col_count]) + && $analyses[$i][FORMATTEDSQL][$col_count] == true + ) { + $tempSQLStr .= (string) $tables[$i][ROWS][$j][$k]; + } else { + if ($analyses != null) { + $is_varchar = ($analyses[$i][TYPES][$col_count] === VARCHAR); + } else { + $is_varchar = ! is_numeric($tables[$i][ROWS][$j][$k]); + } + + /* Don't put quotes around NULL fields */ + if (! strcmp($tables[$i][ROWS][$j][$k], 'NULL')) { + $is_varchar = false; + } + + $tempSQLStr .= (($is_varchar) ? "'" : ""); + $tempSQLStr .= PMA_Util::sqlAddSlashes( + (string) $tables[$i][ROWS][$j][$k] + ); + $tempSQLStr .= (($is_varchar) ? "'" : ""); + } + + if ($k != ($num_cols - 1)) { + $tempSQLStr .= ", "; + } + + if ($col_count == ($num_cols - 1)) { + $col_count = 0; + } else { + $col_count++; + } + + /* Delete the cell after we are done with it */ + unset($tables[$i][ROWS][$j][$k]); + } + + $tempSQLStr .= ")"; + + if ($j != ($num_rows - 1)) { + $tempSQLStr .= ",\n "; + } + + $col_count = 0; + /* Delete the row after we are done with it */ + unset($tables[$i][ROWS][$j]); + } + + $tempSQLStr .= ";"; + + /** + * Each SQL statement is executed immediately + * after it is formed so that we don't have + * to store them in a (possibly large) buffer + */ + PMA_importRunQuery($tempSQLStr, $tempSQLStr); + } + + /* No longer needed */ + unset($tempSQLStr); + + /** + * A work in progress + */ + + /* Add the viewable structures from $additional_sql + * to $tables so they are also displayed + */ + $view_pattern = '@VIEW `[^`]+`\.`([^`]+)@'; + $table_pattern = '@CREATE TABLE IF NOT EXISTS `([^`]+)`@'; + /* Check a third pattern to make sure its not a "USE `db_name`;" statement */ + + $regs = array(); + + $inTables = false; + + $additional_sql_len = count($additional_sql); + for ($i = 0; $i < $additional_sql_len; ++$i) { + preg_match($view_pattern, $additional_sql[$i], $regs); + + if (count($regs) == 0) { + preg_match($table_pattern, $additional_sql[$i], $regs); + } + + if (count($regs)) { + for ($n = 0; $n < $num_tables; ++$n) { + if (! strcmp($regs[1], $tables[$n][TBL_NAME])) { + $inTables = true; + break; + } + } + + if (! $inTables) { + $tables[] = array(TBL_NAME => $regs[1]); + } + } + + /* Reset the array */ + $regs = array(); + $inTables = false; + } + + $params = array('db' => (string)$db_name); + $db_url = 'db_structure.php' . PMA_URL_getCommon($params); + $db_ops_url = 'db_operations.php' . PMA_URL_getCommon($params); + + $message = '

        '; + $message .= '' . __('The following structures have either been created or altered. Here you can:') . '
        '; + $message .= '
        • ' . __("View a structure's contents by clicking on its name") . '
        • '; + $message .= '
        • ' . __('Change any of its settings by clicking the corresponding "Options" link') . '
        • '; + $message .= '
        • ' . __('Edit structure by following the "Structure" link') . '
        • '; + $message .= sprintf( + '
        • %s (' + . __('Options') . ')
        • ', + $db_url, + sprintf( + __('Go to database: %s'), + htmlspecialchars(PMA_Util::backquote($db_name)) + ), + htmlspecialchars($db_name), + $db_ops_url, + sprintf( + __('Edit settings for %s'), + htmlspecialchars(PMA_Util::backquote($db_name)) + ) + ); + + $message .= '
            '; + + unset($params); + + $num_tables = count($tables); + for ($i = 0; $i < $num_tables; ++$i) { + $params = array( + 'db' => (string) $db_name, + 'table' => (string) $tables[$i][TBL_NAME] + ); + $tbl_url = 'sql.php' . PMA_URL_getCommon($params); + $tbl_struct_url = 'tbl_structure.php' . PMA_URL_getCommon($params); + $tbl_ops_url = 'tbl_operations.php' . PMA_URL_getCommon($params); + + unset($params); + + if (! PMA_Table::isView($db_name, $tables[$i][TBL_NAME])) { + $message .= sprintf( + '
          • %s (' . __('Structure') . ') (' . __('Options') . ')
          • ', + $tbl_url, + sprintf( + __('Go to table: %s'), + htmlspecialchars( + PMA_Util::backquote($tables[$i][TBL_NAME]) + ) + ), + htmlspecialchars($tables[$i][TBL_NAME]), + $tbl_struct_url, + sprintf( + __('Structure of %s'), + htmlspecialchars( + PMA_Util::backquote($tables[$i][TBL_NAME]) + ) + ), + $tbl_ops_url, + sprintf( + __('Edit settings for %s'), + htmlspecialchars( + PMA_Util::backquote($tables[$i][TBL_NAME]) + ) + ) + ); + } else { + $message .= sprintf( + '
          • %s
          • ', + $tbl_url, + sprintf( + __('Go to view: %s'), + htmlspecialchars( + PMA_Util::backquote($tables[$i][TBL_NAME]) + ) + ), + htmlspecialchars($tables[$i][TBL_NAME]) + ); + } + } + + $message .= '
        '; + + global $import_notice; + $import_notice = $message; + + unset($tables); +} + +?> diff --git a/sources/libraries/index.lib.php b/sources/libraries/index.lib.php new file mode 100644 index 0000000..f5df69e --- /dev/null +++ b/sources/libraries/index.lib.php @@ -0,0 +1,49 @@ +'; + + $html_output .= PMA_Util::getDivForSliderEffect( + 'indexes', __('Indexes') + ); + $html_output .= PMA_Index::getView($GLOBALS['table'], $GLOBALS['db']); + $html_output .= '
        ' + . '
        '; + $html_output .= PMA_URL_getHiddenInputs( + $GLOBALS['db'], $GLOBALS['table'] + ); + $html_output .= sprintf( + __('Create an index on  %s columns'), + '' + ); + $html_output .= '' + . ''; + + $html_output .= '
        ' + . '
        ' + . '
        ' + . '
      '; + + return $html_output; +} + diff --git a/sources/libraries/information_schema_relations.lib.php b/sources/libraries/information_schema_relations.lib.php new file mode 100644 index 0000000..d153207 --- /dev/null +++ b/sources/libraries/information_schema_relations.lib.php @@ -0,0 +1,332 @@ + array( + 'DEFAULT_COLLATE_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ), + 'CHARACTER_SET_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'CHARACTER_SETS', + 'foreign_field' => 'CHARACTER_SET_NAME' + ) + ), + 'COLLATIONS' => array( + 'CHARACTER_SET_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'CHARACTER_SETS', + 'foreign_field' => 'CHARACTER_SET_NAME' + ) + ), + 'COLLATION_CHARACTER_SET_APPLICABILITY' => array( + 'CHARACTER_SET_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'CHARACTER_SETS', + 'foreign_field' => 'CHARACTER_SET_NAME' + ), + 'COLLATION_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ) + ), + 'COLUMNS' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'CHARACTER_SET_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'CHARACTER_SETS', + 'foreign_field' => 'CHARACTER_SET_NAME' + ), + 'COLLATION_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ) + ), + 'COLUMN_PRIVILEGES' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ) + ), + 'EVENTS' => array( + 'EVENT_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'CHARACTER_SET_CLIENT' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'CHARACTER_SETS', + 'foreign_field' => 'CHARACTER_SET_NAME' + ), + 'COLLATION_CONNECTION' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ), + 'DATABASE_COLLATION' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ) + ), + 'FILES' => array( + 'TABLESPACE_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'TABLESPACES', + 'foreign_field' => 'TABLESPACE_NAME' + ), + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'COLLATION_CONNECTION' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ), + 'ENGINE' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'ENGINES', + 'foreign_field' => 'ENGINE' + ) + ), + 'KEY_COLUMN_USAGE' => array( + 'CONSTRAINT_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'REFERENCED_TABLE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ) + ), + 'PARAMETERS' => array( + 'SPECIFIC_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'CHARACTER_SET_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'CHARACTER_SETS', + 'foreign_field' => 'CHARACTER_SET_NAME' + ), + 'COLLATION_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ) + ), + 'PARTITIONS' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'TABLESPACE_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'TABLESPACES', + 'foreign_field' => 'TABLESPACE_NAME' + ) + ), + 'PROCESSLIST' => array( + 'DB' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ) + ), + 'REFERENTIAL_CONSTRAINTS' => array( + 'CONSTRAINT_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'UNIQUE_CONSTRAINT_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ) + ), + 'ROUTINES' => array( + 'ROUTINE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'CHARACTER_SET_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'CHARACTER_SETS', + 'foreign_field' => 'CHARACTER_SET_NAME' + ), + 'COLLATION_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ), + 'CHARACTER_SET_CLIENT' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'CHARACTER_SETS', + 'foreign_field' => 'CHARACTER_SET_NAME' + ), + 'COLLATION_CONNECTION' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ), + 'DATABASE_COLLATION' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ) + ), + 'SCHEMATA' => array( + 'DEFAULT_CHARACTER_SET_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'CHARACTER_SETS', + 'foreign_field' => 'CHARACTER_SET_NAME' + ), + 'DEFAULT_COLLATION_NAME' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ) + ), + 'SCHEMA_PRIVILEGES' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ) + ), + 'STATISTICS' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'INDEX_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ) + ), + 'TABLES' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'TABLE_COLLATION' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ), + 'ENGINE' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'ENGINES', + 'foreign_field' => 'ENGINE' + ), + ), + 'TABLESAPCES' => array( + 'ENGINE' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'ENGINES', + 'foreign_field' => 'ENGINE' + ) + ), + 'TABLE_CONSTRAINTS' => array( + 'CONSTRAINT_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ) + ), + 'TABLE_PRIVILEGES' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ) + ), + 'TRIGGERS' => array( + 'TRIGGER_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'EVENT_OBJECT_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'CHARACTER_SET_CLIENT' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'CHARACTER_SETS', + 'foreign_field' => 'CHARACTER_SET_NAME' + ), + 'COLLATION_CONNECTION' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ), + 'DATABASE_COLLATION' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ) + ), + 'VIEWS' => array( + 'TABLE_SCHEMA' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'SCHEMATA', + 'foreign_field' => 'SCHEMA_NAME' + ), + 'CHARACTER_SET_CLIENT' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'CHARACTER_SETS', + 'foreign_field' => 'CHARACTER_SET_NAME' + ), + 'COLLATION_CONNECTION' => array( + 'foreign_db' => 'information_schema', + 'foreign_table' => 'COLLATIONS', + 'foreign_field' => 'COLLATION_NAME' + ) + ) +); + +?> diff --git a/sources/libraries/insert_edit.lib.php b/sources/libraries/insert_edit.lib.php new file mode 100644 index 0000000..7370c3e --- /dev/null +++ b/sources/libraries/insert_edit.lib.php @@ -0,0 +1,2909 @@ + $db, + 'table' => $table, + 'goto' => $GLOBALS['goto'], + 'err_url' => $err_url, + 'sql_query' => $_REQUEST['sql_query'], + ); + if (isset($where_clauses)) { + foreach ($where_clause_array as $key_id => $where_clause) { + $_form_params['where_clause[' . $key_id . ']'] = trim($where_clause); + } + } + if (isset($_REQUEST['clause_is_unique'])) { + $_form_params['clause_is_unique'] = $_REQUEST['clause_is_unique']; + } + return $_form_params; +} + +/** + * Creates array of where clauses + * + * @param array $where_clause where clause + * + * @return array|void whereClauseArray array of where clauses + */ +function PMA_getWhereClauseArray($where_clause) +{ + if (isset ($where_clause)) { + if (is_array($where_clause)) { + return $where_clause; + } else { + return array(0 => $where_clause); + } + } +} + +/** + * Analysing where clauses array + * + * @param array $where_clause_array array of where clauses + * @param string $table name of the table + * @param string $db name of the database + * + * @return array $where_clauses, $result, $rows + */ +function PMA_analyzeWhereClauses( + $where_clause_array, $table, $db +) { + $rows = array(); + $result = array(); + $where_clauses = array(); + $found_unique_key = false; + foreach ($where_clause_array as $key_id => $where_clause) { + + $local_query = 'SELECT * FROM ' + . PMA_Util::backquote($db) . '.' + . PMA_Util::backquote($table) + . ' WHERE ' . $where_clause . ';'; + $result[$key_id] = $GLOBALS['dbi']->query( + $local_query, null, PMA_DatabaseInterface::QUERY_STORE + ); + $rows[$key_id] = $GLOBALS['dbi']->fetchAssoc($result[$key_id]); + + $where_clauses[$key_id] = str_replace('\\', '\\\\', $where_clause); + $has_unique_condition = PMA_showEmptyResultMessageOrSetUniqueCondition( + $rows, $key_id, $where_clause_array, $local_query, $result + ); + if ($has_unique_condition) { + $found_unique_key = true; + } + } + return array($where_clauses, $result, $rows, $found_unique_key); +} + +/** + * Show message for empty result or set the unique_condition + * + * @param array $rows MySQL returned rows + * @param string $key_id ID in current key + * @param array $where_clause_array array of where clauses + * @param string $local_query query performed + * @param array $result MySQL result handle + * + * @return boolean $has_unique_condition + */ +function PMA_showEmptyResultMessageOrSetUniqueCondition($rows, $key_id, + $where_clause_array, $local_query, $result +) { + $has_unique_condition = false; + + // No row returned + if (! $rows[$key_id]) { + unset($rows[$key_id], $where_clause_array[$key_id]); + PMA_Response::getInstance()->addHtml( + PMA_Util::getMessage( + __('MySQL returned an empty result set (i.e. zero rows).'), + $local_query + ) + ); + /** + * @todo not sure what should be done at this point, but we must not + * exit if we want the message to be displayed + */ + } else {// end if (no row returned) + $meta = $GLOBALS['dbi']->getFieldsMeta($result[$key_id]); + + list($unique_condition, $tmp_clause_is_unique) + = PMA_Util::getUniqueCondition( + $result[$key_id], count($meta), $meta, $rows[$key_id], true + ); + + if (! empty($unique_condition)) { + $has_unique_condition = true; + } + unset($unique_condition, $tmp_clause_is_unique); + } + return $has_unique_condition; +} + +/** + * No primary key given, just load first row + * + * @param string $table name of the table + * @param string $db name of the database + * + * @return array containing $result and $rows arrays + */ +function PMA_loadFirstRow($table, $db) +{ + $result = $GLOBALS['dbi']->query( + 'SELECT * FROM ' . PMA_Util::backquote($db) + . '.' . PMA_Util::backquote($table) . ' LIMIT 1;', + null, + PMA_DatabaseInterface::QUERY_STORE + ); + $rows = array_fill(0, $GLOBALS['cfg']['InsertRows'], false); + return array($result, $rows); +} + +/** + * Add some url parameters + * + * @param array $url_params containing $db and $table as url parameters + * @param array $where_clause_array where clauses array + * @param string $where_clause where clause + * + * @return array Add some url parameters to $url_params array and return it + */ +function PMA_urlParamsInEditMode($url_params, $where_clause_array, $where_clause) +{ + if (isset($where_clause)) { + foreach ($where_clause_array as $where_clause) { + $url_params['where_clause'] = trim($where_clause); + } + } + if (! empty($_REQUEST['sql_query'])) { + $url_params['sql_query'] = $_REQUEST['sql_query']; + } + return $url_params; +} + +/** + * Show function fields in data edit view in pma + * + * @param array $url_params containing url parameters + * @param boolean $showFuncFields whether to show function field + * + * @return string an html snippet + */ +function PMA_showFunctionFieldsInEditMode($url_params, $showFuncFields) +{ + $params = array(); + if (! $showFuncFields) { + $params['ShowFunctionFields'] = 1; + } else { + $params['ShowFunctionFields'] = 0; + } + $params['ShowFieldTypesInDataEditView'] + = $GLOBALS['cfg']['ShowFieldTypesInDataEditView']; + $params['goto'] = 'sql.php'; + $this_url_params = array_merge($url_params, $params); + if (! $showFuncFields) { + return ' : ' + . __('Function') + . '' . "\n"; + } + return '' + . __('Function') + . '' . "\n"; +} + +/** + * Show field types in data edit view in pma + * + * @param array $url_params containing url parameters + * @param boolean $showColumnType whether to show column type + * + * @return string an html snippet + */ +function PMA_showColumnTypesInDataEditView($url_params, $showColumnType) +{ + $params = array(); + if (! $showColumnType) { + $params['ShowFieldTypesInDataEditView'] = 1; + } else { + $params['ShowFieldTypesInDataEditView'] = 0; + } + $params['ShowFunctionFields'] = $GLOBALS['cfg']['ShowFunctionFields']; + $params['goto'] = 'sql.php'; + $this_other_url_params = array_merge($url_params, $params); + if (! $showColumnType) { + return ' : ' + . __('Type') . '' . "\n"; + } + return '' . __('Type') . '' . "\n"; + +} + +/** + * Retrieve the default for datetime data type + * + * @param array $column containing column type, Default and null + * + * @return void + */ +function PMA_getDefaultForDatetime($column) +{ + // d a t e t i m e + // + // Current date should not be set as default if the field is NULL + // for the current row, but do not put here the current datetime + // if there is a default value (the real default value will be set + // in the Default value logic below) + + // Note: (tested in MySQL 4.0.16): when lang is some UTF-8, + // $column['Default'] is not set if it contains NULL: + // Array ([Field] => d [Type] => datetime [Null] => YES [Key] => + // [Extra] => [True_Type] => datetime) + // but, look what we get if we switch to iso: (Default is NULL) + // Array ([Field] => d [Type] => datetime [Null] => YES [Key] => + // [Default] => [Extra] => [True_Type] => datetime) + // so I force a NULL into it (I don't think it's possible + // to have an empty default value for DATETIME) + // then, the "if" after this one will work + if ($column['Type'] == 'datetime' + && ! isset($column['Default']) + && isset($column['Null']) + && $column['Null'] == 'YES' + ) { + $column['Default'] = null; + } +} + + /** + * Analyze the table column array + * + * @param array $column description of column in given table + * @param array $comments_map comments for every column that has a comment + * @param boolean $timestamp_seen whether a timestamp has been seen + * + * @return array description of column in given table + */ +function PMA_analyzeTableColumnsArray($column, $comments_map, $timestamp_seen) +{ + $column['Field_html'] = htmlspecialchars($column['Field']); + $column['Field_md5'] = md5($column['Field']); + // True_Type contains only the type (stops at first bracket) + $column['True_Type'] = preg_replace('@\(.*@s', '', $column['Type']); + PMA_getDefaultForDatetime($column); + $column['len'] = preg_match('@float|double@', $column['Type']) ? 100 : -1; + $column['Field_title'] = PMA_getColumnTitle($column, $comments_map); + $column['is_binary'] = PMA_isColumnBinary($column); + $column['is_blob'] = PMA_isColumnBlob($column); + $column['is_char'] = PMA_isColumnChar($column); + list($column['pma_type'], $column['wrap'], $column['first_timestamp']) + = PMA_getEnumSetAndTimestampColumns($column, $timestamp_seen); + + return $column; +} + + /** + * Retrieve the column title + * + * @param array $column description of column in given table + * @param array $comments_map comments for every column that has a comment + * + * @return string column title + */ +function PMA_getColumnTitle($column, $comments_map) +{ + if (isset($comments_map[$column['Field']])) { + return '' + . $column['Field_html'] . ''; + } else { + return $column['Field_html']; + } +} + + /** + * check whether the column is a bainary + * + * @param array $column description of column in given table + * + * @return boolean If check to ensure types such as "enum('one','two','binary',..)" + * or "enum('one','two','varbinary',..)" are not categorized as + * binary. + */ +function PMA_isColumnBinary($column) +{ + // The type column. + // Fix for bug #3152931 'ENUM and SET cannot have "Binary" option' + if (stripos($column['Type'], 'binary') === 0 + || stripos($column['Type'], 'varbinary') === 0 + ) { + return stristr($column['Type'], 'binary'); + } else { + return false; + } + +} + + /** + * check whether the column is a blob + * + * @param array $column description of column in given table + * + * @return boolean If check to ensure types such as "enum('one','two','blob',..)" + * or "enum('one','two','tinyblob',..)" etc. are not categorized + * as blob. + */ +function PMA_isColumnBlob($column) +{ + if (stripos($column['Type'], 'blob') === 0 + || stripos($column['Type'], 'tinyblob') === 0 + || stripos($column['Type'], 'mediumblob') === 0 + || stripos($column['Type'], 'longblob') === 0 + ) { + return stristr($column['Type'], 'blob'); + } else { + return false; + } +} + +/** + * check is table column char + * + * @param array $column description of column in given table + * + * @return boolean If check to ensure types such as "enum('one','two','char',..)" or + * "enum('one','two','varchar',..)" are not categorized as char. + */ +function PMA_isColumnChar($column) +{ + if (stripos($column['Type'], 'char') === 0 + || stripos($column['Type'], 'varchar') === 0 + ) { + return stristr($column['Type'], 'char'); + } else { + return false; + } +} +/** + * Retrieve set, enum, timestamp table columns + * + * @param array $column description of column in given table + * @param boolean $timestamp_seen whether a timestamp has been seen + * + * @return array $column['pma_type'], $column['wrap'], $column['first_timestamp'] + */ +function PMA_getEnumSetAndTimestampColumns($column, $timestamp_seen) +{ + $column['first_timestamp'] = false; + switch ($column['True_Type']) { + case 'set': + $column['pma_type'] = 'set'; + $column['wrap'] = ''; + break; + case 'enum': + $column['pma_type'] = 'enum'; + $column['wrap'] = ''; + break; + case 'timestamp': + if (! $timestamp_seen) { // can only occur once per table + $timestamp_seen = true; + $column['first_timestamp'] = true; + } + $column['pma_type'] = $column['Type']; + $column['wrap'] = ' nowrap'; + break; + + default: + $column['pma_type'] = $column['Type']; + $column['wrap'] = ' nowrap'; + break; + } + return array($column['pma_type'], $column['wrap'], $column['first_timestamp']); +} + +/** + * The function column + * We don't want binary data to be destroyed + * Note: from the MySQL manual: "BINARY doesn't affect how the column is + * stored or retrieved" so it does not mean that the contents is binary + * + * @param array $column description of column in given table + * @param boolean $is_upload upload or no + * @param string $column_name_appendix the name atttibute + * @param string $unnullify_trigger validation string + * @param array $no_support_types list of datatypes that are not (yet) + * handled by PMA + * @param integer $tabindex_for_function +3000 + * @param integer $tabindex tab index + * @param integer $idindex id index + * @param boolean $insert_mode insert mode or edit mode + * + * @return string an html sippet + */ +function PMA_getFunctionColumn($column, $is_upload, $column_name_appendix, + $unnullify_trigger, $no_support_types, $tabindex_for_function, + $tabindex, $idindex, $insert_mode +) { + $html_output = ''; + if (($GLOBALS['cfg']['ProtectBinary'] && $column['is_blob'] && ! $is_upload) + || ($GLOBALS['cfg']['ProtectBinary'] === 'all' && $column['is_binary']) + || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' && ! $column['is_blob']) + ) { + $html_output .= '' . __('Binary') . '' . "\n"; + } elseif (strstr($column['True_Type'], 'enum') + || strstr($column['True_Type'], 'set') + || in_array($column['pma_type'], $no_support_types) + ) { + $html_output .= '--' . "\n"; + } else { + $html_output .= '' . "\n"; + + $html_output .= '' . "\n"; + $html_output .= '' . "\n"; + } + return $html_output; +} + +/** + * The null column + * + * @param array $column description of column in given table + * @param string $column_name_appendix the name atttibute + * @param array $real_null_value is column value null or not null + * @param integer $tabindex tab index + * @param integer $tabindex_for_null +6000 + * @param integer $idindex id index + * @param array $vkey [multi_edit]['row_id'] + * @param array $foreigners keys into foreign fields + * @param array $foreignData data about the foreign keys + * + * @return string an html snippet + */ +function PMA_getNullColumn($column, $column_name_appendix, $real_null_value, + $tabindex, $tabindex_for_null, $idindex, $vkey, $foreigners, $foreignData +) { + if ($column['Null'] != 'YES') { + return "\n"; + } + $html_output = ''; + $html_output .= '' . "\n"; + $html_output .= ''; + + // nullify_code is needed by the js nullify() function + $nullify_code = PMA_getNullifyCodeForNullColumn( + $column, $foreigners, $foreignData + ); + // to be able to generate calls to nullify() in jQuery + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= '' . "\n"; + + return $html_output; +} + +/** + * Retrieve the nullify code for the null column + * + * @param array $column description of column in given table + * @param array $foreigners keys into foreign fields + * @param array $foreignData data about the foreign keys + * + * @return integer $nullify_code + */ +function PMA_getNullifyCodeForNullColumn($column, $foreigners, $foreignData) +{ + if (strstr($column['True_Type'], 'enum')) { + if (strlen($column['Type']) > 20) { + $nullify_code = '1'; + } else { + $nullify_code = '2'; + } + } elseif (strstr($column['True_Type'], 'set')) { + $nullify_code = '3'; + } elseif ($foreigners + && isset($foreigners[$column['Field']]) + && $foreignData['foreign_link'] == false + ) { + // foreign key in a drop-down + $nullify_code = '4'; + } elseif ($foreigners + && isset($foreigners[$column['Field']]) + && $foreignData['foreign_link'] == true + ) { + // foreign key with a browsing icon + $nullify_code = '6'; + } else { + $nullify_code = '5'; + } + return $nullify_code; +} + +/** + * Get the HTML elements for value column in insert form + * (here, "column" is used in the sense of HTML column in HTML table) + * + * @param array $column description of column in given table + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name atttibute + * @param string $unnullify_trigger validation string + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param array $data description of the column field + * @param array $special_chars special characters + * @param array $foreignData data about the foreign keys + * @param boolean $odd_row whether row is odd + * @param array $paramTableDbArray array containing $table and $db + * @param array $rownumber the row number + * @param array $titles An HTML IMG tag for a particular icon from + * a theme, which may be an actual file or + * an icon from a sprite + * @param array $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 integer $biggest_max_file_size 0 intger + * @param string $default_char_editing default char editing mode which is stroe + * in the config.inc.php script + * @param array $no_support_types list of datatypes that are not (yet) + * handled by PMA + * @param array $gis_data_types list of GIS data types + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly + * enum_set_values (another array) + * + * @return string an html snippet + */ +function PMA_getValueColumn($column, $backup_field, $column_name_appendix, + $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex, $data, + $special_chars, $foreignData, $odd_row, $paramTableDbArray, $rownumber, + $titles, $text_dir, $special_chars_encoded, $vkey, + $is_upload, $biggest_max_file_size, + $default_char_editing, $no_support_types, $gis_data_types, $extracted_columnspec +) { + $html_output = ''; + + if ($foreignData['foreign_link'] == true) { + $html_output .= PMA_getForeignLink( + $column, $backup_field, $column_name_appendix, + $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex, $data, + $paramTableDbArray, $rownumber, $titles + ); + + } elseif (is_array($foreignData['disp_row'])) { + $html_output .= PMA_dispRowForeignData( + $backup_field, $column_name_appendix, + $unnullify_trigger, $tabindex, $tabindex_for_value, + $idindex, $data, $foreignData + ); + + } elseif ($GLOBALS['cfg']['LongtextDoubleTextarea'] + && strstr($column['pma_type'], 'longtext') + ) { + $html_output = ' '; + $html_output .= ''; + $html_output .= '' + . ''; + $html_output .= PMA_getTextarea( + $column, $backup_field, $column_name_appendix, $unnullify_trigger, + $tabindex, $tabindex_for_value, $idindex, $text_dir, + $special_chars_encoded + ); + + } elseif (strstr($column['pma_type'], 'text')) { + + $html_output .= PMA_getTextarea( + $column, $backup_field, $column_name_appendix, $unnullify_trigger, + $tabindex, $tabindex_for_value, $idindex, $text_dir, + $special_chars_encoded + ); + $html_output .= "\n"; + if (strlen($special_chars) > 32000) { + $html_output .= "\n"; + $html_output .= '' . __( + 'Because of its length,
      this column might not be editable' + ); + } + + } elseif ($column['pma_type'] == 'enum') { + $html_output .= PMA_getPmaTypeEnum( + $column, $backup_field, $column_name_appendix, $extracted_columnspec, + $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex, $data + ); + + } elseif ($column['pma_type'] == 'set') { + $html_output .= PMA_getPmaTypeSet( + $column, $extracted_columnspec, $backup_field, + $column_name_appendix, $unnullify_trigger, $tabindex, + $tabindex_for_value, $idindex, $data + ); + + } elseif ($column['is_binary'] || $column['is_blob']) { + $html_output .= PMA_getBinaryAndBlobColumn( + $column, $data, $special_chars, $biggest_max_file_size, + $backup_field, $column_name_appendix, $unnullify_trigger, $tabindex, + $tabindex_for_value, $idindex, $text_dir, $special_chars_encoded, + $vkey, $is_upload + ); + + } elseif (! in_array($column['pma_type'], $no_support_types)) { + $html_output .= PMA_getValueColumnForOtherDatatypes( + $column, $default_char_editing, $backup_field, + $column_name_appendix, $unnullify_trigger, $tabindex, $special_chars, + $tabindex_for_value, $idindex, $text_dir, $special_chars_encoded, + $data, $extracted_columnspec + ); + } + + if (in_array($column['pma_type'], $gis_data_types)) { + $html_output .= PMA_getHTMLforGisDataTypes(); + } + + return $html_output; +} + +/** + * Get HTML for foreign link in insert form + * + * @param array $column description of column in given table + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name atttibute + * @param string $unnullify_trigger validation string + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param array $data data to edit + * @param array $paramTableDbArray array containing $table and $db + * @param array $rownumber the row number + * @param array $titles An HTML IMG tag for a particular icon from + * a theme, which may be an actual file or + * an icon from a sprite + * + * @return string an html snippet + */ +function PMA_getForeignLink($column, $backup_field, $column_name_appendix, + $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex, $data, + $paramTableDbArray, $rownumber, $titles +) { + list($table, $db) = $paramTableDbArray; + $html_output = ''; + $html_output .= $backup_field . "\n"; + + $html_output .= ''; + + $html_output .= ''; + + $html_output .= '' + . str_replace("'", "\'", $titles['Browse']) . ''; + return $html_output; +} + +/** + * Get HTML to display foreign data + * + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name atttibute + * @param string $unnullify_trigger validation string + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param array $data data to edit + * @param array $foreignData data about the foreign keys + * + * @return string an html snippet + */ +function PMA_dispRowForeignData($backup_field, $column_name_appendix, + $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex, $data, + $foreignData +) { + $html_output = ''; + $html_output .= $backup_field . "\n"; + $html_output .= ''; + + $html_output .= ''; + + return $html_output; +} + +/** + * Get HTML textarea for insert form + * + * @param array $column column information + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name atttibute + * @param string $unnullify_trigger validation string + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param array $text_dir text direction + * @param array $special_chars_encoded replaced char if the string starts + * with a \r\n pair (0x0d0a) add an extra \n + * + * @return string an html snippet + */ +function PMA_getTextarea($column, $backup_field, $column_name_appendix, + $unnullify_trigger, + $tabindex, $tabindex_for_value, $idindex, $text_dir, $special_chars_encoded +) { + $the_class = ''; + $textAreaRows = $GLOBALS['cfg']['TextareaRows']; + $textareaCols = $GLOBALS['cfg']['TextareaCols']; + + if ($column['is_char']) { + /** + * @todo clarify the meaning of the "textfield" class and explain + * why character columns have the "char" class instead + */ + $the_class = 'char'; + $textAreaRows = $GLOBALS['cfg']['CharTextareaRows']; + $textareaCols = $GLOBALS['cfg']['CharTextareaCols']; + } elseif ($GLOBALS['cfg']['LongtextDoubleTextarea'] + && strstr($column['pma_type'], 'longtext') + ) { + $textAreaRows = $GLOBALS['cfg']['TextareaRows'] * 2; + $textareaCols = $GLOBALS['cfg']['TextareaCols'] * 2; + } + $html_output = $backup_field . "\n" + . ''; + + return $html_output; +} + +/** + * Get HTML for enum type + * + * @param type $column description of column in given table + * @param type $backup_field hidden input field + * @param type $column_name_appendix the name atttibute + * @param type $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly + * enum_set_values (another array) + * @param type $unnullify_trigger validation string + * @param type $tabindex tab index + * @param type $tabindex_for_value offset for the values tabindex + * @param type $idindex id index + * @param type $data data to edit + * + * @return string an html snippet + */ +function PMA_getPmaTypeEnum($column, $backup_field, $column_name_appendix, + $extracted_columnspec, $unnullify_trigger, $tabindex, $tabindex_for_value, + $idindex, $data +) { + $html_output = ''; + if (! isset($column['values'])) { + $column['values'] = PMA_getColumnEnumValues( + $column, $extracted_columnspec + ); + } + $column_enum_values = $column['values']; + $html_output .= ''; + $html_output .= ''; + $html_output .= "\n" . ' ' . $backup_field . "\n"; + if (strlen($column['Type']) > 20) { + $html_output .= PMA_getDropDownDependingOnLength( + $column, $column_name_appendix, $unnullify_trigger, + $tabindex, $tabindex_for_value, $idindex, $data, $column_enum_values + ); + } else { + $html_output .= PMA_getRadioButtonDependingOnLength( + $column_name_appendix, $unnullify_trigger, + $tabindex, $column, $tabindex_for_value, + $idindex, $data, $column_enum_values + ); + } + 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 + */ +function PMA_getColumnEnumValues($column, $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 atttibute + * @param string $unnullify_trigger validation string + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param array $data data to edit + * @param array $column_enum_values $column['values'] + * + * @return string an html snippet + */ +function PMA_getDropDownDependingOnLength( + $column, $column_name_appendix, $unnullify_trigger, + $tabindex, $tabindex_for_value, $idindex, $data, $column_enum_values +) { + $html_output = ''; + return $html_output; +} + +/** + * Get HTML radio button for less than 20 string length + * + * @param string $column_name_appendix the name atttibute + * @param string $unnullify_trigger validation string + * @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 array $data data to edit + * @param array $column_enum_values $column['values'] + * + * @return string an html snippet + */ +function PMA_getRadioButtonDependingOnLength( + $column_name_appendix, $unnullify_trigger, + $tabindex, $column, $tabindex_for_value, $idindex, $data, $column_enum_values +) { + $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 atttibute + * @param string $unnullify_trigger validation string + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param array $data description of the column field + * + * @return string an html snippet + */ +function PMA_getPmaTypeSet( + $column, $extracted_columnspec, $backup_field, + $column_name_appendix, $unnullify_trigger, $tabindex, + $tabindex_for_value, $idindex, $data +) { + list($column_set_values, $select_size) = PMA_getColumnSetValueAndSelectSize( + $column, $extracted_columnspec + ); + $vset = array_flip(explode(',', $data)); + $html_output = $backup_field . "\n"; + $html_output .= ''; + $html_output .= ''; + 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'] + */ +function PMA_getColumnSetValueAndSelectSize($column, $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 array $data data to edit + * @param array $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 atttibute + * @param string $unnullify_trigger validation string + * @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 + * + * @return string an html snippet + */ +function PMA_getBinaryAndBlobColumn( + $column, $data, $special_chars, $biggest_max_file_size, + $backup_field, $column_name_appendix, $unnullify_trigger, $tabindex, + $tabindex_for_value, $idindex, $text_dir, $special_chars_encoded, + $vkey, $is_upload +) { + $html_output = ''; + if (($GLOBALS['cfg']['ProtectBinary'] && $column['is_blob']) + || ($GLOBALS['cfg']['ProtectBinary'] == 'all' && $column['is_binary']) + || ($GLOBALS['cfg']['ProtectBinary'] == 'noblob' && !$column['is_blob']) + ) { + $html_output .= __('Binary - do not edit'); + if (isset($data)) { + $data_size = PMA_Util::formatByteDown( + strlen(stripslashes($data)), 3, 1 + ); + $html_output .= ' ('. $data_size[0] . ' ' . $data_size[1] . ')'; + unset($data_size); + } + $html_output .= '' + . ''; + } elseif ($column['is_blob'] + || ($column['len'] > $GLOBALS['cfg']['LimitChars']) + ) { + $html_output .= "\n" . PMA_getTextarea( + $column, $backup_field, $column_name_appendix, $unnullify_trigger, + $tabindex, $tabindex_for_value, $idindex, $text_dir, + $special_chars_encoded + ); + } 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" . PMA_getHTMLinput( + $column, $column_name_appendix, $special_chars, $fieldsize, + $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex + ); + } + + if ($is_upload && $column['is_blob']) { + $html_output .= '
      ' + . ' '; + list($html_out, $biggest_max_file_size) = PMA_getMaxUploadSize( + $column, $biggest_max_file_size + ); + $html_output .= $html_out; + } + + if (!empty($GLOBALS['cfg']['UploadDir'])) { + $html_output .= PMA_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 array $special_chars special characters + * @param integer $fieldsize html field size + * @param string $unnullify_trigger validation string + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * + * @return string an html snippet + */ +function PMA_getHTMLinput($column, $column_name_appendix, $special_chars, + $fieldsize, $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex +) { + 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') + ) + ); + + $input_type = 'text'; + $the_class = 'textfield'; + if ($column['pma_type'] === 'date') { + $the_class .= ' datefield'; + $input_type = 'date'; + } elseif ($column['pma_type'] === 'datetime' + || substr($column['pma_type'], 0, 9) === 'timestamp' + ) { + $the_class .= ' datetimefield'; + } elseif ($column['pma_type'] === 'time') { + $input_type = 'time'; + } + $input_min_max = false; + if (in_array( + $column['True_Type'], + array('tinyint', 'smallint', 'mediumint', 'int', 'bigint') + )) { + $input_type = 'number'; + $is_unsigned = substr($column['pma_type'], -9) === ' unsigned'; + $min_max_values + = $min_max_data[$is_unsigned ? 'unsigned' : 'signed'] + [$column['True_Type']]; + $input_min_max = 'min="' . $min_max_values[0] . '" ' + . 'max="' . $min_max_values[1] . '" '; + } + 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 + */ +function PMA_getSelectOptionForUpload($vkey, $column) +{ + $files = PMA_getFileSelectOptions( + PMA_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"; + } +} + +/** + * 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 + */ +function PMA_getMaxUploadSize($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 + = PMA_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 stroe + * in the config.inc.php script + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name atttibute + * @param string $unnullify_trigger validation string + * @param integer $tabindex tab index + * @param array $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 array $special_chars_encoded replaced char if the string starts + * with a \r\n pair (0x0d0a) add an extra \n + * @param strign $data data to edit + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly + * enum_set_values (another array) + * + * @return string an html snippet + */ +function PMA_getValueColumnForOtherDatatypes($column, $default_char_editing, + $backup_field, + $column_name_appendix, $unnullify_trigger, $tabindex, $special_chars, + $tabindex_for_value, $idindex, $text_dir, $special_chars_encoded, $data, + $extracted_columnspec +) { + $fieldsize = PMA_getColumnSize($column, $extracted_columnspec); + $html_output = $backup_field . "\n"; + if ($column['is_char'] + && ($GLOBALS['cfg']['CharEditing'] == 'textarea' + || strpos($data, "\n") !== false) + ) { + $html_output .= "\n"; + $GLOBALS['cfg']['CharEditing'] = $default_char_editing; + $html_output .= PMA_getTextarea( + $column, $backup_field, $column_name_appendix, $unnullify_trigger, + $tabindex, $tabindex_for_value, $idindex, $text_dir, + $special_chars_encoded + ); + } else { + $html_output .= PMA_getHTMLinput( + $column, $column_name_appendix, $special_chars, + $fieldsize, $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex + ); + + 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 + */ +function PMA_getColumnSize($column, $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 + */ +function PMA_getHTMLforGisDataTypes() +{ + $edit_str = PMA_Util::getIcon('b_edit.png', __('Edit/Insert')); + return '' + . PMA_Util::linkOrButton( + '#', $edit_str, array(), false, false, '_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 + */ +function PMA_getContinueInsertionForm($table, $db, $where_clause_array, $err_url) +{ + $html_output = '
      ' + . PMA_URL_getHiddenInputs($db, $table) + . '' + . '' + . ''; + + if (isset($_REQUEST['where_clause'])) { + foreach ($where_clause_array as $key_id => $where_clause) { + + $html_output .= ''. "\n"; + } + } + $tmp = '' . "\n"; + $html_output .= "\n" . sprintf(__('Continue insertion with %s rows'), $tmp); + unset($tmp); + $html_output .= '
      ' . "\n"; + return $html_output; +} + +/** + * Get action panel + * + * @param array $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 + */ +function PMA_getActionsPanel($where_clause, $after_insert, $tabindex, + $tabindex_for_value, $found_unique_key +) { + $html_output = '
      ' + . '' + . '' + . '' + . '' + . '' + . ''; + $html_output .='' + . PMA_getSumbitAndResetButtonForActionsPanel($tabindex, $tabindex_for_value) + . '' + . '
      ' + . PMA_getSubmitTypeDropDown($where_clause, $tabindex, $tabindex_for_value) + . "\n"; + + $html_output .= '' + . '   ' + . __('and then') . '   ' + . '' + . PMA_getAfterInsertDropDown( + $where_clause, $after_insert, $found_unique_key + ) + . '
      ' + . '
      '; + return $html_output; +} + +/** + * Get a HTML drop down for submit types + * + * @param array $where_clause where clause + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * + * @return string an html snippet + */ +function PMA_getSubmitTypeDropDown($where_clause, $tabindex, $tabindex_for_value) +{ + $html_output = ''; + return $html_output; +} + +/** + * Get HTML drop down for after insert + * + * @param array $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 + */ +function PMA_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 + */ +function PMA_getSumbitAndResetButtonForActionsPanel($tabindex, $tabindex_for_value) +{ + return '' + . PMA_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 + */ +function PMA_getHeadAndFootOfInsertRowTable($url_params) +{ + $html_output = '' + . '' + . '' + . ''; + + if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) { + $html_output .= PMA_showColumnTypesInDataEditView($url_params, true); + } + if ($GLOBALS['cfg']['ShowFunctionFields']) { + $html_output .= PMA_showFunctionFieldsInEditMode($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 + * + * @return array $real_null_value, $data, $special_chars, $backup_field, + * $special_chars_encoded + */ +function PMA_getSpecialCharsAndBackupFieldForExistingRow( + $current_row, $column, $extracted_columnspec, + $real_null_value, $gis_data_types, $column_name_appendix +) { + $special_chars_encoded = ''; + $data = null; + // (we are editing) + if (is_null($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 = PMA_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' + ) { + $current_row[$column['Field']] = PMA_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']] = PMA_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']) + ) { + if ($_SESSION['tmpval']['display_binary_as_hex'] + && $GLOBALS['cfg']['ShowFunctionFields'] + ) { + $current_row[$column['Field']] = bin2hex( + $current_row[$column['Field']] + ); + $column['display_binary_as_hex'] = true; + } else { + $current_row[$column['Field']] + = PMA_Util::replaceBinaryContents( + $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 + = PMA_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($_REQUEST['default_action']) + && $_REQUEST['default_action'] === 'insert' + ) { + if ($column['Key'] === 'PRI' + && 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 type $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 + */ +function PMA_getSpecialCharsAndBackupFieldForInsertingMode( + $column, $real_null_value +) { + if (! isset($column['Default'])) { + $column['Default'] = ''; + $real_null_value = true; + $data = ''; + } else { + $data = $column['Default']; + } + + if ($column['True_Type'] == 'bit') { + $special_chars = PMA_Util::convertBitDefaultValue($column['Default']); + } elseif (substr($column['True_Type'], 0, 9) == 'timestamp' + || $column['True_Type'] == 'datetime' + || $column['True_Type'] == 'time' + ) { + $special_chars = PMA_Util::addMicroseconds($column['Default']); + } else { + $special_chars = htmlspecialchars($column['Default']); + } + $backup_field = ''; + $special_chars_encoded = PMA_Util::duplicateFirstNewline($special_chars); + // this will select the UNHEX function while inserting + if (($column['is_binary'] + || ($column['is_blob'] && ! $GLOBALS['cfg']['ProtectBinary'])) + && (isset($_SESSION['tmpval']['display_binary_as_hex']) + && $_SESSION['tmpval']['display_binary_as_hex']) + && $GLOBALS['cfg']['ShowFunctionFields'] + ) { + $column['display_binary_as_hex'] = true; + } + 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 + */ +function PMA_getParamsForUpdateOrInsert() +{ + if (isset($_REQUEST['where_clause'])) { + // we were editing something => use the WHERE clause + $loop_array = is_array($_REQUEST['where_clause']) + ? $_REQUEST['where_clause'] + : array($_REQUEST['where_clause']); + $using_key = true; + $is_insert = $_REQUEST['submit_type'] == 'insert' + || $_REQUEST['submit_type'] == 'showinsert' + || $_REQUEST['submit_type'] == 'insertignore'; + } else { + // new row => use indexes + $loop_array = array(); + foreach ($_REQUEST['fields']['multi_edit'] as $key => $dummy) { + $loop_array[] = $key; + } + $using_key = false; + $is_insert = true; + } + $is_insertignore = $_REQUEST['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 + */ +function PMA_isInsertRow() +{ + if (isset($_REQUEST['insert_rows']) + && is_numeric($_REQUEST['insert_rows']) + && $_REQUEST['insert_rows'] != $GLOBALS['cfg']['InsertRows'] + ) { + $GLOBALS['cfg']['InsertRows'] = $_REQUEST['insert_rows']; + $response = PMA_Response::getInstance(); + $header = $response->getHeader(); + $scripts = $header->getScripts(); + $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 + */ +function PMA_setSessionForEditNext($one_where_clause) +{ + $local_query = 'SELECT * FROM ' . PMA_Util::backquote($GLOBALS['db']) + . '.' . PMA_Util::backquote($GLOBALS['table']) . ' WHERE ' + . str_replace('` =', '` >', $one_where_clause) . ' LIMIT 1;'; + + $res = $GLOBALS['dbi']->query($local_query); + $row = $GLOBALS['dbi']->fetchRow($res); + $meta = $GLOBALS['dbi']->getFieldsMeta($res); + // must find a unique condition based on unique key, + // not a combination of all fields + list($unique_condition, $clause_is_unique) + = PMA_Util::getUniqueCondition( + $res, count($meta), $meta, $row, true + ); + 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 + */ +function PMA_getGotoInclude($goto_include) +{ + $valid_options = array('new_insert', 'same_insert', 'edit_next'); + if (isset($_REQUEST['after_insert']) + && in_array($_REQUEST['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'])) { + $GLOBALS['table'] = ''; + } + } + if (! $goto_include) { + if (! strlen($GLOBALS['table'])) { + $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 + */ +function PMA_getErrorUrl($url_params) +{ + if (isset($_REQUEST['err_url'])) { + return $_REQUEST['err_url']; + } else { + return 'tbl_change.php' . PMA_URL_getCommon($url_params); + } +} + +/** + * Builds the sql query + * + * @param boolean $is_insertignore $_REQUEST['submit_type'] == 'insertignore' + * @param array $query_fields column names array + * @param array $value_sets array of query values + * + * @return string a query + */ +function PMA_buildSqlQuery($is_insertignore, $query_fields, $value_sets) +{ + if ($is_insertignore) { + $insert_command = 'INSERT IGNORE '; + } else { + $insert_command = 'INSERT '; + } + $query[] = $insert_command . 'INTO ' + . PMA_Util::backquote($GLOBALS['db']) . '.' + . PMA_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 PMA_buildSqlQuery() + * + * @return array $url_params, $total_affected_rows, $last_messages + * $warning_messages, $error_messages, $return_to_sql_query + */ +function PMA_executeSqlQuery($url_params, $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 ($_REQUEST['submit_type'] == 'showinsert') { + $last_messages[] = PMA_Message::notice(__('Showing SQL query')); + continue; + } + if ($GLOBALS['cfg']['IgnoreMultiSubmitErrors']) { + $result = $GLOBALS['dbi']->tryQuery($single_query); + } else { + $result = $GLOBALS['dbi']->query($single_query); + } + if (! $result) { + $error_messages[] = PMA_Message::sanitize($GLOBALS['dbi']->getError()); + } else { + // The next line contains a real assignment, it's not a typo + if ($tmp = @$GLOBALS['dbi']->affectedRows()) { + $total_affected_rows += $tmp; + } + unset($tmp); + + $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 + + if ($total_affected_rows > 0) { + $insert_id = $insert_id + $total_affected_rows - 1; + } + $last_message = PMA_Message::notice(__('Inserted row id: %1$d')); + $last_message->addParam($insert_id); + $last_messages[] = $last_message; + } + $GLOBALS['dbi']->freeResult($result); + } + $warning_messages = PMA_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 + */ +function PMA_getWarningMessages() +{ + $warning_essages = array(); + foreach ($GLOBALS['dbi']->getWarnings() as $warning) { + $warning_essages[] = PMA_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 string $relation_field_value 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 + */ +function PMA_getDisplayValueForForeignTableColumn($where_comparison, + $relation_field_value, $map, $relation_field +) { + $display_field = PMA_getDisplayField( + $map[$relation_field]['foreign_db'], + $map[$relation_field]['foreign_table'] + ); + // Field to display from the foreign table? + if (isset($display_field) && strlen($display_field)) { + $dispsql = 'SELECT ' . PMA_Util::backquote($display_field) + . ' FROM ' . PMA_Util::backquote($map[$relation_field]['foreign_db']) + . '.' . PMA_Util::backquote($map[$relation_field]['foreign_table']) + . ' WHERE ' . PMA_Util::backquote($map[$relation_field]['foreign_field']) + . $where_comparison; + $dispresult = $GLOBALS['dbi']->tryQuery( + $dispsql, null, PMA_DatabaseInterface::QUERY_STORE + ); + if ($dispresult && $GLOBALS['dbi']->numRows($dispresult) > 0) { + list($dispval) = $GLOBALS['dbi']->fetchRow($dispresult, 0); + } + @$GLOBALS['dbi']->freeResult($dispresult); + return $dispval; + } + return ''; +} + +/** + * Display option in the cell according to user choises + * + * @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 + */ +function PMA_getLinkForRelationalDisplayField($map, $relation_field, + $where_comparison, $dispval, $relation_field_value +) { + 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' => $map[$relation_field]['foreign_db'], + 'table' => $map[$relation_field]['foreign_table'], + 'pos' => '0', + 'sql_query' => 'SELECT * FROM ' + . PMA_Util::backquote($map[$relation_field]['foreign_db']) + . '.' . PMA_Util::backquote($map[$relation_field]['foreign_table']) + . ' WHERE ' . PMA_Util::backquote($map[$relation_field]['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 + * + * @return array $extra_data + */ +function PMA_transformEditedValues($db, $table, + $transformation, $edited_values, $file, $column_name, $extra_data +) { + foreach ($edited_values as $cell_index => $curr_cell_edited_values) { + if (isset($curr_cell_edited_values[$column_name])) { + $column_data = $curr_cell_edited_values[$column_name]; + + $_url_params = array( + 'db' => $db, + 'table' => $table, + 'where_clause' => $_REQUEST['where_clause'], + 'transform_key' => $column_name + ); + + $include_file = 'libraries/plugins/transformations/' . $file; + if (file_exists($include_file)) { + include_once $include_file; + + $transform_options = PMA_Transformation_getOptions( + isset($transformation['transformation_options']) + ? $transformation['transformation_options'] + : '' + ); + $transform_options['wrapper_link'] + = PMA_URL_getCommon($_url_params); + $class_name = str_replace('.class.php', '', $file); + $plugin_manager = null; + $transformation_plugin = new $class_name( + $plugin_manager + ); + } + + $extra_data['transformations'][$cell_index] + = $transformation_plugin->applyTransformation( + $column_data, + $transform_options, + '' + ); + } + } // end of loop for each transformation cell + return $extra_data; +} + +/** + * Get current value in multi edit mode + * + * @param array $multi_edit_colummns multiple edit column array + * @param array $multi_edit_columns_name multiple edit columns name array + * @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 initialy $val is $multi_edit_colummns[$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 + */ +function PMA_getCurrentValueAsAnArrayForMultipleEdit($multi_edit_colummns, + $multi_edit_columns_name, $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 = $GLOBALS['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 = substr($current_value, 1, strlen($current_value) - 2); + // 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") + ) { + return $multi_edit_funcs[$key] . '(' . $current_value . ",'" + . PMA_Util::sqlAddSlashes($multi_edit_salt[$key]) . "')"; + } else { + return $multi_edit_funcs[$key] . '(' . $current_value . ')'; + } + } else { + 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) + */ +function PMA_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)) { + $query_values[] = $current_value_as_an_array; + // first inserted row so prepare the list of fields + if (empty($value_sets)) { + $query_fields[] = PMA_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[] + = PMA_Util::backquote($multi_edit_columns_name[$key]) + . ' = ' . $current_value_as_an_array; + } elseif (empty($multi_edit_funcs[$key]) + && isset($multi_edit_columns_prev[$key]) + && ("'" . PMA_Util::sqlAddSlashes($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[] + = PMA_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 $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 string $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 array $where_clause where clauses + * @param string $table table name + * + * @return string $current_value current column value in the form + */ +function PMA_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 +) { + // 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) && isset($where_clause) + ) { + $protected_row = $GLOBALS['dbi']->fetchSingleRow( + 'SELECT * FROM ' . PMA_Util::backquote($table) + . ' WHERE ' . $where_clause . ';' + ); + } + + if (false !== $possibly_uploaded_val) { + $current_value = $possibly_uploaded_val; + } 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' && 0 === strlen($current_value)) { + // 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($_REQUEST['fields']['multi_edit'][$rownumber][$key])) { + $current_value = implode( + ',', $_REQUEST['fields']['multi_edit'][$rownumber][$key] + ); + $current_value = "'" . PMA_Util::sqlAddSlashes($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 + // coulmns 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 == 'bit') { + $current_value = preg_replace('/[^01]/', '0', $current_value); + $current_value = "b'" . PMA_Util::sqlAddSlashes($current_value) . "'"; + } elseif (! ($type == 'datetime' || $type == 'timestamp') + || $current_value != 'CURRENT_TIMESTAMP' + ) { + $current_value = "'" . PMA_Util::sqlAddSlashes($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 + */ +function PMA_verifyWhetherValueCanBeTruncatedAndAppendExtraData( + $db, $table, $column_name, &$extra_data +) { + + $extra_data['isNeedToRecheck'] = true; + + $sql_for_real_value = 'SELECT '. PMA_Util::backquote($table) . '.' + . PMA_Util::backquote($column_name) + . ' FROM ' . PMA_Util::backquote($db) . '.' + . PMA_Util::backquote($table) + . ' WHERE ' . $_REQUEST['where_clause'][0]; + + $result = $GLOBALS['dbi']->tryQuery($sql_for_real_value); + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + $meta = $fields_meta[0]; + $new_value = $GLOBALS['dbi']->fetchValue($result); + if ($new_value !== false) { + if ((substr($meta->type, 0, 9) == 'timestamp') + || ($meta->type == 'datetime') + || ($meta->type == 'time') + ) { + $new_value = PMA_Util::addMicroseconds($new_value); + } + $extra_data['truncatableFieldValue'] = $new_value; + } else { + $extra_data['isNeedToRecheck'] = false; + } + +} + +/** + * Function to get the columns of a table + * + * @param string $db current db + * @param string $table current table + * + * @return array + */ +function PMA_getTableColumns($db, $table) +{ + $GLOBALS['dbi']->selectDb($db); + return array_values($GLOBALS['dbi']->getColumns($db, $table)); + +} + +/** + * Function to determine Insert/Edit rows + * + * @param string $where_clause where clause + * @param string $db current database + * @param string $table current table + * + * @return mixed + */ +function PMA_determineInsertOrEdit($where_clause, $db, $table) +{ + if (isset($_REQUEST['where_clause'])) { + $where_clause = $_REQUEST['where_clause']; + } + if (isset($_SESSION['edit_next'])) { + $where_clause = $_SESSION['edit_next']; + unset($_SESSION['edit_next']); + $after_insert = 'edit_next'; + } + if (isset($_REQUEST['ShowFunctionFields'])) { + $GLOBALS['cfg']['ShowFunctionFields'] = $_REQUEST['ShowFunctionFields']; + } + if (isset($_REQUEST['ShowFieldTypesInDataEditView'])) { + $GLOBALS['cfg']['ShowFieldTypesInDataEditView'] + = $_REQUEST['ShowFieldTypesInDataEditView']; + } + if (isset($_REQUEST['after_insert'])) { + $after_insert = $_REQUEST['after_insert']; + } + + if (isset($where_clause)) { + // we are editing + $insert_mode = false; + $where_clause_array = PMA_getWhereClauseArray($where_clause); + list($where_clauses, $result, $rows, $found_unique_key) + = PMA_analyzeWhereClauses( + $where_clause_array, $table, $db + ); + } else { + // we are inserting + $insert_mode = true; + $where_clause = null; + list($result, $rows) = PMA_loadFirstRow($table, $db); + $where_clauses = null; + $where_clause_array = null; + $found_unique_key = false; + } + + // Copying a row - fetched data will be inserted as a new row, + // therefore the where clause is needless. + if (isset($_REQUEST['default_action']) + && $_REQUEST['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 + */ +function PMA_getCommentsMap($db, $table) +{ + /** + * get table information + * @todo should be done by a Table object + */ + include 'libraries/tbl_info.inc.php'; + + /** + * Get comments for table fields/columns + */ + $comments_map = array(); + + if ($GLOBALS['cfg']['ShowPropertyComments']) { + $comments_map = PMA_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 + */ +function PMA_getUrlParameters($db, $table) +{ + /** + * @todo check if we could replace by "db_|tbl_" - please clarify!? + */ + $url_params = array( + 'db' => $db, + 'sql_query' => $_REQUEST['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 + */ +function PMA_getHtmlForGisEditor() +{ + return '
      ' + . '' + . '
      '; +} + +/** + * Function to get html for the ignore option in insert mode + * + * @param int $row_id row id + * + * @return string + */ +function PMA_getHtmlForIgnoreOption($row_id) +{ + return '' + .'
      ' . "\n"; +} + +/** + * Function to get html for the function option + * + * @param bool $odd_row whether odd row or not + * @param array $column column + * @param string $column_name_appendix column name appendix + * + * @return String + */ +function PMA_getHtmlForFunctionOption($odd_row, $column, $column_name_appendix) +{ + $longDoubleTextArea = $GLOBALS['cfg']['LongtextDoubleTextarea']; + return '' + . ''; + +} + +/** + * Function to get html for the column type + * + * @param array $column column + * + * @return string + */ +function PMA_getHtmlForInsertEditColumnType($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 + */ +function PMA_getHtmlForInsertEditFormHeader($has_blob_field, $is_upload) +{ + $html_output ='fieldLen($current_result, $i); + // 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... + $unnullify_trigger = $chg_evt_handler + . "=\"return verificationsAfterFieldChange('" + . PMA_escapeJsString($column['Field_md5']) . "', '" + . PMA_escapeJsString($jsvkey) . "','".$column['pma_type'] . "')\""; + + // Use an MD5 as an array index to avoid having special characters + // in the name atttibute (see bug #1746964 ) + $column_name_appendix = $vkey . '[' . $column['Field_md5'] . ']'; + + if ($column['Type'] == 'datetime' + && ! isset($column['Default']) + && ! is_null($column['Default']) + && ($insert_mode || ! isset($current_row[$column['Field']])) + ) { + // INSERT case or + // UPDATE case with an NULL value + $current_row[$column['Field']] = date('Y-m-d H:i:s', time()); + } + + $html_output = PMA_getHtmlForFunctionOption( + $odd_row, $column, $column_name_appendix + ); + + if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) { + $html_output .= PMA_getHtmlForInsertEditColumnType($column); + } //End if + + // Get a list of GIS data types. + $gis_data_types = PMA_Util::getGISDatatypes(); + + // Prepares the field value + $real_null_value = false; + $special_chars_encoded = ''; + if (isset($current_row)) { + // (we are editing) + list( + $real_null_value, $special_chars_encoded, $special_chars, + $data, $backup_field + ) + = PMA_getSpecialCharsAndBackupFieldForExistingRow( + $current_row, $column, $extracted_columnspec, + $real_null_value, $gis_data_types, $column_name_appendix + ); + } else { + // (we are inserting) + // display default values + list($real_null_value, $data, $special_chars, $backup_field, + $special_chars_encoded + ) + = PMA_getSpecialCharsAndBackupFieldForInsertingMode( + $column, $real_null_value + ); + } + + $idindex = ($o_rows * $columns_cnt) + $i + 1; + $tabindex = $idindex; + + // Get a list of data types that are not yet supported. + $no_support_types = PMA_Util::unsupportedDatatypes(); + + // The function column + // ------------------- + if ($GLOBALS['cfg']['ShowFunctionFields']) { + $html_output .= PMA_getFunctionColumn( + $column, $is_upload, $column_name_appendix, + $unnullify_trigger, $no_support_types, $tabindex_for_function, + $tabindex, $idindex, $insert_mode + ); + } + + // The null column + // --------------- + $foreignData = PMA_getForeignData( + $foreigners, $column['Field'], false, '', '' + ); + $html_output .= PMA_getNullColumn( + $column, $column_name_appendix, $real_null_value, + $tabindex, $tabindex_for_null, $idindex, $vkey, $foreigners, + $foreignData + ); + + // The value column (depends on type) + // ---------------- + // See bug #1667887 for the reason why we don't use the maxlength + // HTML attribute + $html_output .= ' ' + . ''; + + 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 $column column + * @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 + * + * @return string + */ +function PMA_getHtmlForInsertEditRow($url_params, $table_columns, + $column, $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, $text_dir +) { + $html_output = PMA_getHeadAndFootOfInsertRowTable($url_params) + . ''; + + //store the default value for CharEditing + $default_char_editing = $GLOBALS['cfg']['CharEditing']; + + $odd_row = true; + for ($i = 0; $i < $columns_cnt; $i++) { + $html_output .= PMA_getHtmlForInsertEditFormColumn( + $table_columns, $i, $column, $comments_map, $timestamp_seen, + $current_result, $chg_evt_handler, $jsvkey, $vkey, $insert_mode, + $current_row, $odd_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 + ); + $odd_row = !$odd_row; + } // end for + $o_rows++; + $html_output .= ' ' + . '
      ' . __('Column') . ''. __('Null') . '' . __('Value') . '
      ' + . '' + . '
      ' + . $column['Field_title'] + . '' + . '' + . '' . $column['pma_type'] . '' + . '
      ' . "\n"; + // Will be used by js/tbl_change.js to set the default value + // for the "Continue insertion" feature + $html_output .= '' + . $special_chars . ''; + + $html_output .= PMA_getValueColumn( + $column, $backup_field, $column_name_appendix, $unnullify_trigger, + $tabindex, $tabindex_for_value, $idindex, $data, $special_chars, + $foreignData, $odd_row, 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 + ); + + $html_output .= '

      '; + + return $html_output; +} +?> diff --git a/sources/libraries/ip_allow_deny.lib.php b/sources/libraries/ip_allow_deny.lib.php new file mode 100644 index 0000000..86a88ed --- /dev/null +++ b/sources/libraries/ip_allow_deny.lib.php @@ -0,0 +1,334 @@ + -1 || strpos($ipToTest, ':') > -1) { + // assume IPv6 + $result = PMA_ipv6MaskTest($testRange, $ipToTest); + } else { + $result = PMA_ipv4MaskTest($testRange, $ipToTest); + } + + return $result; +} // end of the "PMA_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 + */ +function PMA_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 + PMA_Util::pow(2, (30 - $i)); + } // end if + } // end for + + if (($maskl & $rangel) == ($maskl & $ipl)) { + return true; + } else { + return false; + } + } else { + // 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 + } //end if/else + + return $result; +} // end of the "PMA_ipv4MaskTest()" function + + +/** + * IPv6 matcher + * CIDR section taken from http://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 + */ +function PMA_ipv6MaskTest($test_range, $ip_to_test) +{ + $result = true; + + // convert to lowercase for easier comparison + $test_range = strtolower($test_range); + $ip_to_test = strtolower($ip_to_test); + + $is_cidr = strpos($test_range, '/') > -1; + $is_range = 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 = $ip_hex === $range_hex; + } elseif ($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); + } + } elseif ($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); + + // Overwriting first address string to make sure notation is optimal + $first_ip = inet_ntop($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 = 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 -= 1; + } + + // check if the IP to test is within the range + $result = ($ip_hex >= $first_hex && $ip_hex <= $last_hex); + } + + return $result; +} // end of the "PMA_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 Matched a rule ? + * + * @access public + * + * @see PMA_getIp() + */ +function PMA_allowDeny($type) +{ + global $cfg; + + // Grabs true IP of the user and returns if it can't be found + $remote_ip = PMA_getIp(); + if (empty($remote_ip)) { + return false; + } + + // copy username + $username = $cfg['Server']['user']; + + // copy rule database + $rules = $cfg['Server']['AllowDeny']['rules']; + + // 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 (PMA_getenv('SERVER_ADDR')) { + $shortcuts['localnetA'] = PMA_getenv('SERVER_ADDR') . '/8'; + $shortcuts['localnetB'] = PMA_getenv('SERVER_ADDR') . '/16'; + $shortcuts['localnetC'] = PMA_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 + && ($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 (PMA_ipMaskTest($rule_data[2], $remote_ip)) { + return true; + } + } // end while + + return false; +} // end of the "PMA_AllowDeny()" function + +?> diff --git a/sources/libraries/js_escape.lib.php b/sources/libraries/js_escape.lib.php new file mode 100644 index 0000000..42d7fed --- /dev/null +++ b/sources/libraries/js_escape.lib.php @@ -0,0 +1,133 @@ + + * 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. + */ +function PMA_formatJsVal($value) +{ + if (is_bool($value)) { + if ($value) { + return 'true'; + } else { + return 'false'; + } + } elseif (is_int($value)) { + return (int)$value; + } else { + return '"' . PMA_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. + */ +function PMA_getJsValue($key, $value, $escape = true) +{ + $result = $key . ' = '; + if (!$escape) { + $result .= $value; + } elseif (is_array($value)) { + $result .= '['; + foreach ($value as $val) { + $result .= PMA_formatJsVal($val) . ","; + } + $result .= "];\n"; + } else { + $result .= PMA_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 + */ +function PMA_printJsValue($key, $value) +{ + echo PMA_getJsValue($key, $value); +} + +?> diff --git a/sources/libraries/kanji-encoding.lib.php b/sources/libraries/kanji-encoding.lib.php new file mode 100644 index 0000000..0495112 --- /dev/null +++ b/sources/libraries/kanji-encoding.lib.php @@ -0,0 +1,168 @@ + + * + * @package PhpMyAdmin + */ +if (! defined('PHPMYADMIN')) { + exit; +} + +/** + * Gets the php internal encoding codes and sets the available encoding + * codes list + * 2002/1/4 by Y.Kawada + * + * @global string $kanji_encoding_list the available encoding codes list + * + * @return boolean always true + */ +function PMA_Kanji_checkEncoding() +{ + global $kanji_encoding_list; + + $internal_enc = mb_internal_encoding(); + if ($internal_enc == 'EUC-JP') { + $kanji_encoding_list = 'ASCII,EUC-JP,SJIS,JIS'; + } else { + $kanji_encoding_list = 'ASCII,SJIS,EUC-JP,JIS'; + } + + return true; +} // end of the 'PMA_Kanji_checkEncoding' function + + +/** + * Reverses SJIS & EUC-JP position in the encoding codes list + * 2002/1/4 by Y.Kawada + * + * @global string $kanji_encoding_list the available encoding codes list + * + * @return boolean always true + */ +function PMA_Kanji_changeOrder() +{ + global $kanji_encoding_list; + + $parts = explode(',', $kanji_encoding_list); + if ($parts[1] == 'EUC-JP') { + $kanji_encoding_list = 'ASCII,SJIS,EUC-JP,JIS'; + } else { + $kanji_encoding_list = 'ASCII,EUC-JP,SJIS,JIS'; + } + + return true; +} // end of the 'PMA_Kanji_changeOrder' function + + +/** + * Kanji string encoding convert + * 2002/1/4 by Y.Kawada + * + * @param string $str the string to convert + * @param string $enc the destination encoding code + * @param string $kana set 'kana' convert to JIS-X208-kana + * + * @global string $kanji_encoding_list the available encoding codes list + * + * @return string the converted string + */ +function PMA_Kanji_strConv($str, $enc, $kana) +{ + global $kanji_encoding_list; + + if ($enc == '' && $kana == '') { + return $str; + } + $string_encoding = mb_detect_encoding($str, $kanji_encoding_list); + + 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; +} // end of the 'PMA_Kanji_strConv' function + + +/** + * Kanji file encoding convert + * 2002/1/4 by Y.Kawada + * + * @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 + */ +function PMA_Kanji_fileConv($file, $enc, $kana) +{ + if ($enc == '' && $kana == '') { + return $file; + } + + $tmpfname = tempnam('', $enc); + $fpd = fopen($tmpfname, 'wb'); + $fps = fopen($file, 'r'); + PMA_Kanji_changeOrder(); + while (!feof($fps)) { + $line = fgets($fps, 4096); + $dist = PMA_Kanji_strConv($line, $enc, $kana); + fputs($fpd, $dist); + } // end while + PMA_Kanji_changeOrder(); + fclose($fps); + fclose($fpd); + unlink($file); + + return $tmpfname; +} // end of the 'PMA_Kanji_fileConv' function + + +/** + * Defines radio form fields to switch between encoding modes + * 2002/1/4 by Y.Kawada + * + * @return string xhtml code for the radio controls + */ +function PMA_Kanji_encodingForm() +{ + return "\n" + . '
        ' . "\n" . '
      • ' + . '' + . '\n" + . '' + . '' . "\n" + . '' + . '' . "\n" + . '
      • ' . "\n" . '
      • ' + . '' + . "\n" + . '
        ' + . "\n" + . '
      • ' . "\n" . '
      ' + ; +} // end of the 'PMA_Kanji_encodingForm' function + + +PMA_Kanji_checkEncoding(); + +?> diff --git a/sources/libraries/language_stats.inc.php b/sources/libraries/language_stats.inc.php new file mode 100644 index 0000000..931819f --- /dev/null +++ b/sources/libraries/language_stats.inc.php @@ -0,0 +1,80 @@ + 12, + 'ar' => 45, + 'az' => 15, + 'be@latin' => 31, + 'be' => 31, + 'bg' => 70, + 'bn' => 90, + 'br' => 24, + 'bs' => 16, + 'ca' => 91, + 'ckb' => 19, + 'cs' => 100, + 'cy' => 23, + 'da' => 99, + 'de' => 94, + 'el' => 100, + 'en_GB' => 98, + 'es' => 100, + 'et' => 100, + 'eu' => 20, + 'fa' => 33, + 'fi' => 70, + 'fr' => 100, + 'gl' => 96, + 'he' => 22, + 'hi' => 52, + 'hr' => 38, + 'hu' => 98, + 'hy' => 3, + 'ia' => 37, + 'id' => 77, + 'it' => 89, + 'ja' => 88, + 'ka' => 19, + 'kk' => 12, + 'kn' => 0, + 'ko' => 70, + 'ky' => 2, + 'lt' => 61, + 'lv' => 22, + 'mk' => 21, + 'ml' => 1, + 'mn' => 26, + 'ms' => 13, + 'nb' => 66, + 'nl' => 100, + 'pa' => 0, + 'pl' => 92, + 'pt_BR' => 99, + 'pt' => 55, + 'ro' => 59, + 'ru' => 95, + 'si' => 78, + 'sk' => 80, + 'sl' => 100, + 'sq' => 28, + 'sr@latin' => 62, + 'sr' => 29, + 'sv' => 96, + 'ta' => 15, + 'te' => 12, + 'th' => 41, + 'tk' => 0, + 'tr' => 100, + 'tt' => 21, + 'ug' => 13, + 'uk' => 93, + 'ur' => 24, + 'uz@latin' => 44, + 'uz' => 45, + 'vls' => 0, + 'zh_CN' => 93, + 'zh_TW' => 100, +); +?> diff --git a/sources/libraries/logging.lib.php b/sources/libraries/logging.lib.php new file mode 100644 index 0000000..975fc5a --- /dev/null +++ b/sources/libraries/logging.lib.php @@ -0,0 +1,30 @@ + diff --git a/sources/libraries/mime.lib.php b/sources/libraries/mime.lib.php new file mode 100644 index 0000000..399b36d --- /dev/null +++ b/sources/libraries/mime.lib.php @@ -0,0 +1,34 @@ += 2 && $test[0] == chr(0xff) && $test[1] == chr(0xd8)) { + return 'image/jpeg'; + } + if ($len >= 3 && substr($test, 0, 3) == 'GIF') { + return 'image/gif'; + } + if ($len >= 4 && substr($test, 0, 4) == "\x89PNG") { + return 'image/png'; + } + return 'application/octet-stream'; +} +?> diff --git a/sources/libraries/mult_submits.inc.php b/sources/libraries/mult_submits.inc.php new file mode 100644 index 0000000..b52087f --- /dev/null +++ b/sources/libraries/mult_submits.inc.php @@ -0,0 +1,254 @@ +getVirtualTables($db); + +/** + * Displays the confirmation form if required + */ +if (!empty($submit_mult) && !empty($what)) { + unset($message); + + if (strlen($table)) { + include './libraries/tbl_common.inc.php'; + $url_query .= '&goto=tbl_sql.php&back=tbl_sql.php'; + include './libraries/tbl_info.inc.php'; + } elseif (strlen($db)) { + include './libraries/db_common.inc.php'; + include './libraries/db_info.inc.php'; + } else { + include_once './libraries/server_common.inc.php'; + } + + // Builds the query + list($full_query, $reload, $full_query_views) + = PMA_getQueryFromSelected( + $what, $db, $table, $selected, $action, $views + ); + + // Displays the confirmation form + $_url_params = PMA_getUrlParams( + $what, $reload, $action, $db, $table, $selected, $views, + isset($original_sql_query)? $original_sql_query : null, + isset($original_url_query)? $original_url_query : null + ); + + if ($what == 'replace_prefix_tbl' || $what == 'copy_tbl_change_prefix') { + echo PMA_getHtmlForReplacePrefixTable($what, $action, $_url_params); + } elseif ($what == 'add_prefix_tbl') { + echo PMA_getHtmlForAddPrefixTable($action, $_url_params); + } else { + echo PMA_getHtmlForOtherActions($what, $action, $_url_params, $full_query); + } + exit; + +} elseif (! empty($mult_btn) && $mult_btn == __('Yes')) { + /** + * Executes the query - dropping rows, columns/fields, tables or dbs + */ + if ($query_type == 'drop_db' + || $query_type == 'drop_tbl' + || $query_type == 'drop_fld' + ) { + include_once './libraries/relation_cleanup.lib.php'; + } + + if ($query_type == 'primary_fld') { + // Gets table primary key + $GLOBALS['dbi']->selectDb($db); + $result = $GLOBALS['dbi']->query( + 'SHOW KEYS FROM ' . PMA_Util::backquote($table) . ';' + ); + $primary = ''; + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + // Backups the list of primary keys + if ($row['Key_name'] == 'PRIMARY') { + $primary .= $row['Column_name'] . ', '; + } + } // end while + $GLOBALS['dbi']->freeResult($result); + } + + list( + $result, $rebuild_database_list, $reload_ret, + $run_parts, $use_sql, $sql_query, $sql_query_views + ) = PMA_getQueryStrFromSelected( + $query_type, $selected, $db, $table, $views, + isset($primary) ? $primary : null, + isset($from_prefix) ? $from_prefix : null, + isset($to_prefix) ? $to_prefix : null + ); + //update the existed variable + if (isset($reload_ret)) { + $reload = $reload_ret; + } + + if ($query_type == 'drop_tbl') { + $default_fk_check_value = $GLOBALS['dbi']->fetchValue( + 'SHOW VARIABLES LIKE \'foreign_key_checks\';', 0, 1 + ) == 'ON'; + if (!empty($sql_query)) { + $sql_query .= ';'; + } elseif (!empty($sql_query_views)) { + $sql_query = $sql_query_views . ';'; + unset($sql_query_views); + } + } + + if ($use_sql) { + /** + * Parse and analyze the query + */ + include_once 'libraries/parse_analyze.inc.php'; + + PMA_executeQueryAndSendQueryResponse( + $analyzed_sql_results, false, $db, $table, null, null, null, + false, null, null, null, null, $goto, $pmaThemeImage, null, null, + $query_type, $sql_query, $selected, null + ); + } elseif (!$run_parts) { + $GLOBALS['dbi']->selectDb($db); + // for disabling foreign key checks while dropping tables + if (! isset($_REQUEST['fk_check']) && $query_type == 'drop_tbl') { + $GLOBALS['dbi']->query('SET FOREIGN_KEY_CHECKS = 0;'); + } + $result = $GLOBALS['dbi']->tryQuery($sql_query); + if (! isset($_REQUEST['fk_check']) + && $query_type == 'drop_tbl' + && $default_fk_check_value + ) { + $GLOBALS['dbi']->query('SET FOREIGN_KEY_CHECKS = 1;'); + } + if ($result && !empty($sql_query_views)) { + $sql_query .= ' ' . $sql_query_views . ';'; + $result = $GLOBALS['dbi']->tryQuery($sql_query_views); + unset($sql_query_views); + } + + if (! $result) { + $message = PMA_Message::error($GLOBALS['dbi']->getError()); + } + } + if ($rebuild_database_list) { + // avoid a problem with the database list navigator + // when dropping a db from server_databases + $GLOBALS['pma']->databases->build(); + } +} else { + $message = PMA_Message::success(__('No change')); +} +?> diff --git a/sources/libraries/mult_submits.lib.php b/sources/libraries/mult_submits.lib.php new file mode 100644 index 0000000..1247513 --- /dev/null +++ b/sources/libraries/mult_submits.lib.php @@ -0,0 +1,627 @@ + $what, + 'reload' => (! empty($reload) ? 1 : 0), + ); + if (strpos(' ' . $action, 'db_') == 1) { + $_url_params['db']= $db; + } elseif (strpos(' ' . $action, 'tbl_') == 1 || $what == 'row_delete') { + $_url_params['db']= $db; + $_url_params['table']= $table; + } + foreach ($selected as $idx => $sval) { + if ($what == 'row_delete') { + $_url_params['selected'][] = 'DELETE FROM ' + . PMA_Util::backquote($db) . '.' . PMA_Util::backquote($table) + . ' WHERE ' . urldecode($sval) . ' LIMIT 1;'; + } else { + $_url_params['selected'][] = $sval; + } + } + if ($what == 'drop_tbl' && !empty($views)) { + foreach ($views as $current) { + $_url_params['views'][] = $current; + } + } + if ($what == 'row_delete') { + $_url_params['original_sql_query'] = $original_sql_query; + if (! empty($original_url_query)) { + $_url_params['original_url_query'] = $original_url_query; + } + } + + return $_url_params; +} + +/** + * Gets query results from + * + * @param string $query_type query type + * @param array $selected selected tables + * @param string $db db name + * @param string $table table name + * @param string $views table views + * @param string $primary table primary + * @param string $from_prefix from prefix original + * @param string $to_prefix to prefix original + * + * @return array + */ +function PMA_getQueryStrFromSelected( + $query_type, $selected, $db, $table, $views, $primary, + $from_prefix, $to_prefix +) { + $rebuild_database_list = false; + $reload = null; + $a_query = null; + $sql_query = ''; + $sql_query_views = null; + // whether to run query after each pass + $run_parts = false; + // whether to execute the query at the end (to display results) + $use_sql = false; + $result = null; + + if ($query_type == 'drop_tbl') { + $sql_query_views = ''; + } + + $selected_cnt = count($selected); + $deletes = false; + + for ($i = 0; $i < $selected_cnt; $i++) { + switch ($query_type) { + case 'row_delete': + $deletes = true; + $a_query = $selected[$i]; + $run_parts = true; + break; + + case 'drop_db': + PMA_relationsCleanupDatabase($selected[$i]); + $a_query = 'DROP DATABASE ' + . PMA_Util::backquote($selected[$i]); + $reload = 1; + $run_parts = true; + $rebuild_database_list = true; + break; + + case 'drop_tbl': + PMA_relationsCleanupTable($db, $selected[$i]); + $current = $selected[$i]; + if (!empty($views) && in_array($current, $views)) { + $sql_query_views .= (empty($sql_query_views) ? 'DROP VIEW ' : ', ') + . PMA_Util::backquote($current); + } else { + $sql_query .= (empty($sql_query) ? 'DROP TABLE ' : ', ') + . PMA_Util::backquote($current); + } + $reload = 1; + break; + + case 'check_tbl': + $sql_query .= (empty($sql_query) ? 'CHECK TABLE ' : ', ') + . PMA_Util::backquote($selected[$i]); + $use_sql = true; + break; + + case 'optimize_tbl': + $sql_query .= (empty($sql_query) ? 'OPTIMIZE TABLE ' : ', ') + . PMA_Util::backquote($selected[$i]); + $use_sql = true; + break; + + case 'analyze_tbl': + $sql_query .= (empty($sql_query) ? 'ANALYZE TABLE ' : ', ') + . PMA_Util::backquote($selected[$i]); + $use_sql = true; + break; + + case 'repair_tbl': + $sql_query .= (empty($sql_query) ? 'REPAIR TABLE ' : ', ') + . PMA_Util::backquote($selected[$i]); + $use_sql = true; + break; + + case 'empty_tbl': + $deletes = true; + $a_query = 'TRUNCATE '; + $a_query .= PMA_Util::backquote($selected[$i]); + $run_parts = true; + break; + + case 'drop_fld': + PMA_relationsCleanupColumn($db, $table, $selected[$i]); + $sql_query .= (empty($sql_query) + ? 'ALTER TABLE ' . PMA_Util::backquote($table) + : ',') + . ' DROP ' . PMA_Util::backquote($selected[$i]) + . (($i == $selected_cnt-1) ? ';' : ''); + break; + + case 'primary_fld': + $sql_query .= (empty($sql_query) + ? 'ALTER TABLE ' . PMA_Util::backquote($table) . (empty($primary) + ? '' + : ' DROP PRIMARY KEY,') . ' ADD PRIMARY KEY( ' + : ', ') + . PMA_Util::backquote($selected[$i]) + . (($i == $selected_cnt-1) ? ');' : ''); + break; + + case 'index_fld': + $sql_query .= (empty($sql_query) + ? 'ALTER TABLE ' . PMA_Util::backquote($table) . ' ADD INDEX( ' + : ', ') + . PMA_Util::backquote($selected[$i]) + . (($i == $selected_cnt-1) ? ');' : ''); + break; + + case 'unique_fld': + $sql_query .= (empty($sql_query) + ? 'ALTER TABLE ' . PMA_Util::backquote($table) . ' ADD UNIQUE( ' + : ', ') + . PMA_Util::backquote($selected[$i]) + . (($i == $selected_cnt-1) ? ');' : ''); + break; + + case 'spatial_fld': + $sql_query .= (empty($sql_query) + ? 'ALTER TABLE ' . PMA_Util::backquote($table) . ' ADD SPATIAL( ' + : ', ') + . PMA_Util::backquote($selected[$i]) + . (($i == $selected_cnt-1) ? ');' : ''); + break; + + case 'fulltext_fld': + $sql_query .= (empty($sql_query) + ? 'ALTER TABLE ' . PMA_Util::backquote($table) . ' ADD FULLTEXT( ' + : ', ') + . PMA_Util::backquote($selected[$i]) + . (($i == $selected_cnt-1) ? ');' : ''); + break; + + case 'add_prefix_tbl': + $newtablename = $_POST['add_prefix'] . $selected[$i]; + // ADD PREFIX TO TABLE NAME + $a_query = 'ALTER TABLE ' + . PMA_Util::backquote($selected[$i]) + . ' RENAME ' + . PMA_Util::backquote($newtablename); + $run_parts = true; + break; + + case 'replace_prefix_tbl': + $current = $selected[$i]; + if (substr($current, 0, strlen($from_prefix)) == $from_prefix) { + $newtablename = $to_prefix . substr($current, strlen($from_prefix)); + } else { + $newtablename = $current; + } + // CHANGE PREFIX PATTERN + $a_query = 'ALTER TABLE ' + . PMA_Util::backquote($selected[$i]) + . ' RENAME ' + . PMA_Util::backquote($newtablename); + $run_parts = true; + break; + + case 'copy_tbl_change_prefix': + $current = $selected[$i]; + if (substr($current, 0, strlen($from_prefix)) == $from_prefix) { + $newtablename = $to_prefix . substr($current, strlen($from_prefix)); + } else { + $newtablename = $current; + } + $newtablename = $to_prefix . substr($current, strlen($from_prefix)); + // COPY TABLE AND CHANGE PREFIX PATTERN + $a_query = 'CREATE TABLE ' + . PMA_Util::backquote($newtablename) + . ' SELECT * FROM ' + . PMA_Util::backquote($selected[$i]); + $run_parts = true; + break; + + } // end switch + + // All "DROP TABLE", "DROP FIELD", "OPTIMIZE TABLE" and "REPAIR TABLE" + // statements will be run at once below + if ($run_parts) { + $sql_query .= $a_query . ';' . "\n"; + if ($query_type != 'drop_db') { + $GLOBALS['dbi']->selectDb($db); + } + $result = $GLOBALS['dbi']->query($a_query); + + if ($query_type == 'drop_db') { + PMA_clearTransformations($selected[$i]); + } elseif ($query_type == 'drop_tbl') { + PMA_clearTransformations($db, $selected[$i]); + } else if ($query_type == 'drop_fld') { + PMA_clearTransformations($db, $table, $selected[$i]); + } + } // end if + } // end for + + if ($deletes) { + $_REQUEST['pos'] = PMA_calculatePosForLastPage($db, $table, $_REQUEST['pos']); + } + + return array( + $result, $rebuild_database_list, $reload, + $run_parts, $use_sql, $sql_query, $sql_query_views + ); +} + +/** + * Gets table primary key + * + * @param string $db name of db + * @param string $table name of table + * + * @return string + */ +function PMA_getKeyForTablePrimary($db, $table) +{ + $GLOBALS['dbi']->selectDb($db); + $result = $GLOBALS['dbi']->query( + 'SHOW KEYS FROM ' . PMA_Util::backquote($table) . ';' + ); + $primary = ''; + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + // Backups the list of primary keys + if ($row['Key_name'] == 'PRIMARY') { + $primary .= $row['Column_name'] . ', '; + } + } // end while + $GLOBALS['dbi']->freeResult($result); + + return $primary; +} + +/** + * Gets HTML for replace_prefix_tbl or copy_tbl_change_prefix + * + * @param string $what mult_submit type + * @param string $action action type + * @param array $_url_params URL params + * + * @return string + */ +function PMA_getHtmlForReplacePrefixTable($what, $action, $_url_params) +{ + $html = ''; + $html .= PMA_URL_getHiddenInputs($_url_params); + $html .= '
      '; + $html .= ''; + if ($what == 'replace_prefix_tbl') { + $html .= __('Replace table prefix:'); + } else { + $html .= __('Copy table with prefix:'); + } + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '
      ' . __('From') . ''; + $html .= ''; + $html .= '
      ' . __('To') . ''; + $html .= ''; + $html .= '
      '; + $html .= '
      '; + $html .= '
      '; + $html .= ''; + $html .= ''; + $html .= '
      '; + $html .= ''; + + return $html; +} + +/** + * Gets HTML for add_prefix_tbl + * + * @param string $action action type + * @param array $_url_params URL params + * + * @return string + */ +function PMA_getHtmlForAddPrefixTable($action, $_url_params) +{ + $html = '
      '; + $html .= PMA_URL_getHiddenInputs($_url_params); + $html .= '
      '; + $html .= '' . __('Add table prefix:') . ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '
      ' . __('Add prefix') . ''; + $html .= ''; + $html .= '
      '; + $html .= '
      '; + $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 $_url_params URL params + * @param array $full_query full sql query string + * + * @return string + */ +function PMA_getHtmlForOtherActions($what, $action, $_url_params, $full_query) +{ + $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 .= '' . $full_query . ''; + $html .= '
      '; + $html .= '
      '; + $html .= '
      '; + $html .= PMA_URL_getHiddenInputs($_url_params); + // Display option to disable foreign key checks while dropping tables + if ($what == 'drop_tbl') { + $html .= '
      '; + $html .= ''; + $html .= __('Foreign key check:'); + $html .= ''; + $html .= ''; + $html .= 'fetchValue( + 'SHOW VARIABLES LIKE \'foreign_key_checks\';', 0, 1 + ) == 'ON'; + if ($default_fk_check_value) { + $html .= ' checked="checked"'; + } + $html .= '/>'; + $html .= ''; + $html .= ($default_fk_check_value) ? __('(Enabled)') : __('(Disabled)'); + $html .= ''; + $html .= '
      '; + } + $html .= ''; + $html .= ''; + $html .= '
      '; + + $html .= '
      '; + $html .= PMA_URL_getHiddenInputs($_url_params); + $html .= ''; + $html .= ''; + $html .= '
      '; + $html .= '
      '; + + return $html; +} + +/** + * Get List of information for Submit Mult + * + * @param string $submit_mult mult_submit type + * @param string $db dtabase name + * @param array $table table name + * @param array $selected the selected columns + * @param array $action action type + * + * @return array() + */ +function PMA_getDataForSubmitMult($submit_mult, $db, $table, $selected, $action) +{ + $what = null; + $query_type = null; + $is_unset_submit_mult = false; + $mult_btn = null; + + switch ($submit_mult) { + case 'drop': + $what = 'drop_fld'; + break; + case 'primary': + // Gets table primary key + $primary = PMA_getKeyForTablePrimary($db, $table); + 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 'change': + PMA_displayHtmlForColumnChange($db, $table, $selected, $action); + // execution stops here but PMA_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); +} + +/** + * Get query string from Selected + * + * @param string $what mult_submit type + * @param string $db dtabase name + * @param array $table table name + * @param array $selected the selected columns + * @param array $action action type + * @param array $views table views + * + * @return array() + */ +function PMA_getQueryFromSelected($what, $db, $table, $selected, $action, $views) +{ + $reload = null; + $full_query_views = null; + $full_query = ''; + + if ($what == 'drop_tbl') { + $full_query_views = ''; + } + + $selected_cnt = count($selected); + $i = 0; + foreach ($selected as $idx => $sval) { + switch ($what) { + case 'row_delete': + $full_query .= 'DELETE FROM ' . PMA_Util::backquote($db) + . '.' . PMA_Util::backquote($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 ' . urldecode($sval) + . ';
      '; + break; + case 'drop_db': + $full_query .= 'DROP DATABASE ' + . PMA_Util::backquote(htmlspecialchars($sval)) + . ';
      '; + $reload = 1; + break; + + case 'drop_tbl': + $current = $sval; + if (!empty($views) && in_array($current, $views)) { + $full_query_views .= (empty($full_query_views) ? 'DROP VIEW ' : ', ') + . PMA_Util::backquote(htmlspecialchars($current)); + } else { + $full_query .= (empty($full_query) ? 'DROP TABLE ' : ', ') + . PMA_Util::backquote(htmlspecialchars($current)); + } + break; + + case 'empty_tbl': + $full_query .= 'TRUNCATE '; + $full_query .= PMA_Util::backquote(htmlspecialchars($sval)) + . ';
      '; + break; + + case 'primary_fld': + if ($full_query == '') { + $full_query .= 'ALTER TABLE ' + . PMA_Util::backquote(htmlspecialchars($table)) + . '
        DROP PRIMARY KEY,' + . '
         ADD PRIMARY KEY(' + . '
           ' + . PMA_Util::backquote(htmlspecialchars($sval)) + . ','; + } else { + $full_query .= '
           ' + . PMA_Util::backquote(htmlspecialchars($sval)) + . ','; + } + if ($i == $selected_cnt-1) { + $full_query = preg_replace('@,$@', ');
      ', $full_query); + } + break; + + case 'drop_fld': + if ($full_query == '') { + $full_query .= 'ALTER TABLE ' + . PMA_Util::backquote(htmlspecialchars($table)); + } + $full_query .= '
        DROP ' + . PMA_Util::backquote(htmlspecialchars($sval)) + . ','; + if ($i == $selected_cnt - 1) { + $full_query = preg_replace('@,$@', ';
      ', $full_query); + } + break; + } // end switch + $i++; + } + + if ($what == 'drop_tbl') { + if (!empty($full_query)) { + $full_query .= ';
      ' . "\n"; + } + if (!empty($full_query_views)) { + $full_query .= $full_query_views . ';
      ' . "\n"; + } + unset($full_query_views); + } + + $full_query_views = isset($full_query_views)? $full_query_views : null; + + return array($full_query, $reload, $full_query_views); +} + +?> diff --git a/sources/libraries/mysql_charsets.inc.php b/sources/libraries/mysql_charsets.inc.php new file mode 100644 index 0000000..ea3aa29 --- /dev/null +++ b/sources/libraries/mysql_charsets.inc.php @@ -0,0 +1,143 @@ +query($sql); + + $mysql_charsets = array(); + while ($row = $GLOBALS['dbi']->fetchAssoc($res)) { + $mysql_charsets[] = $row['CHARACTER_SET_NAME']; + // never used + //$mysql_charsets_maxlen[$row['Charset']] = $row['Maxlen']; + $mysql_charsets_descriptions[$row['CHARACTER_SET_NAME']] + = $row['DESCRIPTION']; + } + $GLOBALS['dbi']->freeResult($res); + + sort($mysql_charsets, SORT_STRING); + + $mysql_collations = array_flip($mysql_charsets); + $mysql_default_collations = $mysql_collations_flat + = $mysql_charsets_available = $mysql_collations_available = array(); + + $sql = PMA_DRIZZLE + ? 'SELECT * FROM data_dictionary.COLLATIONS' + : 'SELECT * FROM information_schema.COLLATIONS'; + $res = $GLOBALS['dbi']->query($sql); + while ($row = $GLOBALS['dbi']->fetchAssoc($res)) { + if (! is_array($mysql_collations[$row['CHARACTER_SET_NAME']])) { + $mysql_collations[$row['CHARACTER_SET_NAME']] + = array($row['COLLATION_NAME']); + } else { + $mysql_collations[$row['CHARACTER_SET_NAME']][] = $row['COLLATION_NAME']; + } + $mysql_collations_flat[] = $row['COLLATION_NAME']; + if ($row['IS_DEFAULT'] == 'Yes' || $row['IS_DEFAULT'] == '1') { + $mysql_default_collations[$row['CHARACTER_SET_NAME']] + = $row['COLLATION_NAME']; + } + //$mysql_collations_available[$row['Collation']] + // = ! isset($row['Compiled']) || $row['Compiled'] == 'Yes'; + $mysql_collations_available[$row['COLLATION_NAME']] = true; + $mysql_charsets_available[$row['CHARACTER_SET_NAME']] + = !empty($mysql_charsets_available[$row['CHARACTER_SET_NAME']]) + || !empty($mysql_collations_available[$row['COLLATION_NAME']]); + } + $GLOBALS['dbi']->freeResult($res); + unset($res, $row); + + if (PMA_DRIZZLE + && isset($mysql_collations['utf8_general_ci']) + && isset($mysql_collations['utf8']) + ) { + $mysql_collations['utf8'] = $mysql_collations['utf8_general_ci']; + $mysql_default_collations['utf8'] + = $mysql_default_collations['utf8_general_ci']; + $mysql_charsets_available['utf8'] + = $mysql_charsets_available['utf8_general_ci']; + unset( + $mysql_collations['utf8_general_ci'], + $mysql_default_collations['utf8_general_ci'], + $mysql_charsets_available['utf8_general_ci'] + ); + } + + sort($mysql_collations_flat, SORT_STRING); + foreach ($mysql_collations as $key => $value) { + sort($mysql_collations[$key], SORT_STRING); + reset($mysql_collations[$key]); + } + unset($key, $value); + + PMA_Util::cacheSet( + 'mysql_charsets', $GLOBALS['mysql_charsets'], true + ); + PMA_Util::cacheSet( + 'mysql_charsets_descriptions', $GLOBALS['mysql_charsets_descriptions'], true + ); + PMA_Util::cacheSet( + 'mysql_charsets_available', $GLOBALS['mysql_charsets_available'], true + ); + PMA_Util::cacheSet( + 'mysql_collations', $GLOBALS['mysql_collations'], true + ); + PMA_Util::cacheSet( + 'mysql_default_collations', $GLOBALS['mysql_default_collations'], true + ); + PMA_Util::cacheSet( + 'mysql_collations_flat', $GLOBALS['mysql_collations_flat'], true + ); + PMA_Util::cacheSet( + 'mysql_collations_available', $GLOBALS['mysql_collations_available'], true + ); +} else { + $GLOBALS['mysql_charsets'] = PMA_Util::cacheGet( + 'mysql_charsets', true + ); + $GLOBALS['mysql_charsets_descriptions'] = PMA_Util::cacheGet( + 'mysql_charsets_descriptions', true + ); + $GLOBALS['mysql_charsets_available'] = PMA_Util::cacheGet( + 'mysql_charsets_available', true + ); + $GLOBALS['mysql_collations'] = PMA_Util::cacheGet( + 'mysql_collations', true + ); + $GLOBALS['mysql_default_collations'] = PMA_Util::cacheGet( + 'mysql_default_collations', true + ); + $GLOBALS['mysql_collations_flat'] = PMA_Util::cacheGet( + 'mysql_collations_flat', true + ); + $GLOBALS['mysql_collations_available'] = PMA_Util::cacheGet( + 'mysql_collations_available', true + ); +} + +define('PMA_CSDROPDOWN_COLLATION', 0); +define('PMA_CSDROPDOWN_CHARSET', 1); + +/** + * shared functions for mysql charsets + */ +require_once './libraries/mysql_charsets.lib.php'; + +?> diff --git a/sources/libraries/mysql_charsets.lib.php b/sources/libraries/mysql_charsets.lib.php new file mode 100644 index 0000000..3a88ec0 --- /dev/null +++ b/sources/libraries/mysql_charsets.lib.php @@ -0,0 +1,354 @@ +' . "\n"; + if ($label) { + $return_str .= '' . "\n"; + } + $return_str .= '' . "\n"; + foreach ($mysql_charsets as $current_charset) { + if (!$mysql_charsets_available[$current_charset]) { + continue; + } + $current_cs_descr + = empty($mysql_charsets_descriptions[$current_charset]) + ? $current_charset + : $mysql_charsets_descriptions[$current_charset]; + + if ($type == PMA_CSDROPDOWN_COLLATION) { + $return_str .= '' . "\n"; + foreach ($mysql_collations[$current_charset] as $current_collation) { + if (!$mysql_collations_available[$current_collation]) { + continue; + } + $return_str .= '' . "\n"; + } + $return_str .= '' . "\n"; + } else { + $return_str .= '' . "\n"; + } + } + $return_str .= '' . "\n"; + + return $return_str; +} + +/** + * Generate the charset query part + * + * @param string $collation Collation + * + * @return string + */ +function PMA_generateCharsetQueryPart($collation) +{ + if (!PMA_DRIZZLE) { + list($charset) = explode('_', $collation); + return ' CHARACTER SET ' . $charset + . ($charset == $collation ? '' : ' COLLATE ' . $collation); + } else { + return ' COLLATE ' . $collation; + } +} + +/** + * returns collation of given db + * + * @param string $db name of db + * + * @return string collation of $db + */ +function PMA_getDbCollation($db) +{ + if ($GLOBALS['dbi']->isSystemSchema($db)) { + // We don't have to check the collation of the virtual + // information_schema database: We know it! + return 'utf8_general_ci'; + } + + $sql = PMA_DRIZZLE + ? 'SELECT DEFAULT_COLLATION_NAME FROM data_dictionary.SCHEMAS' + . ' WHERE SCHEMA_NAME = \'' . PMA_Util::sqlAddSlashes($db) + . '\' LIMIT 1' + : 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA' + . ' WHERE SCHEMA_NAME = \'' . PMA_Util::sqlAddSlashes($db) + . '\' LIMIT 1'; + return $GLOBALS['dbi']->fetchValue($sql); +} + +/** + * returns default server collation from show variables + * + * @return string $server_collation + */ +function PMA_getServerCollation() +{ + return $GLOBALS['dbi']->fetchValue( + 'SHOW VARIABLES LIKE \'collation_server\'', 0, 1 + ); +} + +/** + * returns description for given collation + * + * @param string $collation MySQL collation string + * + * @return string collation description + */ +function PMA_getCollationDescr($collation) +{ + if ($collation == 'binary') { + return __('Binary'); + } + $parts = explode('_', $collation); + if (count($parts) == 1) { + $parts[1] = 'general'; + } elseif ($parts[1] == 'ci' || $parts[1] == 'cs') { + $parts[2] = $parts[1]; + $parts[1] = 'general'; + } + $descr = ''; + switch ($parts[1]) { + case 'bulgarian': + $descr = __('Bulgarian'); + break; + case 'chinese': + if ($parts[0] == 'gb2312' || $parts[0] == 'gbk') { + $descr = __('Simplified Chinese'); + } elseif ($parts[0] == 'big5') { + $descr = __('Traditional Chinese'); + } + break; + case 'ci': + $descr = __('case-insensitive'); + break; + case 'cs': + $descr = __('case-sensitive'); + break; + case 'croatian': + $descr = __('Croatian'); + break; + case 'czech': + $descr = __('Czech'); + break; + case 'danish': + $descr = __('Danish'); + break; + case 'english': + $descr = __('English'); + break; + case 'esperanto': + $descr = __('Esperanto'); + break; + case 'estonian': + $descr = __('Estonian'); + break; + case 'german1': + $descr = __('German') . ' (' . __('dictionary') . ')'; + break; + case 'german2': + $descr = __('German') . ' (' . __('phone book') . ')'; + break; + case 'hungarian': + $descr = __('Hungarian'); + break; + case 'icelandic': + $descr = __('Icelandic'); + break; + case 'japanese': + $descr = __('Japanese'); + break; + case 'latvian': + $descr = __('Latvian'); + break; + case 'lithuanian': + $descr = __('Lithuanian'); + break; + case 'korean': + $descr = __('Korean'); + break; + case 'persian': + $descr = __('Persian'); + break; + case 'polish': + $descr = __('Polish'); + break; + case 'roman': + $descr = __('West European'); + break; + case 'romanian': + $descr = __('Romanian'); + break; + case 'slovak': + $descr = __('Slovak'); + break; + case 'slovenian': + $descr = __('Slovenian'); + break; + case 'spanish': + $descr = __('Spanish'); + break; + case 'spanish2': + $descr = __('Traditional Spanish'); + break; + case 'swedish': + $descr = __('Swedish'); + break; + case 'thai': + $descr = __('Thai'); + break; + case 'turkish': + $descr = __('Turkish'); + break; + case 'ukrainian': + $descr = __('Ukrainian'); + break; + case 'unicode': + $descr = __('Unicode') . ' (' . __('multilingual') . ')'; + break; + case 'bin': + $is_bin = true; + case 'general': + switch ($parts[0]) { + // Unicode charsets + case 'ucs2': + case 'utf8': + case 'utf8mb4': + $descr = __('Unicode') . ' (' . __('multilingual') . ')'; + break; + // West European charsets + case 'ascii': + case 'cp850': + case 'dec8': + case 'hp8': + case 'latin1': + case 'macroman': + $descr = __('West European') . ' (' . __('multilingual') . ')'; + break; + // Central European charsets + case 'cp1250': + case 'cp852': + case 'latin2': + case 'macce': + $descr = __('Central European') . ' (' . __('multilingual') . ')'; + break; + // Russian charsets + case 'cp866': + case 'koi8r': + $descr = __('Russian'); + break; + // Simplified Chinese charsets + case 'gb2312': + case 'gbk': + $descr = __('Simplified Chinese'); + break; + // Japanese charsets + case 'sjis': + case 'ujis': + case 'cp932': + case 'eucjpms': + $descr = __('Japanese'); + break; + // Baltic charsets + case 'cp1257': + case 'latin7': + $descr = __('Baltic') . ' (' . __('multilingual') . ')'; + break; + // Other + case 'armscii8': + case 'armscii': + $descr = __('Armenian'); + break; + case 'big5': + $descr = __('Traditional Chinese'); + break; + case 'cp1251': + $descr = __('Cyrillic') . ' (' . __('multilingual') . ')'; + break; + case 'cp1256': + $descr = __('Arabic'); + break; + case 'euckr': + $descr = __('Korean'); + break; + case 'hebrew': + $descr = __('Hebrew'); + break; + case 'geostd8': + $descr = __('Georgian'); + break; + case 'greek': + $descr = __('Greek'); + break; + case 'keybcs2': + $descr = __('Czech-Slovak'); + break; + case 'koi8u': + $descr = __('Ukrainian'); + break; + case 'latin5': + $descr = __('Turkish'); + break; + case 'swe7': + $descr = __('Swedish'); + break; + case 'tis620': + $descr = __('Thai'); + break; + default: + $descr = __('unknown'); + break; + } + if (!empty($is_bin)) { + $descr .= ', ' . __('Binary'); + } + break; + default: $descr = __('unknown'); + } + if (!empty($parts[2])) { + if ($parts[2] == 'ci') { + $descr .= ', ' . __('case-insensitive'); + } elseif ($parts[2] == 'cs') { + $descr .= ', ' . __('case-sensitive'); + } + } + + return $descr; +} +?> diff --git a/sources/libraries/navigation/Navigation.class.php b/sources/libraries/navigation/Navigation.class.php new file mode 100644 index 0000000..b7827f6 --- /dev/null +++ b/sources/libraries/navigation/Navigation.class.php @@ -0,0 +1,211 @@ +isAjax()) { + $header = new PMA_NavigationHeader(); + $retval = $header->getDisplay(); + } + $tree = new PMA_NavigationTree(); + if (! PMA_Response::getInstance()->isAjax() + || ! empty($_REQUEST['full']) + || ! empty($_REQUEST['reload']) + ) { + $treeRender = $tree->renderState(); + } else { + $treeRender = $tree->renderPath(); + } + + if (! $treeRender) { + $retval .= PMA_Message::error( + __('An error has occurred while loading the navigation tree') + )->getDisplay(); + } else { + $retval .= $treeRender; + } + + if (! PMA_Response::getInstance()->isAjax()) { + // closes the tags that were opened by the navigation header + $retval .= '
      '; + $retval .= '
      '; + $retval .= '
      '; + } + + 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 = PMA_Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . PMA_Util::backquote($GLOBALS['cfgRelation']['navigationhiding']); + $sqlQuery = "INSERT INTO " . $navTable + . "(`username`, `item_name`, `item_type`, `db_name`, `table_name`)" + . " VALUES (" + . "'" . PMA_Util::sqlAddSlashes($GLOBALS['cfg']['Server']['user']) . "'," + . "'" . PMA_Util::sqlAddSlashes($itemName) . "'," + . "'" . PMA_Util::sqlAddSlashes($itemType) . "'," + . "'" . PMA_Util::sqlAddSlashes($dbName) . "'," + . "'" . (! empty($tableName)? PMA_Util::sqlAddSlashes($tableName) : "" ) + . "')"; + PMA_queryAsControlUser($sqlQuery, false); + } + + /** + * 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 = PMA_Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . PMA_Util::backquote($GLOBALS['cfgRelation']['navigationhiding']); + $sqlQuery = "DELETE FROM " . $navTable + . " WHERE" + . " `username`='" + . PMA_Util::sqlAddSlashes($GLOBALS['cfg']['Server']['user']) . "'" + . " AND `item_name`='" . PMA_Util::sqlAddSlashes($itemName) . "'" + . " AND `item_type`='" . PMA_Util::sqlAddSlashes($itemType) . "'" + . " AND `db_name`='" . PMA_Util::sqlAddSlashes($dbName) . "'" + . (! empty($tableName) + ? " AND `table_name`='" . PMA_Util::sqlAddSlashes($tableName) . "'" + : "" + ); + PMA_queryAsControlUser($sqlQuery, false); + } + + /** + * Returns HTML for the dialog to show hidden nativation 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 nativation items + */ + public function getItemUnhideDialog($dbName, $itemType = null, $tableName = null) + { + $html = '
      '; + $html .= '
      '; + $html .= PMA_URL_getHiddenInputs($dbName, $tableName); + + $navTable = PMA_Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . PMA_Util::backquote($GLOBALS['cfgRelation']['navigationhiding']); + $sqlQuery = "SELECT `item_name`, `item_type` FROM " . $navTable + . " WHERE `username`='" + . PMA_Util::sqlAddSlashes($GLOBALS['cfg']['Server']['user']) . "'" + . " AND `db_name`='" . PMA_Util::sqlAddSlashes($dbName) . "'" + . " AND `table_name`='" + . (! empty($tableName) ? PMA_Util::sqlAddSlashes($tableName) : '') . "'"; + $result = PMA_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( + '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 .= ''; + $odd = true; + foreach ($hidden[$t] as $hiddenItem) { + $html .= ''; + $html .= ''; + $html .= ''; + $odd = ! $odd; + } + $html .= '
      ' . htmlspecialchars($hiddenItem) . '' + . PMA_Util::getIcon('lightbulb.png', __('Show')) + . '
      '; + $first = false; + } + } + } + + $html .= '
      '; + $html .= '
      '; + return $html; + } +} +?> diff --git a/sources/libraries/navigation/NavigationHeader.class.php b/sources/libraries/navigation/NavigationHeader.class.php new file mode 100644 index 0000000..ea6e2f2 --- /dev/null +++ b/sources/libraries/navigation/NavigationHeader.class.php @@ -0,0 +1,310 @@ + true + ) + ); + $class = ' class="list_container'; + 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 .= $this->_recent(); + $buffer .= PMA_Util::getImage( + 'ajax_clock_small.gif', + __('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() + { + $retval = ''; + // display Logo, depending on $GLOBALS['cfg']['NavigationDisplayLogo'] + if ($GLOBALS['cfg']['NavigationDisplayLogo']) { + $logo = 'phpMyAdmin'; + if (@file_exists($GLOBALS['pmaThemeImage'] . 'logo_left.png')) { + $logo = ''; + } elseif (@file_exists($GLOBALS['pmaThemeImage'] . 'pma_logo2.png')) { + $logo = ''; + } + $retval .= '"; + } + } + } + if ($node->hasSiblings() || isset($_REQUEST['results'])) { + $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_min = (int)$GLOBALS['cfg']['NavigationTreeDisplayDbFilterMinimum']; + if ($node === $this->_tree + && $this->_tree->getPresence() >= $filter_min + ) { + $url_params = array( + 'pos' => 0 + ); + $retval .= '
        '; + $retval .= '
      • '; + $retval .= '
        '; + $retval .= PMA_getHiddenFields($url_params); + $retval .= ' 9 + ) { + $placeholder_key = 'placeholder'; + } + $retval .= " $placeholder_key='" + . __('Filter databases by name or regex'); + $retval .= "' />"; + $retval .= 'X'; + $retval .= "
        "; + $retval .= "
      • "; + $retval .= "
      "; + } else 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_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 .= PMA_getHiddenFields($url_params); + $retval .= " 9 + ) { + $placeholder_key = 'placeholder'; + } + $retval .= " $placeholder_key='" + . __('Filter by name or regex') . "' />"; + $retval .= "X"; + $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 .= PMA_Util::getListNavigator( + $this->_tree->getPresence('databases', $this->_searchClause), + $this->_pos, + array('server' => $GLOBALS['server']), + 'navigation.php', + 'frame_navigation', + $GLOBALS['cfg']['MaxNavigationItems'], + '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 .= PMA_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; + } else if ($b->isNew) { + return 1; + } + if ($GLOBALS['cfg']['NaturalOrder']) { + return strnatcasecmp($a->name, $b->name); + } else { + return strcasecmp($a->name, $b->name); + } + } +} +?> diff --git a/sources/libraries/navigation/NodeFactory.class.php b/sources/libraries/navigation/NodeFactory.class.php new file mode 100644 index 0000000..f561fb8 --- /dev/null +++ b/sources/libraries/navigation/NodeFactory.class.php @@ -0,0 +1,97 @@ + diff --git a/sources/libraries/navigation/Nodes/Node.class.php b/sources/libraries/navigation/Nodes/Node.class.php new file mode 100644 index 0000000..dad71bf --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node.class.php @@ -0,0 +1,457 @@ +name = $name; + $this->real_name = $name; + } + if ($type === Node::CONTAINER) { + $this->type = Node::CONTAINER; + } + $this->is_group = (bool)$is_group; + } + + /** + * 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; + } + } + } + + /** + * Retreives 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 != true || $groups) + ) { + $parents[] = $this; + $self = false; + } + $parent = $this->parent; + while (isset($parent)) { + if ( ($parent->type != Node::CONTAINER || $containers) + && ($parent->is_group != true || $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 + */ + public function realParent() + { + $retval = $this->parents(); + if (count($retval) > 0) { + return $retval[0]; + } else { + return false; + } + } + + /** + * 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 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; + } else { + 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 Node_Database and Node_Table 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 = '') + { + if ($type == 'databases' + && ! empty($GLOBALS['cfg']['Server']['only_db']) + ) { + $db_list = $GLOBALS['cfg']['Server']['only_db']; + $query = "SELECT * FROM ( SELECT '"; + + if (is_string($db_list)) { + $db_list = array($db_list); + } + + if (count($db_list)) { + $query .= implode("' UNION ALL SELECT '", $db_list); + $query .= "' "; + } + return $GLOBALS['dbi']->fetchResult($query . ") as alias"); + } else { + $query = "SELECT `SCHEMA_NAME` "; + $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA` "; + $query .= $this->_getWhereClause($searchClause); + $query .= "ORDER BY `SCHEMA_NAME` ASC "; + $query .= "LIMIT $pos, {$GLOBALS['cfg']['MaxNavigationItems']}"; + return $GLOBALS['dbi']->fetchResult($query); + } + } + + /** + * Returns the number of children of type $type present inside this container + * This method is overridden by the Node_Database and Node_Table 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 = '') + { + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA` "; + $query .= $this->_getWhereClause($searchClause); + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + return $retval; + } + + /** + * Returns the WHERE clause depending on the $searchClause parameter + * and the hide_db directive + * + * @param string $searchClause A string used to filter the results of the query + * + * @return string + */ + private function _getWhereClause($searchClause = '') + { + $whereClause = "WHERE TRUE "; + if (! empty($searchClause)) { + $whereClause .= "AND `SCHEMA_NAME` LIKE '%"; + $whereClause .= PMA_Util::sqlAddSlashes( + $searchClause, true + ); + $whereClause .= "%' "; + } + + if (! empty($GLOBALS['cfg']['Server']['hide_db'])) { + $whereClause .= "AND `SCHEMA_NAME` NOT REGEXP '" + . $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[] = " `SCHEMA_NAME` LIKE '" + . $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 ''; + } +} +?> diff --git a/sources/libraries/navigation/Nodes/Node_Column.class.php b/sources/libraries/navigation/Nodes/Node_Column.class.php new file mode 100644 index 0000000..2458aee --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Column.class.php @@ -0,0 +1,46 @@ +icon = PMA_Util::getImage('pause.png', __('Column')); + $this->links = array( + 'text' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&field=%1$s' + . '&change_column=1' + . '&token=' . $GLOBALS['token'], + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&field=%1$s' + . '&change_column=1' + . '&token=' . $GLOBALS['token'] + ); + } +} + +?> diff --git a/sources/libraries/navigation/Nodes/Node_Column_Container.class.php b/sources/libraries/navigation/Nodes/Node_Column_Container.class.php new file mode 100644 index 0000000..3bb1462 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Column_Container.class.php @@ -0,0 +1,57 @@ +icon = PMA_Util::getImage('pause.png', __('Columns')); + $this->links = array( + 'text' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s' + . '&token=' . $GLOBALS['token'], + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s' + . '&token=' . $GLOBALS['token'], + ); + $this->real_name = 'columns'; + + $new_label = _pgettext('Create new column', 'New'); + $new = PMA_NodeFactory::getInstance('Node', $new_label); + $new->isNew = true; + $new->icon = PMA_Util::getImage('b_column_add.png', $new_label); + $new->links = array( + 'text' => 'tbl_addfield.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s' + . '&field_where=last&after_field=' + . '&token=' . $GLOBALS['token'], + 'icon' => 'tbl_addfield.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s' + . '&field_where=last&after_field=' + . '&token=' . $GLOBALS['token'], + ); + $new->classes = 'new_column italics'; + $this->addChild($new); + } +} + +?> diff --git a/sources/libraries/navigation/Nodes/Node_Database.class.php b/sources/libraries/navigation/Nodes/Node_Database.class.php new file mode 100644 index 0000000..7a445ab --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Database.class.php @@ -0,0 +1,327 @@ +icon = PMA_Util::getImage( + 's_db.png', + __('Database operations') + ); + $this->links = array( + 'text' => $GLOBALS['cfg']['DefaultTabDatabase'] + . '?server=' . $GLOBALS['server'] + . '&db=%1$s&token=' . $GLOBALS['token'], + 'icon' => 'db_operations.php?server=' . $GLOBALS['server'] + . '&db=%1$s&token=' . $GLOBALS['token'] + ); + $this->classes = 'database'; + } + + /** + * Returns the number of children of type $type present inside this container + * This method is overridden by the Node_Database and Node_Table 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 = '') + { + $retval = 0; + $db = $this->real_name; + switch ($type) { + case 'tables': + $db = PMA_Util::sqlAddSlashes($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` "; + $query .= "WHERE `TABLE_SCHEMA`='$db' "; + if (PMA_DRIZZLE) { + $query .= "AND `TABLE_TYPE`='BASE' "; + } else { + $query .= "AND `TABLE_TYPE`='BASE TABLE' "; + } + if (! empty($searchClause)) { + $query .= "AND `TABLE_NAME` LIKE '%"; + $query .= PMA_Util::sqlAddSlashes( + $searchClause, true + ); + $query .= "%'"; + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + break; + case 'views': + $db = PMA_Util::sqlAddSlashes($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` "; + $query .= "WHERE `TABLE_SCHEMA`='$db' "; + if (PMA_DRIZZLE) { + $query .= "AND `TABLE_TYPE`!='BASE' "; + } else { + $query .= "AND `TABLE_TYPE`!='BASE TABLE' "; + } + if (! empty($searchClause)) { + $query .= "AND `TABLE_NAME` LIKE '%"; + $query .= PMA_Util::sqlAddSlashes( + $searchClause, true + ); + $query .= "%'"; + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + break; + case 'procedures': + $db = PMA_Util::sqlAddSlashes($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA`='$db'"; + $query .= "AND `ROUTINE_TYPE`='PROCEDURE' "; + if (! empty($searchClause)) { + $query .= "AND `ROUTINE_NAME` LIKE '%"; + $query .= PMA_Util::sqlAddSlashes( + $searchClause, true + ); + $query .= "%'"; + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + break; + case 'functions': + $db = PMA_Util::sqlAddSlashes($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA`='$db' "; + $query .= "AND `ROUTINE_TYPE`='FUNCTION' "; + if (! empty($searchClause)) { + $query .= "AND `ROUTINE_NAME` LIKE '%"; + $query .= PMA_Util::sqlAddSlashes( + $searchClause, true + ); + $query .= "%'"; + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + break; + case 'events': + $db = PMA_Util::sqlAddSlashes($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`EVENTS` "; + $query .= "WHERE `EVENT_SCHEMA`='$db' "; + if (! empty($searchClause)) { + $query .= "AND `EVENT_NAME` LIKE '%"; + $query .= PMA_Util::sqlAddSlashes( + $searchClause, true + ); + $query .= "%'"; + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + break; + default: + break; + } + return $retval; + } + + /** + * Returns the names of children of type $type present inside this container + * This method is overridden by the Node_Database and Node_Table 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->real_name; + switch ($type) { + case 'tables': + $escdDb = PMA_Util::sqlAddSlashes($db); + $query = "SELECT `TABLE_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` "; + $query .= "WHERE `TABLE_SCHEMA`='$escdDb' "; + if (PMA_DRIZZLE) { + $query .= "AND `TABLE_TYPE`='BASE' "; + } else { + $query .= "AND `TABLE_TYPE`='BASE TABLE' "; + } + if (! empty($searchClause)) { + $query .= "AND `TABLE_NAME` LIKE '%"; + $query .= PMA_Util::sqlAddSlashes( + $searchClause, true + ); + $query .= "%'"; + } + $query .= "ORDER BY `TABLE_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + break; + case 'views': + $escdDb = PMA_Util::sqlAddSlashes($db); + $query = "SELECT `TABLE_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` "; + $query .= "WHERE `TABLE_SCHEMA`='$escdDb' "; + if (PMA_DRIZZLE) { + $query .= "AND `TABLE_TYPE`!='BASE' "; + } else { + $query .= "AND `TABLE_TYPE`!='BASE TABLE' "; + } + if (! empty($searchClause)) { + $query .= "AND `TABLE_NAME` LIKE '%"; + $query .= PMA_Util::sqlAddSlashes( + $searchClause, true + ); + $query .= "%'"; + } + $query .= "ORDER BY `TABLE_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + break; + case 'procedures': + $escdDb = PMA_Util::sqlAddSlashes($db); + $query = "SELECT `ROUTINE_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA`='$escdDb'"; + $query .= "AND `ROUTINE_TYPE`='PROCEDURE' "; + if (! empty($searchClause)) { + $query .= "AND `ROUTINE_NAME` LIKE '%"; + $query .= PMA_Util::sqlAddSlashes( + $searchClause, true + ); + $query .= "%'"; + } + $query .= "ORDER BY `ROUTINE_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + break; + case 'functions': + $escdDb = PMA_Util::sqlAddSlashes($db); + $query = "SELECT `ROUTINE_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA`='$escdDb' "; + $query .= "AND `ROUTINE_TYPE`='FUNCTION' "; + if (! empty($searchClause)) { + $query .= "AND `ROUTINE_NAME` LIKE '%"; + $query .= PMA_Util::sqlAddSlashes( + $searchClause, true + ); + $query .= "%'"; + } + $query .= "ORDER BY `ROUTINE_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + break; + case 'events': + $escdDb = PMA_Util::sqlAddSlashes($db); + $query = "SELECT `EVENT_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`EVENTS` "; + $query .= "WHERE `EVENT_SCHEMA`='$escdDb' "; + if (! empty($searchClause)) { + $query .= "AND `EVENT_NAME` LIKE '%"; + $query .= PMA_Util::sqlAddSlashes( + $searchClause, true + ); + $query .= "%'"; + } + $query .= "ORDER BY `EVENT_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + break; + default: + break; + } + + // Remove hidden items so that they are not displayed in navigation tree + $cfgRelation = PMA_getRelationsParam(); + if ($cfgRelation['navwork']) { + $navTable = PMA_Util::backquote($cfgRelation['db']) + . "." . PMA_Util::backquote($cfgRelation['navigationhiding']); + $sqlQuery = "SELECT `item_name` FROM " . $navTable + . " WHERE `username`='" . $cfgRelation['user'] . "'" + . " AND `item_type`='" . substr($type, 0, -1) . "'" + . " AND `db_name`='" . PMA_Util::sqlAddSlashes($db) . "'"; + $result = PMA_queryAsControlUser($sqlQuery, false); + if ($result) { + $hiddenItems = array(); + while ($row = $GLOBALS['dbi']->fetchArray($result)) { + $hiddenItems[] = $row[0]; + } + foreach ($retval as $key => $item) { + if (in_array($item, $hiddenItems)) { + unset($retval[$key]); + } + } + } + $GLOBALS['dbi']->freeResult($result); + } + + return $retval; + } + + /** + * Returns HTML for show hidden button displayed infront of database node + * + * @return String HTML for show hidden button + */ + public function getHtmlForControlButtons() + { + $ret = ''; + $db = $this->real_name; + + $cfgRelation = PMA_getRelationsParam(); + if ($cfgRelation['navwork']) { + $navTable = PMA_Util::backquote($cfgRelation['db']) + . "." . PMA_Util::backquote($cfgRelation['navigationhiding']); + $sqlQuery = "SELECT COUNT(*) FROM " . $navTable + . " WHERE `username`='" + . PMA_Util::sqlAddSlashes($GLOBALS['cfg']['Server']['user']) ."'" + . " AND `db_name`='" . PMA_Util::sqlAddSlashes($db) . "'"; + $count = $GLOBALS['dbi']->fetchValue( + $sqlQuery, 0, 0, $GLOBALS['controllink'] + ); + if ($count > 0) { + $ret = '' + . '' + . PMA_Util::getImage( + 'lightbulb.png', __('Show hidden items') + ) + . ''; + } + } + return $ret; + } +} + +?> diff --git a/sources/libraries/navigation/Nodes/Node_DatabaseChild.class.php b/sources/libraries/navigation/Nodes/Node_DatabaseChild.class.php new file mode 100644 index 0000000..36e8a37 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_DatabaseChild.class.php @@ -0,0 +1,52 @@ +realParent()->real_name; + $item = $this->real_name; + $ret = '' + . '' + . PMA_Util::getImage('lightbulb_off', __('Hide')) + . ''; + } + return $ret; + } + + /** + * Returns the type of the item reprsented by the node. + * + * @return string type of the item + */ + protected abstract function getItemType(); +} +?> diff --git a/sources/libraries/navigation/Nodes/Node_Database_Container.class.php b/sources/libraries/navigation/Nodes/Node_Database_Container.class.php new file mode 100644 index 0000000..ed1366d --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Database_Container.class.php @@ -0,0 +1,45 @@ +isNew = true; + $new->icon = PMA_Util::getImage('b_newdb.png', ''); + $new->links = array( + 'text' => 'server_databases.php?server=' . $GLOBALS['server'] + . '&token=' . $GLOBALS['token'], + 'icon' => 'server_databases.php?server=' . $GLOBALS['server'] + . '&token=' . $GLOBALS['token'], + ); + $new->classes = 'new_database italics'; + $this->addChild($new); + } +} +?> \ No newline at end of file diff --git a/sources/libraries/navigation/Nodes/Node_Event.class.php b/sources/libraries/navigation/Nodes/Node_Event.class.php new file mode 100644 index 0000000..7df1f88 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Event.class.php @@ -0,0 +1,57 @@ +icon = PMA_Util::getImage('b_events.png'); + $this->links = array( + 'text' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&edit_item=1' + . '&token=' . $GLOBALS['token'], + 'icon' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&export_item=1' + . '&token=' . $GLOBALS['token'] + ); + $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/sources/libraries/navigation/Nodes/Node_Event_Container.class.php b/sources/libraries/navigation/Nodes/Node_Event_Container.class.php new file mode 100644 index 0000000..cc66db8 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Event_Container.class.php @@ -0,0 +1,54 @@ +icon = PMA_Util::getImage('b_events.png', ''); + $this->links = array( + 'text' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%1$s&token=' . $GLOBALS['token'], + 'icon' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%1$s&token=' . $GLOBALS['token'], + ); + $this->real_name = 'events'; + + $new = PMA_NodeFactory::getInstance( + 'Node', _pgettext('Create new event', 'New') + ); + $new->isNew = true; + $new->icon = PMA_Util::getImage('b_event_add.png', ''); + $new->links = array( + 'text' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&token=' . $GLOBALS['token'] + . '&add_item=1', + 'icon' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&token=' . $GLOBALS['token'] + . '&add_item=1', + ); + $new->classes = 'new_event italics'; + $this->addChild($new); + } +} + +?> diff --git a/sources/libraries/navigation/Nodes/Node_Function.class.php b/sources/libraries/navigation/Nodes/Node_Function.class.php new file mode 100644 index 0000000..c068e49 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Function.class.php @@ -0,0 +1,57 @@ +icon = PMA_Util::getImage('b_routines.png', __('Function')); + $this->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=FUNCTION' + . '&edit_item=1&token=' . $GLOBALS['token'], + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=FUNCTION' + . '&export_item=1&token=' . $GLOBALS['token'] + ); + $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/sources/libraries/navigation/Nodes/Node_Function_Container.class.php b/sources/libraries/navigation/Nodes/Node_Function_Container.class.php new file mode 100644 index 0000000..6a571ff --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Function_Container.class.php @@ -0,0 +1,53 @@ +icon = PMA_Util::getImage('b_routines.png', __('Functions')); + $this->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&token=' . $GLOBALS['token'] + . '&type=FUNCTION', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&token=' . $GLOBALS['token'] + . '&type=FUNCTION', + ); + $this->real_name = 'functions'; + + $new_label = _pgettext('Create new function', 'New'); + $new = PMA_NodeFactory::getInstance('Node', $new_label); + $new->isNew = true; + $new->icon = PMA_Util::getImage('b_routine_add.png', $new_label); + $new->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&token=' . $GLOBALS['token'] + . '&add_item=1&item_type=FUNCTION', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&token=' . $GLOBALS['token'] + . '&add_item=1&item_type=FUNCTION', + ); + $new->classes = 'new_function italics'; + $this->addChild($new); + } +} + +?> diff --git a/sources/libraries/navigation/Nodes/Node_Index.class.php b/sources/libraries/navigation/Nodes/Node_Index.class.php new file mode 100644 index 0000000..15ddcb4 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Index.class.php @@ -0,0 +1,45 @@ +icon = PMA_Util::getImage('b_index.png', __('Index')); + $this->links = array( + 'text' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&index=%1$s' + . '&token=' . $GLOBALS['token'], + 'icon' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&index=%1$s' + . '&token=' . $GLOBALS['token'] + ); + $this->classes = 'index'; + } +} + +?> diff --git a/sources/libraries/navigation/Nodes/Node_Index_Container.class.php b/sources/libraries/navigation/Nodes/Node_Index_Container.class.php new file mode 100644 index 0000000..8903515 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Index_Container.class.php @@ -0,0 +1,55 @@ +icon = PMA_Util::getImage('b_index.png', __('Indexes')); + $this->links = array( + 'text' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s' + . '&token=' . $GLOBALS['token'], + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s' + . '&token=' . $GLOBALS['token'], + ); + $this->real_name = 'indexes'; + + $new_label = _pgettext('Create new index', 'New'); + $new = PMA_NodeFactory::getInstance('Node', $new_label); + $new->isNew = true; + $new->icon = PMA_Util::getImage('b_index_add.png', $new_label); + $new->links = array( + 'text' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&create_index=1&added_fields=2' + . '&db=%3$s&table=%2$s&token=' . $GLOBALS['token'], + 'icon' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&create_index=1&added_fields=2' + . '&db=%3$s&table=%2$s&token=' . $GLOBALS['token'], + ); + $new->classes = 'new_index italics'; + $this->addChild($new); + } +} + +?> diff --git a/sources/libraries/navigation/Nodes/Node_Procedure.class.php b/sources/libraries/navigation/Nodes/Node_Procedure.class.php new file mode 100644 index 0000000..686e2d7 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Procedure.class.php @@ -0,0 +1,57 @@ +icon = PMA_Util::getImage('b_routines.png', __('Procedure')); + $this->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=PROCEDURE' + . '&edit_item=1&token=' . $GLOBALS['token'], + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=PROCEDURE' + . '&export_item=1&token=' . $GLOBALS['token'] + ); + $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/sources/libraries/navigation/Nodes/Node_Procedure_Container.class.php b/sources/libraries/navigation/Nodes/Node_Procedure_Container.class.php new file mode 100644 index 0000000..0e4fe08 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Procedure_Container.class.php @@ -0,0 +1,55 @@ +icon = PMA_Util::getImage('b_routines.png', __('Procedures')); + $this->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&token=' . $GLOBALS['token'] + . '&type=PROCEDURE', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&token=' . $GLOBALS['token'] + . '&type=PROCEDURE', + ); + $this->real_name = 'procedures'; + + $new_label = _pgettext('Create new procedure', 'New'); + $new = PMA_NodeFactory::getInstance('Node', $new_label); + $new->isNew = true; + $new->icon = PMA_Util::getImage('b_routine_add.png', $new_label); + $new->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&token=' . $GLOBALS['token'] + . '&add_item=1', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&token=' . $GLOBALS['token'] + . '&add_item=1', + ); + $new->classes = 'new_procedure italics'; + $this->addChild($new); + } +} + +?> diff --git a/sources/libraries/navigation/Nodes/Node_Table.class.php b/sources/libraries/navigation/Nodes/Node_Table.class.php new file mode 100644 index 0000000..8a398a5 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Table.class.php @@ -0,0 +1,186 @@ +icon = PMA_Util::getImage('b_props.png', __('Structure')); + break; + case 'tbl_select.php': + $this->icon = PMA_Util::getImage('b_search.png', __('Search')); + break; + case 'tbl_change.php': + $this->icon = PMA_Util::getImage('b_insrow.png', __('Insert')); + break; + case 'tbl_sql.php': + $this->icon = PMA_Util::getImage('b_sql.png', __('SQL')); + break; + case 'sql.php': + $this->icon = PMA_Util::getImage('b_browse.png', __('Browse')); + break; + } + $this->links = array( + 'text' => $GLOBALS['cfg']['DefaultTabTable'] + . '?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s' + . '&pos=0&token=' . $GLOBALS['token'], + 'icon' => $GLOBALS['cfg']['NavigationTreeDefaultTabTable'] + . '?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s&token=' . $GLOBALS['token'] + ); + $this->classes = 'table'; + } + + /** + * Returns the number of children of type $type present inside this container + * This method is overridden by the Node_Database and Node_Table 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': + $db = PMA_Util::sqlAddSlashes($db); + $table = PMA_Util::sqlAddSlashes($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); + break; + case 'indexes': + $db = PMA_Util::backquote($db); + $table = PMA_Util::backquote($table); + $query = "SHOW INDEXES FROM $table FROM $db"; + $retval = (int)$GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + break; + case 'triggers': + $db = PMA_Util::sqlAddSlashes($db); + $table = PMA_Util::sqlAddSlashes($table); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`TRIGGERS` "; + $query .= "WHERE `EVENT_OBJECT_SCHEMA`='$db' "; + $query .= "AND `EVENT_OBJECT_TABLE`='$table'"; + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + break; + default: + break; + } + return $retval; + } + + /** + * Returns the names of children of type $type present inside this container + * This method is overridden by the Node_Database and Node_Table 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': + $db = PMA_Util::sqlAddSlashes($db); + $table = PMA_Util::sqlAddSlashes($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; + case 'indexes': + $db = PMA_Util::backquote($db); + $table = PMA_Util::backquote($table); + $query = "SHOW INDEXES FROM $table FROM $db"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + $count = 0; + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if (! in_array($arr['Key_name'], $retval)) { + if ($pos <= 0 && $count < $maxItems) { + $retval[] = $arr['Key_name']; + $count++; + } + $pos--; + } + } + } + break; + case 'triggers': + $db = PMA_Util::sqlAddSlashes($db); + $table = PMA_Util::sqlAddSlashes($table); + $query = "SELECT `TRIGGER_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`TRIGGERS` "; + $query .= "WHERE `EVENT_OBJECT_SCHEMA`='$db' "; + $query .= "AND `EVENT_OBJECT_TABLE`='$table' "; + $query .= "ORDER BY `TRIGGER_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + 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'; + } +} + +?> diff --git a/sources/libraries/navigation/Nodes/Node_Table_Container.class.php b/sources/libraries/navigation/Nodes/Node_Table_Container.class.php new file mode 100644 index 0000000..7a4ece3 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Table_Container.class.php @@ -0,0 +1,60 @@ +icon = PMA_Util::getImage('b_browse.png', __('Tables')); + $this->links = array( + 'text' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=table' + . '&token=' . $GLOBALS['token'], + 'icon' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=table' + . '&token=' . $GLOBALS['token'], + ); + if ($GLOBALS['cfg']['NavigationTreeEnableGrouping']) { + $this->separator = $GLOBALS['cfg']['NavigationTreeTableSeparator']; + $this->separator_depth = (int)( + $GLOBALS['cfg']['NavigationTreeTableLevel'] + ); + } + $this->real_name = 'tables'; + $this->classes = 'tableContainer'; + + $new_label = _pgettext('Create new table', 'New'); + $new = PMA_NodeFactory::getInstance('Node', $new_label); + $new->isNew = true; + $new->icon = PMA_Util::getImage('b_table_add.png', $new_label); + $new->links = array( + 'text' => 'tbl_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s&token=' . $GLOBALS['token'], + 'icon' => 'tbl_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s&token=' . $GLOBALS['token'], + ); + $new->classes = 'new_table italics'; + $this->addChild($new); + } +} + +?> diff --git a/sources/libraries/navigation/Nodes/Node_Trigger.class.php b/sources/libraries/navigation/Nodes/Node_Trigger.class.php new file mode 100644 index 0000000..bc51106 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Trigger.class.php @@ -0,0 +1,45 @@ +icon = PMA_Util::getImage('b_triggers.png'); + $this->links = array( + 'text' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&item_name=%1$s&edit_item=1' + . '&token=' . $GLOBALS['token'], + 'icon' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&item_name=%1$s&export_item=1' + . '&token=' . $GLOBALS['token'] + ); + $this->classes = 'trigger'; + } +} + +?> diff --git a/sources/libraries/navigation/Nodes/Node_Trigger_Container.class.php b/sources/libraries/navigation/Nodes/Node_Trigger_Container.class.php new file mode 100644 index 0000000..1d47d31 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_Trigger_Container.class.php @@ -0,0 +1,55 @@ +icon = PMA_Util::getImage('b_triggers.png'); + $this->links = array( + 'text' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s&token=' . $GLOBALS['token'], + 'icon' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s&token=' . $GLOBALS['token'] + ); + $this->real_name = 'triggers'; + + $new = PMA_NodeFactory::getInstance( + 'Node', _pgettext('Create new trigger', 'New') + ); + $new->isNew = true; + $new->icon = PMA_Util::getImage('b_trigger_add.png', ''); + $new->links = array( + 'text' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&token=' . $GLOBALS['token'] + . '&add_item=1', + 'icon' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&token=' . $GLOBALS['token'] + . '&add_item=1', + ); + $new->classes = 'new_trigger italics'; + $this->addChild($new); + } + +} + +?> diff --git a/sources/libraries/navigation/Nodes/Node_View.class.php b/sources/libraries/navigation/Nodes/Node_View.class.php new file mode 100644 index 0000000..cdb4f33 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_View.class.php @@ -0,0 +1,57 @@ +icon = PMA_Util::getImage('b_props.png', __('View')); + $this->links = array( + 'text' => 'sql.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s&pos=0' + . '&token=' . $GLOBALS['token'], + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s' + . '&token=' . $GLOBALS['token'] + ); + $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/sources/libraries/navigation/Nodes/Node_View_Container.class.php b/sources/libraries/navigation/Nodes/Node_View_Container.class.php new file mode 100644 index 0000000..6eb26d4 --- /dev/null +++ b/sources/libraries/navigation/Nodes/Node_View_Container.class.php @@ -0,0 +1,60 @@ +icon = PMA_Util::getImage('b_views.png', __('Views')); + $this->links = array( + 'text' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=view' + . '&token=' . $GLOBALS['token'], + 'icon' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=view' + . '&token=' . $GLOBALS['token'], + ); + if ($GLOBALS['cfg']['NavigationTreeEnableGrouping']) { + $this->separator = $GLOBALS['cfg']['NavigationTreeTableSeparator']; + $this->separator_depth = (int)( + $GLOBALS['cfg']['NavigationTreeTableLevel'] + ); + } + $this->classes = 'viewContainer'; + $this->real_name = 'views'; + + $new_label = _pgettext('Create new view', 'New'); + $new = PMA_NodeFactory::getInstance('Node', $new_label); + $new->isNew = true; + $new->icon = PMA_Util::getImage('b_view_add.png', $new_label); + $new->links = array( + 'text' => 'view_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s&token=' . $GLOBALS['token'], + 'icon' => 'view_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s&token=' . $GLOBALS['token'], + ); + $new->classes = 'new_view italics'; + $this->addChild($new); + } +} + +?> diff --git a/sources/libraries/opendocument.lib.php b/sources/libraries/opendocument.lib.php new file mode 100644 index 0000000..08ff2b1 --- /dev/null +++ b/sources/libraries/opendocument.lib.php @@ -0,0 +1,171 @@ + addFile($mime, 'mimetype'); + $zipfile -> addFile($data, 'content.xml'); + $zipfile -> addFile( + '' + . '' + . '' + . 'phpMyAdmin ' . PMA_VERSION . '' + . 'phpMyAdmin ' . PMA_VERSION + . '' + . '' . strftime('%Y-%m-%dT%H:%M:%S') + . '' + . '' + . '', + 'meta.xml' + ); + $zipfile -> addFile( + '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '', + 'styles.xml' + ); + $zipfile -> addFile( + '' + . '' + . '' + . '' + . '' + . '' + . '', + 'META-INF/manifest.xml' + ); + return $zipfile -> file(); +} +?> diff --git a/sources/libraries/operations.lib.php b/sources/libraries/operations.lib.php new file mode 100644 index 0000000..c570b61 --- /dev/null +++ b/sources/libraries/operations.lib.php @@ -0,0 +1,1619 @@ +' + . '
      ' + . PMA_URL_getHiddenInputs($db) + . '
      ' + . ''; + if (PMA_Util::showIcons('ActionLinksMode')) { + $html_output .= ''; + } + $html_output .= __('Database comment: '); + $html_output .= ''; + $html_output .= '' + . '
      '; + $html_output .= '
      ' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; +} + +/** + * Get HTML output for rename database + * + * @param string $db database name + * + * @return string $html_output + */ +function PMA_getHtmlForRenameDatabase($db) +{ + $html_output = '
      ' + . '
      '; + if (isset($_REQUEST['db_collation'])) { + $html_output .= '' . "\n"; + } + $html_output .= '' + . '' + . PMA_URL_getHiddenInputs($db) + . '
      ' + . ''; + + if (PMA_Util::showIcons('ActionLinksMode')) { + $html_output .= PMA_Util::getImage('b_edit.png'); + } + $html_output .= __('Rename database to:') + . ''; + + $html_output .= '' + . '
      ' + . '
      ' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; +} + +/** + * Get HTML for database drop link + * + * @param string $db database name + * + * @return string $html_output + */ +function PMA_getHtmlForDropDatabaseLink($db) +{ + $this_sql_query = 'DROP DATABASE ' . PMA_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(PMA_Util::backquote($db)) + ), + 'db' => null, + ); + + $html_output = '
      ' + . '
      '; + $html_output .= ''; + if (PMA_Util::showIcons('ActionLinksMode')) { + $html_output .= PMA_Util::getImage('b_deltbl.png'); + } + $html_output .= __('Remove database') + . ''; + $html_output .= '
        '; + $html_output .= PMA_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 + * + * @return string $html_output + */ +function PMA_getHtmlForCopyDatabase($db) +{ + $drop_clause = 'DROP TABLE / DROP VIEW'; + $choices = array( + 'structure' => __('Structure only'), + 'data' => __('Structure and data'), + 'dataonly' => __('Data only') + ); + + if (isset($_COOKIE) + && isset($_COOKIE['pma_switch_to_new']) + && $_COOKIE['pma_switch_to_new'] == 'true' + ) { + $pma_switch_to_new = 'true'; + } + + $html_output = '
      '; + $html_output .= '
      '; + + if (isset($_REQUEST['db_collation'])) { + $html_output .= '' . "\n"; + } + $html_output .= '' . "\n" + . PMA_URL_getHiddenInputs($db); + $html_output .= '
      ' + . ''; + + if (PMA_Util::showIcons('ActionLinksMode')) { + $html_output .= PMA_Util::getImage('b_edit.png'); + } + $html_output .= __('Copy database to:') + . '' + . '
      ' + . PMA_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 .= '' + . '
      '; + $html_output .= '
      ' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; +} + +/** + * Get HTML snippet for change database charset + * + * @param string $db database name + * @param string $table tabel name + * + * @return string $html_output + */ +function PMA_getHtmlForChangeDatabaseCharset($db, $table) +{ + $html_output = '
      ' + . '
      '; + if (PMA_Util::showIcons('ActionLinksMode')) { + $html_output .= PMA_Util::getImage('s_asci.png'); + } + $html_output .= '' . "\n" + . '' . "\n" + . PMA_generateCharsetDropdownBox( + PMA_CSDROPDOWN_COLLATION, + 'db_collation', + 'select_db_collation', + isset($_REQUEST['db_collation']) ? $_REQUEST['db_collation'] : '', + false, + 3 + ) + . '' + . '
      ' + . '' . "\n" + . '
      ' . "\n" + . '
      ' . "\n"; + + return $html_output; +} + +/** + * Get HTML snippet for export relational schema view + * + * @param string $url_query Query string for link + * + * @return string $html_output + */ +function PMA_getHtmlForExportRelationalSchemaView($url_query) +{ + $html_output = ''; + + 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 + */ +function PMA_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 + ); + // collect for later display + $GLOBALS['sql_query'] .= "\n" . $tmp_query; + $GLOBALS['dbi']->selectDb($_REQUEST['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 + ); + // collect for later display + $GLOBALS['sql_query'] .= "\n" . $tmp_query; + $GLOBALS['dbi']->selectDb($_REQUEST['newname']); + $GLOBALS['dbi']->query($tmp_query); + } + } +} + +/** + * Get sql query and create database before copy + * + * @return string $sql_query + */ +function PMA_getSqlQueryAndCreateDbBeforeCopy() +{ + // lower_case_table_names=1 `DB` becomes `db` + if (! PMA_DRIZZLE) { + $lower_case_table_names = $GLOBALS['dbi']->fetchValue( + 'SHOW VARIABLES LIKE "lower_case_table_names"', 0, 1 + ); + if ($lower_case_table_names === '1') { + $_REQUEST['newname'] = $GLOBALS['PMA_String']->strtolower( + $_REQUEST['newname'] + ); + } + } + + $local_query = 'CREATE DATABASE IF NOT EXISTS ' + . PMA_Util::backquote($_REQUEST['newname']); + if (isset($_REQUEST['db_collation'])) { + $local_query .= ' DEFAULT' + . PMA_generateCharsetQueryPart($_REQUEST['db_collation']); + } + $local_query .= ';'; + $sql_query = $local_query; + // save the original db name because Tracker.class.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 PMA_Table::moveCopy + // checks in this list if the target db exists + $GLOBALS['pma']->databases->build(); + + return $sql_query; +} + +/** + * remove all foreign key constraints and return + * sql constraints query for full database + * + * @param array $tables_full array of all tables in given db or dbs + * @param object $export_sql_plugin export plugin instance + * @param boolean $move whether database name is empty or not + * @param string $db database name + * + * @return string sql constraints query for full databases + */ +function PMA_getSqlConstraintsQueryForFullDb( + $tables_full, $export_sql_plugin, $move, $db +) { + global $sql_constraints, $sql_drop_foreign_keys; + $sql_constraints_query_full_db = array(); + foreach ($tables_full as $each_table => $tmp) { + /* Following globals are set in getTableDef */ + $sql_constraints = ''; + $sql_drop_foreign_keys = ''; + $export_sql_plugin->getTableDef( + $db, $each_table, "\n", '', false, false + ); + if ($move && ! empty($sql_drop_foreign_keys)) { + $GLOBALS['dbi']->query($sql_drop_foreign_keys); + } + // keep the constraint we just dropped + if (! empty($sql_constraints)) { + $sql_constraints_query_full_db[] = $sql_constraints; + } + } + return $sql_constraints_query_full_db; +} + +/** + * 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 object $export_sql_plugin export plugin instance + * @param string $db database name + * + * @return array $views + */ +function PMA_getViewsAndCreateSqlViewStandIn( + $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 (PMA_Table::isView($db, $each_table)) { + $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($_REQUEST['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 string $sql_query sql query for all operations + * @param boolean $move whether database name is empty or not + * @param string $db database name + * + * @return array ($sql_query, $error) + */ +function PMA_getSqlQueryForCopyTable($tables_full, $sql_query, $move, $db) +{ + $error = false; + foreach ($tables_full as $each_table => $tmp) { + // skip the views; we have creted stand-in definitions + if (PMA_Table::isView($db, $each_table)) { + continue; + } + $back = $sql_query; + $sql_query = ''; + + // value of $what for this table only + $this_what = $_REQUEST['what']; + + // do not copy the data from a Merge table + // note: on the calling FORM, 'data' means 'structure and data' + if (PMA_Table::isMerge($db, $each_table)) { + 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 (! PMA_Table::moveCopy( + $db, $each_table, $_REQUEST['newname'], $each_table, + (isset($this_what) ? $this_what : 'data'), + $move, 'db_copy' + )) { + $error = true; + // $sql_query is filled by PMA_Table::moveCopy() + $sql_query = $back . $sql_query; + break; + } + // apply the triggers to the destination db+table + if ($triggers) { + $GLOBALS['dbi']->selectDb($_REQUEST['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($_REQUEST['add_constraints']) + && ! empty($GLOBALS['sql_constraints_query']) + ) { + $GLOBALS['sql_constraints_query_full_db'][] + = $GLOBALS['sql_constraints_query']; + unset($GLOBALS['sql_constraints_query']); + } + } + // $sql_query is filled by PMA_Table::moveCopy() + $sql_query = $back . $sql_query; + } + return array($sql_query, $error); +} + +/** + * 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 + */ +function PMA_runEventDefinitionsForDb($db) +{ + $event_names = $GLOBALS['dbi']->fetchResult( + 'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE EVENT_SCHEMA= \'' + . PMA_Util::sqlAddSlashes($db, true) . '\';' + ); + 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($_REQUEST['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 boolean $_error whether table rename/copy or not + */ +function PMA_handleTheViews($views, $move, $db) +{ + $_error = false; + // temporarily force to add DROP IF EXIST to CREATE VIEW query, + // to remove stand-in VIEW that was created earlier + // ( $_REQUEST['drop_if_exists'] is used in moveCopy() ) + if (isset($_REQUEST['drop_if_exists'])) { + $temp_drop_if_exists = $_REQUEST['drop_if_exists']; + } + $_REQUEST['drop_if_exists'] = 'true'; + + foreach ($views as $view) { + $copying_succeeded = PMA_Table::moveCopy( + $db, $view, $_REQUEST['newname'], $view, 'structure', $move, 'db_copy' + ); + if (! $copying_succeeded) { + $_error = true; + break; + } + } + unset($_REQUEST['drop_if_exists']); + if (isset($temp_drop_if_exists)) { + // restore previous value + $_REQUEST['drop_if_exists'] = $temp_drop_if_exists; + } + return $_error; +} + +/** + * Create all accumulated constraaints + * + * @return void + */ +function PMA_createAllAccumulatedConstraints() +{ + $GLOBALS['dbi']->selectDb($_REQUEST['newname']); + foreach ($GLOBALS['sql_constraints_query_full_db'] as $one_query) { + $GLOBALS['dbi']->query($one_query); + // and prepare to display them + $GLOBALS['sql_query'] .= "\n" . $one_query; + } + unset($GLOBALS['sql_constraints_query_full_db']); +} + +/** + * 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 + */ +function PMA_duplicateBookmarks($_error, $db) +{ + if (! $_error && $db != $_REQUEST['newname']) { + $get_fields = array('user', 'label', 'query'); + $where_fields = array('dbase' => $db); + $new_fields = array('dbase' => $_REQUEST['newname']); + PMA_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 + */ +function PMA_getHtmlForOrderTheTable($columns) +{ + $html_output = '
      '; + $html_output .= '
      '; + $html_output .= PMA_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 + */ +function PMA_getHtmlForMoveTable() +{ + $html_output = '
      '; + $html_output .= '
      ' + . PMA_URL_getHiddenInputs($GLOBALS['db'], $GLOBALS['table']); + + $html_output .= '' + . '' + . '
      '; + + $html_output .= '' . __('Move table to (database.table):') + . ''; + + if (count($GLOBALS['pma']->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 .= '' + . '
      ' + . '
      '; + + $html_output .= '
      ' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; +} + +/** + * Get the HTML div for Table option + * + * @param string $comment Comment + * @param array $tbl_collation table collation + * @param string $tbl_storage_engine table storage engine + * @param boolean $is_myisam_or_aria whether MYISAM | ARIA or not + * @param boolean $is_isam whether ISAM or not + * @param array $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 boolean $is_innodb whether INNODB or not + * @param boolean $is_pbxt whether PBXT or not + * @param boolean $is_aria whether ARIA or not + * @param string $checksum the checksum + * + * @return string $html_output + */ +function PMA_getTableOptionDiv($comment, $tbl_collation, $tbl_storage_engine, + $is_myisam_or_aria, $is_isam, $pack_keys, $auto_increment, $delay_key_write, + $transactional, $page_checksum, $is_innodb, $is_pbxt, $is_aria, $checksum +) { + $html_output = '
      '; + $html_output .= '
      ' + . '' + . '' + . '
      ' + . '
      '; + + return $html_output; +} + +/** + * Get HTML fieldset for Table option, it contains HTML table for options + * + * @param string $comment Comment + * @param array $tbl_collation table collation + * @param string $tbl_storage_engine table storage engine + * @param boolean $is_myisam_or_aria whether MYISAM | ARIA or not + * @param boolean $is_isam whether ISAM or not + * @param array $pack_keys pack keys + * @param string $delay_key_write delay key write + * @param string $auto_increment value of auto increment + * @param string $transactional value of transactional + * @param string $page_checksum value of page checksum + * @param boolean $is_innodb whether INNODB or not + * @param boolean $is_pbxt whether PBXT or not + * @param boolean $is_aria whether ARIA or not + * @param string $checksum the checksum + * + * @return string $html_output + */ +function PMA_getTableOptionFieldset($comment, $tbl_collation, + $tbl_storage_engine, $is_myisam_or_aria, $is_isam, $pack_keys, + $delay_key_write, $auto_increment, $transactional, + $page_checksum, $is_innodb, $is_pbxt, $is_aria, $checksum +) { + $html_output = '
      ' + . '' . __('Table options') . ''; + + $html_output .= ''; + //Change table name + $html_output .= '' + . '' + . ''; + + //Table comments + $html_output .= '' + . '' + . ''; + + //Storage engine + $html_output .= '' + . '' + . ''; + + //Table character set + $html_output .= '' + . '' + . ''; + + if ($is_myisam_or_aria || $is_isam) { + $html_output .= '' + . '' + . '' + . ''; + } // end if (MYISAM|ISAM) + + if ($is_myisam_or_aria) { + $html_output .= PMA_getHtmlForTableRow( + 'new_checksum', + 'CHECKSUM', + $checksum + ); + + $html_output .= PMA_getHtmlForTableRow( + 'new_delay_key_write', + 'DELAY_KEY_WRITE', + $delay_key_write + ); + } // end if (MYISAM) + + if ($is_aria) { + $html_output .= PMA_getHtmlForTableRow( + 'new_transactional', + 'TRANSACTIONAL', + $transactional + ); + + $html_output .= PMA_getHtmlForTableRow( + 'new_page_checksum', + 'PAGE_CHECKSUM', + $page_checksum + ); + } // end if (ARIA) + + if (strlen($auto_increment) > 0 + && ($is_myisam_or_aria || $is_innodb || $is_pbxt) + ) { + $html_output .= '' + . '' + . ' '; + } // end if (MYISAM|INNODB) + + $possible_row_formats = PMA_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 = strtoupper($GLOBALS['showtable']['Row_format']); + $html_output .= '' + . ''; + } + $html_output .= '
      ' . __('Rename table to') . '' + . '' + . '
      ' . __('Table comments') . '' + . '' + . '
      ' . __('Storage Engine') + . PMA_Util::showMySQLDocu('Storage_engines') + . '' + . PMA_StorageEngine::getHtmlSelect( + 'new_tbl_storage_engine', null, $tbl_storage_engine + ) + . '
      ' . __('Collation') . '' + . PMA_generateCharsetDropdownBox( + PMA_CSDROPDOWN_COLLATION, + 'tbl_collation', null, $tbl_collation, false, 3 + ) + . '
      ' + . '
      ' + . ''; + $html_output .= PMA_Util::getDropdown( + 'new_row_format', $possible_row_formats[$tbl_storage_engine], + $current_row_format, 'new_row_format' + ); + $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 + */ +function PMA_getHtmlForTableRow($attribute, $label, $val) +{ + return '' + . '' + . '' + . ''; +} + +/** + * Get array of possible row formats + * + * @return array $possible_row_formats + */ +function PMA_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( + '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' + ) + ); + + $innodb_engine_plugin = PMA_StorageEngine::getEngine('innodb'); + $innodb_plugin_version = $innodb_engine_plugin->getInnodbPluginVersion(); + if (!empty($innodb_plugin_version)) { + $innodb_file_format = $innodb_engine_plugin->getInnodbFileFormat(); + } else { + $innodb_file_format = ''; + } + if ('Barracuda' == $innodb_file_format + && $innodb_engine_plugin->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 + */ +function PMA_getHtmlForCopytable() +{ + $html_output = '
      '; + $html_output .= '
      ' + . PMA_URL_getHiddenInputs($GLOBALS['db'], $GLOBALS['table']) + . ''; + + $html_output .= '
      '; + $html_output .= '' + . __('Copy table to (database.table):') . ''; + + if (count($GLOBALS['pma']->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 .= PMA_Util::getRadioFields( + 'what', $choices, 'data', true + ); + + $html_output .= '' + . '
      ' + . '' + . '
      '; + + // display "Add constraints" choice only if there are + // foreign keys + if (PMA_getForeigners($GLOBALS['db'], $GLOBALS['table'], '', 'foreign')) { + $html_output .= ''; + $html_output .= '
      '; + } // endif + + if (isset($_COOKIE['pma_switch_to_new']) + && $_COOKIE['pma_switch_to_new'] == 'true' + ) { + $pma_switch_to_new = 'true'; + } + + $html_output .= ''); + $html_output .= '' + . '
      '; + + $html_output .= '
      ' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; +} + +/** + * Get HTML snippet for table maintence + * + * @param boolean $is_myisam_or_aria whether MYISAM | ARIA or not + * @param boolean $is_innodb whether innodb or not + * @param boolean $is_berkeleydb whether berkeleydb or not + * @param array $url_params array of URL parameters + * + * @return string $html_output + */ +function PMA_getHtmlForTableMaintenance( + $is_myisam_or_aria, $is_innodb, $is_berkeleydb, $url_params +) { + $html_output = '
      '; + $html_output .= '
      ' + . '' . __('Table maintenance') . ''; + $html_output .= '
        '; + + // Note: BERKELEY (BDB) is no longer supported, starting with MySQL 5.1 + $html_output .= PMA_getListofMaintainActionLink( + $is_myisam_or_aria, $is_innodb, $url_params, $is_berkeleydb + ); + + $html_output .= '
      ' + . '
      ' + . '
      '; + + return $html_output; +} + +/** + * Get HTML 'li' having a link of maintain action + * + * @param boolean $is_myisam_or_aria whether MYISAM | ARIA or not + * @param boolean $is_innodb whether innodb or not + * @param array $url_params array of URL parameters + * @param boolean $is_berkeleydb whether berkeleydb or not + * + * @return string $html_output + */ +function PMA_getListofMaintainActionLink($is_myisam_or_aria, + $is_innodb, $url_params, $is_berkeleydb +) { + $html_output = ''; + + if ($is_myisam_or_aria || $is_innodb || $is_berkeleydb) { + if ($is_myisam_or_aria || $is_innodb) { + $params = array( + 'sql_query' => 'CHECK TABLE ' + . PMA_Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= PMA_getMaintainActionlink( + __('Check table'), + $params, + $url_params, + 'CHECK_TABLE' + ); + } + if ($is_innodb) { + $params = array( + 'sql_query' => 'ALTER TABLE ' + . PMA_Util::backquote($GLOBALS['table']) + . ' ENGINE = InnoDB;' + ); + $html_output .= PMA_getMaintainActionlink( + __('Defragment table'), + $params, + $url_params, + 'InnoDB_File_Defragmenting', + 'Table_types' + ); + } + if ($is_innodb || $is_myisam_or_aria || $is_berkeleydb) { + $params = array( + 'sql_query' => 'ANALYZE TABLE ' + . PMA_Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= PMA_getMaintainActionlink( + __('Analyze table'), + $params, + $url_params, + 'ANALYZE_TABLE' + ); + } + if ($is_myisam_or_aria && !PMA_DRIZZLE) { + $params = array( + 'sql_query' => 'REPAIR TABLE ' + . PMA_Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= PMA_getMaintainActionlink( + __('Repair table'), + $params, + $url_params, + 'REPAIR_TABLE' + ); + } + if (($is_myisam_or_aria || $is_innodb || $is_berkeleydb) + && !PMA_DRIZZLE + ) { + $params = array( + 'sql_query' => 'OPTIMIZE TABLE ' + . PMA_Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= PMA_getMaintainActionlink( + __('Optimize table'), + $params, + $url_params, + 'OPTIMIZE_TABLE' + ); + } + } // end MYISAM or BERKELEYDB case + + $params = array( + 'sql_query' => 'FLUSH TABLE ' + . PMA_Util::backquote($GLOBALS['table']), + 'message_to_show' => sprintf( + __('Table %s has been flushed'), + htmlspecialchars($GLOBALS['table']) + ), + 'reload' => 1, + ); + + $html_output .= PMA_getMaintainActionlink( + __('Flush the table (FLUSH)'), + $params, + $url_params, + 'FLUSH' + ); + + return $html_output; +} + +/** + * Get maintain action HTML link + * + * @param string $action action name + * @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 + */ +function PMA_getMaintainActionlink($action, $params, $url_params, $link) +{ + return '
    • ' + . '' + . $action + . '' + . PMA_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 $drop_table_url_params url parameter array for drop table + * + * @return string $html_output + */ +function PMA_getHtmlForDeleteDataOrTable( + $truncate_table_url_params, + $drop_table_url_params +) { + $html_output = '
      ' + . '
      ' + . '' . __('Delete data or table') . ''; + + $html_output .= '
        '; + + if (! empty($truncate_table_url_params)) { + $html_output .= PMA_getDeleteDataOrTablelink( + $truncate_table_url_params, + 'TRUNCATE_TABLE', + __('Empty the table (TRUNCATE)'), + 'truncate_tbl_anchor' + ); + } + if (!empty ($drop_table_url_params)) { + $html_output .= PMA_getDeleteDataOrTablelink( + $drop_table_url_params, + '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 $id id of the link + * + * @return String html output + */ +function PMA_getDeleteDataOrTablelink($url_params, $syntax, $link, $id) +{ + return '
    • ' + . $link . '' + . PMA_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 + */ +function PMA_getHtmlForPartitionMaintenance($partition_names, $url_params) +{ + $choices = array( + 'ANALYZE' => __('Analyze'), + 'CHECK' => __('Check'), + 'OPTIMIZE' => __('Optimize'), + 'REBUILD' => __('Rebuild'), + 'REPAIR' => __('Repair') + ); + + $html_output = '
      ' + . '
      ' + . PMA_URL_getHiddenInputs($GLOBALS['db'], $GLOBALS['table']) + . '
      ' + . '' . __('Partition maintenance') . ''; + + $html_select = '' . "\n"; + $html_output .= sprintf(__('Partition %s'), $html_select); + + $html_output .= PMA_Util::getRadioFields( + 'partition_operation', $choices, '', false + ); + $html_output .= PMA_Util::showMySQLDocu('partitioning_maintenance'); + $this_url_params = array_merge( + $url_params, + array( + 'sql_query' => 'ALTER TABLE ' + . PMA_Util::backquote($GLOBALS['table']) + . ' REMOVE PARTITIONING;' + ) + ); + $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 + */ +function PMA_getHtmlForReferentialIntegrityCheck($foreign, $url_params) +{ + $html_output = '
      ' + . '
      ' + . '' . __('Check referential integrity:') . ''; + + $html_output .= '
        '; + + foreach ($foreign as $master => $arr) { + $join_query = 'SELECT ' + . PMA_Util::backquote($GLOBALS['table']) . '.*' + . ' FROM ' . PMA_Util::backquote($GLOBALS['table']) + . ' LEFT JOIN ' + . PMA_Util::backquote($arr['foreign_db']) + . '.' + . PMA_Util::backquote($arr['foreign_table']); + if ($arr['foreign_table'] == $GLOBALS['table']) { + $foreign_table = $GLOBALS['table'] . '1'; + $join_query .= ' AS ' . PMA_Util::backquote($foreign_table); + } else { + $foreign_table = $arr['foreign_table']; + } + $join_query .= ' ON ' + . PMA_Util::backquote($GLOBALS['table']) . '.' + . PMA_Util::backquote($master) + . ' = ' + . PMA_Util::backquote($arr['foreign_db']) + . '.' + . PMA_Util::backquote($foreign_table) . '.' + . PMA_Util::backquote($arr['foreign_field']) + . ' WHERE ' + . PMA_Util::backquote($arr['foreign_db']) + . '.' + . PMA_Util::backquote($foreign_table) . '.' + . PMA_Util::backquote($arr['foreign_field']) + . ' IS NULL AND ' + . PMA_Util::backquote($GLOBALS['table']) . '.' + . PMA_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 + */ +function PMA_getQueryAndResultForReorderingTable() +{ + $sql_query = 'ALTER TABLE ' + . PMA_Util::backquote($GLOBALS['table']) + . ' ORDER BY ' + . PMA_Util::backquote(urldecode($_REQUEST['order_field'])); + if (isset($_REQUEST['order_order']) + && $_REQUEST['order_order'] === 'desc' + ) { + $sql_query .= ' DESC'; + } + $sql_query .= ';'; + $result = $GLOBALS['dbi']->query($sql_query); + + return array($sql_query, $result); +} + +/** + * Get table alters array + * + * @param boolean $is_myisam_or_aria whether MYISAM | ARIA or not + * @param boolean $is_isam whether ISAM or not + * @param string $pack_keys pack keys + * @param string $checksum value of checksum + * @param boolean $is_aria whether ARIA or not + * @param string $page_checksum value of page checksum + * @param string $delay_key_write delay key write + * @param boolean $is_innodb whether INNODB or not + * @param boolean $is_pbxt whether PBXT or not + * @param string $row_format row format + * @param string $new_tbl_storage_engine table storage engine + * @param string $transactional value of transactional + * @param string $tbl_collation collation of the table + * + * @return array $table_alters + */ +function PMA_getTableAltersArray($is_myisam_or_aria, $is_isam, $pack_keys, + $checksum, $is_aria, $page_checksum, $delay_key_write, $is_innodb, + $is_pbxt, $row_format, $new_tbl_storage_engine, $transactional, $tbl_collation +) { + global $auto_increment; + + $table_alters = array(); + + if (isset($_REQUEST['comment']) + && urldecode($_REQUEST['prev_comment']) !== $_REQUEST['comment'] + ) { + $table_alters[] = 'COMMENT = \'' + . PMA_Util::sqlAddSlashes($_REQUEST['comment']) . '\''; + } + if (! empty($new_tbl_storage_engine) + && strtolower($new_tbl_storage_engine) !== strtolower($GLOBALS['tbl_storage_engine']) + ) { + $table_alters[] = 'ENGINE = ' . $new_tbl_storage_engine; + } + if (! empty($_REQUEST['tbl_collation']) + && $_REQUEST['tbl_collation'] !== $tbl_collation + ) { + $table_alters[] = 'DEFAULT ' + . PMA_generateCharsetQueryPart($_REQUEST['tbl_collation']); + } + + if (($is_myisam_or_aria || $is_isam) + && isset($_REQUEST['new_pack_keys']) + && $_REQUEST['new_pack_keys'] != (string)$pack_keys + ) { + $table_alters[] = 'pack_keys = ' . $_REQUEST['new_pack_keys']; + } + + $_REQUEST['new_checksum'] = empty($_REQUEST['new_checksum']) ? '0' : '1'; + if ($is_myisam_or_aria + && $_REQUEST['new_checksum'] !== $checksum + ) { + $table_alters[] = 'checksum = ' . $_REQUEST['new_checksum']; + } + + $_REQUEST['new_transactional'] + = empty($_REQUEST['new_transactional']) ? '0' : '1'; + if ($is_aria + && $_REQUEST['new_transactional'] !== $transactional + ) { + $table_alters[] = 'TRANSACTIONAL = ' . $_REQUEST['new_transactional']; + } + + $_REQUEST['new_page_checksum'] + = empty($_REQUEST['new_page_checksum']) ? '0' : '1'; + if ($is_aria + && $_REQUEST['new_page_checksum'] !== $page_checksum + ) { + $table_alters[] = 'PAGE_CHECKSUM = ' . $_REQUEST['new_page_checksum']; + } + + $_REQUEST['new_delay_key_write'] + = empty($_REQUEST['new_delay_key_write']) ? '0' : '1'; + if ($is_myisam_or_aria + && $_REQUEST['new_delay_key_write'] !== $delay_key_write + ) { + $table_alters[] = 'delay_key_write = ' . $_REQUEST['new_delay_key_write']; + } + + if (($is_myisam_or_aria || $is_innodb || $is_pbxt) + && ! empty($_REQUEST['new_auto_increment']) + && (! isset($auto_increment) + || $_REQUEST['new_auto_increment'] !== $auto_increment) + ) { + $table_alters[] = 'auto_increment = ' + . PMA_Util::sqlAddSlashes($_REQUEST['new_auto_increment']); + } + + if (($is_myisam_or_aria || $is_innodb || $is_pbxt) + && ! empty($_REQUEST['new_row_format']) + && (!strlen($row_format) + || strtolower($_REQUEST['new_row_format']) !== strtolower($row_format)) + ) { + $table_alters[] = 'ROW_FORMAT = ' + . PMA_Util::sqlAddSlashes($_REQUEST['new_row_format']); + } + + return $table_alters; +} + +/** + * set initial value of the set of variables, based on the current table engine + * + * @param string $tbl_storage_engine table storage engine + * + * @return array ($is_myisam_or_aria, $is_innodb, $is_isam, + * $is_berkeleydb, $is_aria, $is_pbxt) + */ +function PMA_setGlobalVariablesForEngine($tbl_storage_engine) +{ + $is_myisam_or_aria = $is_isam = $is_innodb = $is_berkeleydb + = $is_aria = $is_pbxt = false; + $upper_tbl_storage_engine = strtoupper($tbl_storage_engine); + + //Options that apply to MYISAM usually apply to ARIA + $is_myisam_or_aria = ($upper_tbl_storage_engine == 'MYISAM' + || $upper_tbl_storage_engine == 'ARIA' + || $upper_tbl_storage_engine == 'MARIA' + ); + $is_aria = ($upper_tbl_storage_engine == 'ARIA'); + + $is_isam = ($upper_tbl_storage_engine == 'ISAM'); + $is_innodb = ($upper_tbl_storage_engine == 'INNODB'); + $is_berkeleydb = ($upper_tbl_storage_engine == 'BERKELEYDB'); + $is_pbxt = ($upper_tbl_storage_engine == 'PBXT'); + + return array( + $is_myisam_or_aria, $is_innodb, $is_isam, + $is_berkeleydb, $is_aria, $is_pbxt + ); +} + +/** + * Get warning messages array + * + * @return array $warning_messages + */ +function PMA_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 (! ($_REQUEST['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 + */ +function PMA_getQueryAndResultForPartition() +{ + $sql_query = 'ALTER TABLE ' + . PMA_Util::backquote($GLOBALS['table']) . ' ' + . $_REQUEST['partition_operation'] + . ' PARTITION ' + . $_REQUEST['partition_name'] . ';'; + $result = $GLOBALS['dbi']->query($sql_query); + + return array($sql_query, $result); +} + +?> diff --git a/sources/libraries/parse_analyze.inc.php b/sources/libraries/parse_analyze.inc.php new file mode 100644 index 0000000..2a0c465 --- /dev/null +++ b/sources/libraries/parse_analyze.inc.php @@ -0,0 +1,140 @@ + $parsed_sql, + "analyzed_sql" => $analyzed_sql, + "reload" => $reload, + "drop_database" => $drop_database, + "is_explain" => $is_explain, + "is_delete" => $is_delete, + "is_affected" => $is_affected, + "is_replace" => $is_replace, + "is_insert" => $is_insert, + "is_maint" => $is_maint, + "is_show" => $is_show, + "is_analyse" => $is_analyse, + "is_export" => $is_export, + "is_group" => $is_group, + "is_func" => $is_func, + "is_count" => $is_count, + "is_select" => $is_select, + "is_procedure" => $is_procedure +); + + +// If the query is a Select, extract the db and table names and modify +// $db and $table, to have correct page headers, links and left frame. +// db and table name may be enclosed with backquotes, db is optionnal, +// query may contain aliases. + +/** + * @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) + */ +if ($is_select) { + $prev_db = $db; + if (isset($analyzed_sql[0]['table_ref'][0]['table_true_name'])) { + $table = $analyzed_sql[0]['table_ref'][0]['table_true_name']; + } + if (isset($analyzed_sql[0]['table_ref'][0]['db']) + && strlen($analyzed_sql[0]['table_ref'][0]['db']) + ) { + $db = $analyzed_sql[0]['table_ref'][0]['db']; + } else { + $db = $prev_db; + } + // Don't change reload, if we already decided to reload in import + if (empty($reload) && empty($GLOBALS['is_ajax_request'])) { + $reload = ($db == $prev_db) ? 0 : 1; + } +} +?> diff --git a/sources/libraries/php-gettext/gettext.inc b/sources/libraries/php-gettext/gettext.inc new file mode 100644 index 0000000..00b9666 --- /dev/null +++ b/sources/libraries/php-gettext/gettext.inc @@ -0,0 +1,536 @@ + + Copyright (c) 2009 Danilo Segan + + Drop in replacement for native gettext. + + This file is part of PHP-gettext. + + PHP-gettext 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. + + PHP-gettext 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 PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ +/* +LC_CTYPE 0 +LC_NUMERIC 1 +LC_TIME 2 +LC_COLLATE 3 +LC_MONETARY 4 +LC_MESSAGES 5 +LC_ALL 6 +*/ + +// LC_MESSAGES is not available if php-gettext is not loaded +// while the other constants are already available from session extension. +if (!defined('LC_MESSAGES')) { + define('LC_MESSAGES', 5); +} + +require('streams.php'); +require('gettext.php'); + + +// Variables + +global $text_domains, $default_domain, $LC_CATEGORIES, $EMULATEGETTEXT, $CURRENTLOCALE; +$text_domains = array(); +$default_domain = 'messages'; +$LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL'); +$EMULATEGETTEXT = 0; +$CURRENTLOCALE = ''; + +/* Class to hold a single domain included in $text_domains. */ +class domain { + var $l10n; + var $path; + var $codeset; +} + +// Utility functions + +/** + * Return a list of locales to try for any POSIX-style locale specification. + */ +function get_list_of_locales($locale) { + /* Figure out all possible locale names and start with the most + * specific ones. I.e. for sr_CS.UTF-8@latin, look through all of + * sr_CS.UTF-8@latin, sr_CS@latin, sr@latin, sr_CS.UTF-8, sr_CS, sr. + */ + $locale_names = array(); + $lang = NULL; + $country = NULL; + $charset = NULL; + $modifier = NULL; + if ($locale) { + if (preg_match("/^(?P[a-z]{2,3})" // language code + ."(?:_(?P[A-Z]{2}))?" // country code + ."(?:\.(?P[-A-Za-z0-9_]+))?" // charset + ."(?:@(?P[-A-Za-z0-9_]+))?$/", // @ modifier + $locale, $matches)) { + + if (isset($matches["lang"])) $lang = $matches["lang"]; + if (isset($matches["country"])) $country = $matches["country"]; + if (isset($matches["charset"])) $charset = $matches["charset"]; + if (isset($matches["modifier"])) $modifier = $matches["modifier"]; + + if ($modifier) { + if ($country) { + if ($charset) + array_push($locale_names, "${lang}_$country.$charset@$modifier"); + array_push($locale_names, "${lang}_$country@$modifier"); + } elseif ($charset) + array_push($locale_names, "${lang}.$charset@$modifier"); + array_push($locale_names, "$lang@$modifier"); + } + if ($country) { + if ($charset) + array_push($locale_names, "${lang}_$country.$charset"); + array_push($locale_names, "${lang}_$country"); + } elseif ($charset) + array_push($locale_names, "${lang}.$charset"); + array_push($locale_names, $lang); + } + + // If the locale name doesn't match POSIX style, just include it as-is. + if (!in_array($locale, $locale_names)) + array_push($locale_names, $locale); + } + return $locale_names; +} + +/** + * Utility function to get a StreamReader for the given text domain. + */ +function _get_reader($domain=null, $category=5, $enable_cache=true) { + global $text_domains, $default_domain, $LC_CATEGORIES; + if (!isset($domain)) $domain = $default_domain; + if (!isset($text_domains[$domain]->l10n)) { + // get the current locale + $locale = _setlocale(LC_MESSAGES, 0); + $bound_path = isset($text_domains[$domain]->path) ? + $text_domains[$domain]->path : './'; + $subpath = $LC_CATEGORIES[$category] ."/$domain.mo"; + + $locale_names = get_list_of_locales($locale); + $input = null; + foreach ($locale_names as $locale) { + $full_path = $bound_path . $locale . "/" . $subpath; + if (file_exists($full_path)) { + $input = new FileReader($full_path); + break; + } + } + + if (!array_key_exists($domain, $text_domains)) { + // Initialize an empty domain object. + $text_domains[$domain] = new domain(); + } + $text_domains[$domain]->l10n = new gettext_reader($input, + $enable_cache); + } + return $text_domains[$domain]->l10n; +} + +/** + * Returns whether we are using our emulated gettext API or PHP built-in one. + */ +function locale_emulation() { + global $EMULATEGETTEXT; + return $EMULATEGETTEXT; +} + +/** + * Checks if the current locale is supported on this system. + */ +function _check_locale_and_function($function=false) { + global $EMULATEGETTEXT; + if ($function and !function_exists($function)) + return false; + return !$EMULATEGETTEXT; +} + +/** + * Get the codeset for the given domain. + */ +function _get_codeset($domain=null) { + global $text_domains, $default_domain, $LC_CATEGORIES; + if (!isset($domain)) $domain = $default_domain; + return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding'); +} + +/** + * Convert the given string to the encoding set by bind_textdomain_codeset. + */ +function _encode($text) { + $source_encoding = mb_detect_encoding($text); + $target_encoding = _get_codeset(); + if ($source_encoding != $target_encoding) { + return mb_convert_encoding($text, $target_encoding, $source_encoding); + } + else { + return $text; + } +} + + +// Custom implementation of the standard gettext related functions + +/** + * Returns passed in $locale, or environment variable $LANG if $locale == ''. + */ +function _get_default_locale($locale) { + if ($locale == '') // emulate variable support + return getenv('LANG'); + else + return $locale; +} + +/** + * Sets a requested locale, if needed emulates it. + */ +function _setlocale($category, $locale) { + global $CURRENTLOCALE, $EMULATEGETTEXT; + if ($locale === 0) { // use === to differentiate between string "0" + if ($CURRENTLOCALE != '') + return $CURRENTLOCALE; + else + // obey LANG variable, maybe extend to support all of LC_* vars + // even if we tried to read locale without setting it first + return _setlocale($category, $CURRENTLOCALE); + } else { + if (function_exists('setlocale')) { + $ret = setlocale($category, $locale); + if (($locale == '' and !$ret) or // failed setting it by env + ($locale != '' and $ret != $locale)) { // failed setting it + // Failed setting it according to environment. + $CURRENTLOCALE = _get_default_locale($locale); + $EMULATEGETTEXT = 1; + } else { + $CURRENTLOCALE = $ret; + $EMULATEGETTEXT = 0; + } + } else { + // No function setlocale(), emulate it all. + $CURRENTLOCALE = _get_default_locale($locale); + $EMULATEGETTEXT = 1; + } + // Allow locale to be changed on the go for one translation domain. + global $text_domains, $default_domain; + if (array_key_exists($default_domain, $text_domains)) { + unset($text_domains[$default_domain]->l10n); + } + return $CURRENTLOCALE; + } +} + +/** + * Sets the path for a domain. + */ +function _bindtextdomain($domain, $path) { + global $text_domains; + // ensure $path ends with a slash ('/' should work for both, but lets still play nice) + if (substr(php_uname(), 0, 7) == "Windows") { + if ($path[strlen($path)-1] != '\\' and $path[strlen($path)-1] != '/') + $path .= '\\'; + } else { + if ($path[strlen($path)-1] != '/') + $path .= '/'; + } + if (!array_key_exists($domain, $text_domains)) { + // Initialize an empty domain object. + $text_domains[$domain] = new domain(); + } + $text_domains[$domain]->path = $path; +} + +/** + * Specify the character encoding in which the messages from the DOMAIN message catalog will be returned. + */ +function _bind_textdomain_codeset($domain, $codeset) { + global $text_domains; + $text_domains[$domain]->codeset = $codeset; +} + +/** + * Sets the default domain. + */ +function _textdomain($domain) { + global $default_domain; + $default_domain = $domain; +} + +/** + * Lookup a message in the current domain. + */ +function _gettext($msgid) { + $l10n = _get_reader(); + return _encode($l10n->translate($msgid)); +} + +/** + * Alias for gettext. + */ +function __($msgid) { + return _gettext($msgid); +} + +/** + * Plural version of gettext. + */ +function _ngettext($singular, $plural, $number) { + $l10n = _get_reader(); + return _encode($l10n->ngettext($singular, $plural, $number)); +} + +/** + * Override the current domain. + */ +function _dgettext($domain, $msgid) { + $l10n = _get_reader($domain); + return _encode($l10n->translate($msgid)); +} + +/** + * Plural version of dgettext. + */ +function _dngettext($domain, $singular, $plural, $number) { + $l10n = _get_reader($domain); + return _encode($l10n->ngettext($singular, $plural, $number)); +} + +/** + * Overrides the domain and category for a single lookup. + */ +function _dcgettext($domain, $msgid, $category) { + $l10n = _get_reader($domain, $category); + return _encode($l10n->translate($msgid)); +} +/** + * Plural version of dcgettext. + */ +function _dcngettext($domain, $singular, $plural, $number, $category) { + $l10n = _get_reader($domain, $category); + return _encode($l10n->ngettext($singular, $plural, $number)); +} + +/** + * Context version of gettext. + */ +function _pgettext($context, $msgid) { + $l10n = _get_reader(); + return _encode($l10n->pgettext($context, $msgid)); +} + +/** + * Override the current domain in a context gettext call. + */ +function _dpgettext($domain, $context, $msgid) { + $l10n = _get_reader($domain); + return _encode($l10n->pgettext($context, $msgid)); +} + +/** + * Overrides the domain and category for a single context-based lookup. + */ +function _dcpgettext($domain, $context, $msgid, $category) { + $l10n = _get_reader($domain, $category); + return _encode($l10n->pgettext($context, $msgid)); +} + +/** + * Context version of ngettext. + */ +function _npgettext($context, $singular, $plural) { + $l10n = _get_reader(); + return _encode($l10n->npgettext($context, $singular, $plural)); +} + +/** + * Override the current domain in a context ngettext call. + */ +function _dnpgettext($domain, $context, $singular, $plural) { + $l10n = _get_reader($domain); + return _encode($l10n->npgettext($context, $singular, $plural)); +} + +/** + * Overrides the domain and category for a plural context-based lookup. + */ +function _dcnpgettext($domain, $context, $singular, $plural, $category) { + $l10n = _get_reader($domain, $category); + return _encode($l10n->npgettext($context, $singular, $plural)); +} + + + +// Wrappers to use if the standard gettext functions are available, +// but the current locale is not supported by the system. +// Use the standard impl if the current locale is supported, use the +// custom impl otherwise. + +function T_setlocale($category, $locale) { + return _setlocale($category, $locale); +} + +function T_bindtextdomain($domain, $path) { + if (_check_locale_and_function()) return bindtextdomain($domain, $path); + else return _bindtextdomain($domain, $path); +} +function T_bind_textdomain_codeset($domain, $codeset) { + // bind_textdomain_codeset is available only in PHP 4.2.0+ + if (_check_locale_and_function('bind_textdomain_codeset')) + return bind_textdomain_codeset($domain, $codeset); + else return _bind_textdomain_codeset($domain, $codeset); +} +function T_textdomain($domain) { + if (_check_locale_and_function()) return textdomain($domain); + else return _textdomain($domain); +} +function T_gettext($msgid) { + if (_check_locale_and_function()) return gettext($msgid); + else return _gettext($msgid); +} +function T_($msgid) { + if (_check_locale_and_function()) return _($msgid); + return __($msgid); +} +function T_ngettext($singular, $plural, $number) { + if (_check_locale_and_function()) + return ngettext($singular, $plural, $number); + else return _ngettext($singular, $plural, $number); +} +function T_dgettext($domain, $msgid) { + if (_check_locale_and_function()) return dgettext($domain, $msgid); + else return _dgettext($domain, $msgid); +} +function T_dngettext($domain, $singular, $plural, $number) { + if (_check_locale_and_function()) + return dngettext($domain, $singular, $plural, $number); + else return _dngettext($domain, $singular, $plural, $number); +} +function T_dcgettext($domain, $msgid, $category) { + if (_check_locale_and_function()) + return dcgettext($domain, $msgid, $category); + else return _dcgettext($domain, $msgid, $category); +} +function T_dcngettext($domain, $singular, $plural, $number, $category) { + if (_check_locale_and_function()) + return dcngettext($domain, $singular, $plural, $number, $category); + else return _dcngettext($domain, $singular, $plural, $number, $category); +} + +function T_pgettext($context, $msgid) { + if (_check_locale_and_function('pgettext')) + return pgettext($context, $msgid); + else + return _pgettext($context, $msgid); +} + +function T_dpgettext($domain, $context, $msgid) { + if (_check_locale_and_function('dpgettext')) + return dpgettext($domain, $context, $msgid); + else + return _dpgettext($domain, $context, $msgid); +} + +function T_dcpgettext($domain, $context, $msgid, $category) { + if (_check_locale_and_function('dcpgettext')) + return dcpgettext($domain, $context, $msgid, $category); + else + return _dcpgettext($domain, $context, $msgid, $category); +} + +function T_npgettext($context, $singular, $plural, $number) { + if (_check_locale_and_function('npgettext')) + return npgettext($context, $singular, $plural, $number); + else + return _npgettext($context, $singular, $plural, $number); +} + +function T_dnpgettext($domain, $context, $singular, $plural, $number) { + if (_check_locale_and_function('dnpgettext')) + return dnpgettext($domain, $context, $singular, $plural, $number); + else + return _dnpgettext($domain, $context, $singular, $plural, $number); +} + +function T_dcnpgettext($domain, $context, $singular, $plural, + $number, $category) { + if (_check_locale_and_function('dcnpgettext')) + return dcnpgettext($domain, $context, $singular, + $plural, $number, $category); + else + return _dcnpgettext($domain, $context, $singular, + $plural, $number, $category); +} + + + +// Wrappers used as a drop in replacement for the standard gettext functions + +if (!function_exists('gettext')) { + function bindtextdomain($domain, $path) { + return _bindtextdomain($domain, $path); + } + function bind_textdomain_codeset($domain, $codeset) { + return _bind_textdomain_codeset($domain, $codeset); + } + function textdomain($domain) { + return _textdomain($domain); + } + function gettext($msgid) { + return _gettext($msgid); + } + function _($msgid) { + return __($msgid); + } + function ngettext($singular, $plural, $number) { + return _ngettext($singular, $plural, $number); + } + function dgettext($domain, $msgid) { + return _dgettext($domain, $msgid); + } + function dngettext($domain, $singular, $plural, $number) { + return _dngettext($domain, $singular, $plural, $number); + } + function dcgettext($domain, $msgid, $category) { + return _dcgettext($domain, $msgid, $category); + } + function dcngettext($domain, $singular, $plural, $number, $category) { + return _dcngettext($domain, $singular, $plural, $number, $category); + } + function pgettext($context, $msgid) { + return _pgettext($context, $msgid); + } + function npgettext($context, $singular, $plural, $number) { + return _npgettext($context, $singular, $plural, $number); + } + function dpgettext($domain, $context, $msgid) { + return _dpgettext($domain, $context, $msgid); + } + function dnpgettext($domain, $context, $singular, $plural, $number) { + return _dnpgettext($domain, $context, $singular, $plural, $number); + } + function dcpgettext($domain, $context, $msgid, $category) { + return _dcpgettext($domain, $context, $msgid, $category); + } + function dcnpgettext($domain, $context, $singular, $plural, + $number, $category) { + return _dcnpgettext($domain, $context, $singular, $plural, + $number, $category); + } +} + +?> diff --git a/sources/libraries/php-gettext/gettext.php b/sources/libraries/php-gettext/gettext.php new file mode 100644 index 0000000..5064047 --- /dev/null +++ b/sources/libraries/php-gettext/gettext.php @@ -0,0 +1,432 @@ +. + Copyright (c) 2005 Nico Kaiser + + This file is part of PHP-gettext. + + PHP-gettext 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. + + PHP-gettext 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 PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +/** + * Provides a simple gettext replacement that works independently from + * the system's gettext abilities. + * It can read MO files and use them for translating strings. + * The files are passed to gettext_reader as a Stream (see streams.php) + * + * This version has the ability to cache all strings and translations to + * speed up the string lookup. + * While the cache is enabled by default, it can be switched off with the + * second parameter in the constructor (e.g. whenusing very large MO files + * that you don't want to keep in memory) + */ +class gettext_reader { + //public: + var $error = 0; // public variable that holds error code (0 if no error) + + //private: + var $BYTEORDER = 0; // 0: low endian, 1: big endian + var $STREAM = NULL; + var $short_circuit = false; + var $enable_cache = false; + var $originals = NULL; // offset of original table + var $translations = NULL; // offset of translation table + var $pluralheader = NULL; // cache header field for plural forms + var $total = 0; // total string count + var $table_originals = NULL; // table for original strings (offsets) + var $table_translations = NULL; // table for translated strings (offsets) + var $cache_translations = NULL; // original -> translation mapping + + + /* Methods */ + + + /** + * Reads a 32bit Integer from the Stream + * + * @access private + * @return Integer from the Stream + */ + function readint() { + if ($this->BYTEORDER == 0) { + // low endian + $input=unpack('V', $this->STREAM->read(4)); + return array_shift($input); + } else { + // big endian + $input=unpack('N', $this->STREAM->read(4)); + return array_shift($input); + } + } + + function read($bytes) { + return $this->STREAM->read($bytes); + } + + /** + * Reads an array of Integers from the Stream + * + * @param int count How many elements should be read + * @return Array of Integers + */ + function readintarray($count) { + if ($this->BYTEORDER == 0) { + // low endian + return unpack('V'.$count, $this->STREAM->read(4 * $count)); + } else { + // big endian + return unpack('N'.$count, $this->STREAM->read(4 * $count)); + } + } + + /** + * Constructor + * + * @param object Reader the StreamReader object + * @param boolean enable_cache Enable or disable caching of strings (default on) + */ + function gettext_reader($Reader, $enable_cache = true) { + // If there isn't a StreamReader, turn on short circuit mode. + if (! $Reader || isset($Reader->error) ) { + $this->short_circuit = true; + return; + } + + // Caching can be turned off + $this->enable_cache = $enable_cache; + + $MAGIC1 = "\x95\x04\x12\xde"; + $MAGIC2 = "\xde\x12\x04\x95"; + + $this->STREAM = $Reader; + $magic = $this->read(4); + if ($magic == $MAGIC1) { + $this->BYTEORDER = 1; + } elseif ($magic == $MAGIC2) { + $this->BYTEORDER = 0; + } else { + $this->error = 1; // not MO file + return false; + } + + // FIXME: Do we care about revision? We should. + $revision = $this->readint(); + + $this->total = $this->readint(); + $this->originals = $this->readint(); + $this->translations = $this->readint(); + } + + /** + * Loads the translation tables from the MO file into the cache + * If caching is enabled, also loads all strings into a cache + * to speed up translation lookups + * + * @access private + */ + function load_tables() { + if (is_array($this->cache_translations) && + is_array($this->table_originals) && + is_array($this->table_translations)) + return; + + /* get original and translations tables */ + if (!is_array($this->table_originals)) { + $this->STREAM->seekto($this->originals); + $this->table_originals = $this->readintarray($this->total * 2); + } + if (!is_array($this->table_translations)) { + $this->STREAM->seekto($this->translations); + $this->table_translations = $this->readintarray($this->total * 2); + } + + if ($this->enable_cache) { + $this->cache_translations = array (); + /* read all strings in the cache */ + for ($i = 0; $i < $this->total; $i++) { + $this->STREAM->seekto($this->table_originals[$i * 2 + 2]); + $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]); + $this->STREAM->seekto($this->table_translations[$i * 2 + 2]); + $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]); + $this->cache_translations[$original] = $translation; + } + } + } + + /** + * Returns a string from the "originals" table + * + * @access private + * @param int num Offset number of original string + * @return string Requested string if found, otherwise '' + */ + function get_original_string($num) { + $length = $this->table_originals[$num * 2 + 1]; + $offset = $this->table_originals[$num * 2 + 2]; + if (! $length) + return ''; + $this->STREAM->seekto($offset); + $data = $this->STREAM->read($length); + return (string)$data; + } + + /** + * Returns a string from the "translations" table + * + * @access private + * @param int num Offset number of original string + * @return string Requested string if found, otherwise '' + */ + function get_translation_string($num) { + $length = $this->table_translations[$num * 2 + 1]; + $offset = $this->table_translations[$num * 2 + 2]; + if (! $length) + return ''; + $this->STREAM->seekto($offset); + $data = $this->STREAM->read($length); + return (string)$data; + } + + /** + * Binary search for string + * + * @access private + * @param string string + * @param int start (internally used in recursive function) + * @param int end (internally used in recursive function) + * @return int string number (offset in originals table) + */ + function find_string($string, $start = -1, $end = -1) { + if (($start == -1) or ($end == -1)) { + // find_string is called with only one parameter, set start end end + $start = 0; + $end = $this->total; + } + if (abs($start - $end) <= 1) { + // We're done, now we either found the string, or it doesn't exist + $txt = $this->get_original_string($start); + if ($string == $txt) + return $start; + else + return -1; + } else if ($start > $end) { + // start > end -> turn around and start over + return $this->find_string($string, $end, $start); + } else { + // Divide table in two parts + $half = (int)(($start + $end) / 2); + $cmp = strcmp($string, $this->get_original_string($half)); + if ($cmp == 0) + // string is exactly in the middle => return it + return $half; + else if ($cmp < 0) + // The string is in the upper half + return $this->find_string($string, $start, $half); + else + // The string is in the lower half + return $this->find_string($string, $half, $end); + } + } + + /** + * Translates a string + * + * @access public + * @param string string to be translated + * @return string translated string (or original, if not found) + */ + function translate($string) { + if ($this->short_circuit) + return $string; + $this->load_tables(); + + if ($this->enable_cache) { + // Caching enabled, get translated string from cache + if (array_key_exists($string, $this->cache_translations)) + return $this->cache_translations[$string]; + else + return $string; + } else { + // Caching not enabled, try to find string + $num = $this->find_string($string); + if ($num == -1) + return $string; + else + return $this->get_translation_string($num); + } + } + + /** + * Sanitize plural form expression for use in PHP eval call. + * + * @access private + * @return string sanitized plural form expression + */ + function sanitize_plural_expression($expr) { + // Get rid of disallowed characters. + $expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr); + + // Add parenthesis for tertiary '?' operator. + $expr .= ';'; + $res = ''; + $p = 0; + for ($i = 0; $i < strlen($expr); $i++) { + $ch = $expr[$i]; + switch ($ch) { + case '?': + $res .= ' ? ('; + $p++; + break; + case ':': + $res .= ') : ('; + break; + case ';': + $res .= str_repeat( ')', $p) . ';'; + $p = 0; + break; + default: + $res .= $ch; + } + } + return $res; + } + + /** + * Parse full PO header and extract only plural forms line. + * + * @access private + * @return string verbatim plural form header field + */ + function extract_plural_forms_header_from_po_header($header) { + if (preg_match("/(^|\n)plural-forms: ([^\n]*)\n/i", $header, $regs)) + $expr = $regs[2]; + else + $expr = "nplurals=2; plural=n == 1 ? 0 : 1;"; + return $expr; + } + + /** + * Get possible plural forms from MO header + * + * @access private + * @return string plural form header + */ + function get_plural_forms() { + // lets assume message number 0 is header + // this is true, right? + $this->load_tables(); + + // cache header field for plural forms + if (! is_string($this->pluralheader)) { + if ($this->enable_cache) { + $header = $this->cache_translations[""]; + } else { + $header = $this->get_translation_string(0); + } + $expr = $this->extract_plural_forms_header_from_po_header($header); + $this->pluralheader = $this->sanitize_plural_expression($expr); + } + return $this->pluralheader; + } + + /** + * Detects which plural form to take + * + * @access private + * @param n count + * @return int array index of the right plural form + */ + function select_string($n) { + $string = $this->get_plural_forms(); + $string = str_replace('nplurals',"\$total",$string); + $string = str_replace("n",$n,$string); + $string = str_replace('plural',"\$plural",$string); + + $total = 0; + $plural = 0; + + eval("$string"); + if ($plural >= $total) $plural = $total - 1; + return $plural; + } + + /** + * Plural version of gettext + * + * @access public + * @param string single + * @param string plural + * @param string number + * @return translated plural form + */ + function ngettext($single, $plural, $number) { + if ($this->short_circuit) { + if ($number != 1) + return $plural; + else + return $single; + } + + // find out the appropriate form + $select = $this->select_string($number); + + // this should contains all strings separated by NULLs + $key = $single . chr(0) . $plural; + + + if ($this->enable_cache) { + if (! array_key_exists($key, $this->cache_translations)) { + return ($number != 1) ? $plural : $single; + } else { + $result = $this->cache_translations[$key]; + $list = explode(chr(0), $result); + return $list[$select]; + } + } else { + $num = $this->find_string($key); + if ($num == -1) { + return ($number != 1) ? $plural : $single; + } else { + $result = $this->get_translation_string($num); + $list = explode(chr(0), $result); + return $list[$select]; + } + } + } + + function pgettext($context, $msgid) { + $key = $context . chr(4) . $msgid; + $ret = $this->translate($key); + if (strpos($ret, "\004") !== FALSE) { + return $msgid; + } else { + return $ret; + } + } + + function npgettext($context, $singular, $plural, $number) { + $key = $context . chr(4) . $singular; + $ret = $this->ngettext($key, $plural, $number); + if (strpos($ret, "\004") !== FALSE) { + return $singular; + } else { + return $ret; + } + + } +} + +?> diff --git a/sources/libraries/php-gettext/streams.php b/sources/libraries/php-gettext/streams.php new file mode 100644 index 0000000..3cdc158 --- /dev/null +++ b/sources/libraries/php-gettext/streams.php @@ -0,0 +1,167 @@ +. + + This file is part of PHP-gettext. + + PHP-gettext 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. + + PHP-gettext 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 PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + + + // Simple class to wrap file streams, string streams, etc. + // seek is essential, and it should be byte stream +class StreamReader { + // should return a string [FIXME: perhaps return array of bytes?] + function read($bytes) { + return false; + } + + // should return new position + function seekto($position) { + return false; + } + + // returns current position + function currentpos() { + return false; + } + + // returns length of entire stream (limit for seekto()s) + function length() { + return false; + } +}; + +class StringReader { + var $_pos; + var $_str; + + function StringReader($str='') { + $this->_str = $str; + $this->_pos = 0; + } + + function read($bytes) { + $data = substr($this->_str, $this->_pos, $bytes); + $this->_pos += $bytes; + if (strlen($this->_str)<$this->_pos) + $this->_pos = strlen($this->_str); + + return $data; + } + + function seekto($pos) { + $this->_pos = $pos; + if (strlen($this->_str)<$this->_pos) + $this->_pos = strlen($this->_str); + return $this->_pos; + } + + function currentpos() { + return $this->_pos; + } + + function length() { + return strlen($this->_str); + } + +}; + + +class FileReader { + var $_pos; + var $_fd; + var $_length; + + function FileReader($filename) { + if (file_exists($filename)) { + + $this->_length=filesize($filename); + $this->_pos = 0; + $this->_fd = fopen($filename,'rb'); + if (!$this->_fd) { + $this->error = 3; // Cannot read file, probably permissions + return false; + } + } else { + $this->error = 2; // File doesn't exist + return false; + } + } + + function read($bytes) { + if ($bytes) { + fseek($this->_fd, $this->_pos); + + // PHP 5.1.1 does not read more than 8192 bytes in one fread() + // the discussions at PHP Bugs suggest it's the intended behaviour + $data = ''; + while ($bytes > 0) { + $chunk = fread($this->_fd, $bytes); + $data .= $chunk; + $bytes -= strlen($chunk); + } + $this->_pos = ftell($this->_fd); + + return $data; + } else return ''; + } + + function seekto($pos) { + fseek($this->_fd, $pos); + $this->_pos = ftell($this->_fd); + return $this->_pos; + } + + function currentpos() { + return $this->_pos; + } + + function length() { + return $this->_length; + } + + function close() { + fclose($this->_fd); + } + +}; + +// Preloads entire file in memory first, then creates a StringReader +// over it (it assumes knowledge of StringReader internals) +class CachedFileReader extends StringReader { + function CachedFileReader($filename) { + if (file_exists($filename)) { + + $length=filesize($filename); + $fd = fopen($filename,'rb'); + + if (!$fd) { + $this->error = 3; // Cannot read file, probably permissions + return false; + } + $this->_str = fread($fd, $length); + fclose($fd); + + } else { + $this->error = 2; // File doesn't exist + return false; + } + } +}; + + +?> diff --git a/sources/libraries/phpseclib/Crypt/AES.php b/sources/libraries/phpseclib/Crypt/AES.php new file mode 100644 index 0000000..58c8b52 --- /dev/null +++ b/sources/libraries/phpseclib/Crypt/AES.php @@ -0,0 +1,612 @@ + + * setKey('abcdefghijklmnop'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $aes->decrypt($aes->encrypt($plaintext)); + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_AES + * @author Jim Wigginton + * @copyright MMVIII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: AES.php,v 1.7 2010/02/09 06:10:25 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Crypt_Rijndael + */ +if (!class_exists('Crypt_Rijndael')) { + require_once 'Rijndael.php'; +} + +/**#@+ + * @access public + * @see Crypt_AES::encrypt() + * @see Crypt_AES::decrypt() + */ +/** + * Encrypt / decrypt using the Counter mode. + * + * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 + */ +define('CRYPT_AES_MODE_CTR', -1); +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_AES_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_AES_MODE_CBC', 2); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 + */ +define('CRYPT_AES_MODE_CFB', 3); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29 + */ +define('CRYPT_AES_MODE_OFB', 4); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_AES::Crypt_AES() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_AES_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_AES_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of AES. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_AES + */ +class Crypt_AES extends Crypt_Rijndael { + /** + * mcrypt resource for encryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_AES::encrypt() + * @var String + * @access private + */ + var $enmcrypt; + + /** + * mcrypt resource for decryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_AES::decrypt() + * @var String + * @access private + */ + var $demcrypt; + + /** + * mcrypt resource for CFB mode + * + * @see Crypt_AES::encrypt() + * @see Crypt_AES::decrypt() + * @var String + * @access private + */ + var $ecb; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_AES_MODE_ECB or CRYPT_AES_MODE_CBC. If not explictly set, CRYPT_AES_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_AES + * @access public + */ + function Crypt_AES($mode = CRYPT_AES_MODE_CBC) + { + if ( !defined('CRYPT_AES_MODE') ) { + switch (true) { + case extension_loaded('mcrypt') && in_array('rijndael-128', mcrypt_list_algorithms()): + define('CRYPT_AES_MODE', CRYPT_AES_MODE_MCRYPT); + break; + default: + define('CRYPT_AES_MODE', CRYPT_AES_MODE_INTERNAL); + } + } + + switch ( CRYPT_AES_MODE ) { + case CRYPT_AES_MODE_MCRYPT: + switch ($mode) { + case CRYPT_AES_MODE_ECB: + $this->paddable = true; + $this->mode = MCRYPT_MODE_ECB; + break; + case CRYPT_AES_MODE_CTR: + // ctr doesn't have a constant associated with it even though it appears to be fairly widely + // supported. in lieu of knowing just how widely supported it is, i've, for now, opted not to + // include a compatibility layer. the layer has been implemented but, for now, is commented out. + $this->mode = 'ctr'; + //$this->mode = in_array('ctr', mcrypt_list_modes()) ? 'ctr' : CRYPT_AES_MODE_CTR; + break; + case CRYPT_AES_MODE_CFB: + $this->mode = 'ncfb'; + break; + case CRYPT_AES_MODE_OFB: + $this->mode = MCRYPT_MODE_NOFB; + break; + case CRYPT_AES_MODE_CBC: + default: + $this->paddable = true; + $this->mode = MCRYPT_MODE_CBC; + } + + $this->debuffer = $this->enbuffer = ''; + + break; + default: + switch ($mode) { + case CRYPT_AES_MODE_ECB: + $this->paddable = true; + $this->mode = CRYPT_RIJNDAEL_MODE_ECB; + break; + case CRYPT_AES_MODE_CTR: + $this->mode = CRYPT_RIJNDAEL_MODE_CTR; + break; + case CRYPT_AES_MODE_CFB: + $this->mode = CRYPT_RIJNDAEL_MODE_CFB; + break; + case CRYPT_AES_MODE_OFB: + $this->mode = CRYPT_RIJNDAEL_MODE_OFB; + break; + case CRYPT_AES_MODE_CBC: + default: + $this->paddable = true; + $this->mode = CRYPT_RIJNDAEL_MODE_CBC; + } + } + + if (CRYPT_AES_MODE == CRYPT_AES_MODE_INTERNAL) { + parent::Crypt_Rijndael($this->mode); + } + } + + /** + * Dummy function + * + * Since Crypt_AES extends Crypt_Rijndael, this function is, technically, available, but it doesn't do anything. + * + * @access public + * @param Integer $length + */ + function setBlockLength($length) + { + return; + } + + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + parent::setIV($iv); + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $this->changed = true; + } + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with up to 16 additional bytes. Other AES implementations may or may not pad in the + * same manner. Other common approaches to padding and the reasons why it's necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 16, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_AES::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $changed = $this->changed; + $this->_mcryptSetup(); + /* + if ($this->mode == CRYPT_AES_MODE_CTR) { + $iv = $this->encryptIV; + $xor = mcrypt_generic($this->enmcrypt, $this->_generate_xor(strlen($plaintext), $iv)); + $ciphertext = $plaintext ^ $xor; + if ($this->continuousBuffer) { + $this->encryptIV = $iv; + } + return $ciphertext; + } + */ + // re: http://phpseclib.sourceforge.net/cfb-demo.phps + // using mcrypt's default handing of CFB the above would output two different things. using phpseclib's + // rewritten CFB implementation the above outputs the same thing twice. + if ($this->mode == 'ncfb') { + if ($changed) { + $this->ecb = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, ''); + mcrypt_generic_init($this->ecb, $this->key, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); + } + + if (strlen($this->enbuffer)) { + $ciphertext = $plaintext ^ substr($this->encryptIV, strlen($this->enbuffer)); + $this->enbuffer.= $ciphertext; + if (strlen($this->enbuffer) == 16) { + $this->encryptIV = $this->enbuffer; + $this->enbuffer = ''; + mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV); + } + $plaintext = substr($plaintext, strlen($ciphertext)); + } else { + $ciphertext = ''; + } + + $last_pos = strlen($plaintext) & 0xFFFFFFF0; + $ciphertext.= $last_pos ? mcrypt_generic($this->enmcrypt, substr($plaintext, 0, $last_pos)) : ''; + + if (strlen($plaintext) & 0xF) { + if (strlen($ciphertext)) { + $this->encryptIV = substr($ciphertext, -16); + } + $this->encryptIV = mcrypt_generic($this->ecb, $this->encryptIV); + $this->enbuffer = substr($plaintext, $last_pos) ^ $this->encryptIV; + $ciphertext.= $this->enbuffer; + } + + return $ciphertext; + } + + if ($this->paddable) { + $plaintext = $this->_pad($plaintext); + } + + $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv); + } + + return $ciphertext; + } + + return parent::encrypt($plaintext); + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of 16, null bytes will be added to the end of the string until it is. + * + * @see Crypt_AES::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $changed = $this->changed; + $this->_mcryptSetup(); + /* + if ($this->mode == CRYPT_AES_MODE_CTR) { + $iv = $this->decryptIV; + $xor = mcrypt_generic($this->enmcrypt, $this->_generate_xor(strlen($ciphertext), $iv)); + $plaintext = $ciphertext ^ $xor; + if ($this->continuousBuffer) { + $this->decryptIV = $iv; + } + return $plaintext; + } + */ + if ($this->mode == 'ncfb') { + if ($changed) { + $this->ecb = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, ''); + mcrypt_generic_init($this->ecb, $this->key, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); + } + + if (strlen($this->debuffer)) { + $plaintext = $ciphertext ^ substr($this->decryptIV, strlen($this->debuffer)); + + $this->debuffer.= substr($ciphertext, 0, strlen($plaintext)); + if (strlen($this->debuffer) == 16) { + $this->decryptIV = $this->debuffer; + $this->debuffer = ''; + mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV); + } + $ciphertext = substr($ciphertext, strlen($plaintext)); + } else { + $plaintext = ''; + } + + $last_pos = strlen($ciphertext) & 0xFFFFFFF0; + $plaintext.= $last_pos ? mdecrypt_generic($this->demcrypt, substr($ciphertext, 0, $last_pos)) : ''; + + if (strlen($ciphertext) & 0xF) { + if (strlen($plaintext)) { + $this->decryptIV = substr($ciphertext, $last_pos - 16, 16); + } + $this->decryptIV = mcrypt_generic($this->ecb, $this->decryptIV); + $this->debuffer = substr($ciphertext, $last_pos); + $plaintext.= $this->debuffer ^ $this->decryptIV; + } + + return $plaintext; + } + + if ($this->paddable) { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 15) & 0xFFFFFFF0, chr(0)); + } + + $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->demcrypt, $this->key, $this->iv); + } + + return $this->paddable ? $this->_unpad($plaintext) : $plaintext; + } + + return parent::decrypt($ciphertext); + } + + /** + * Setup mcrypt + * + * Validates all the variables. + * + * @access private + */ + function _mcryptSetup() + { + if (!$this->changed) { + return; + } + + if (!$this->explicit_key_length) { + // this just copied from Crypt_Rijndael::_setup() + $length = strlen($this->key) >> 2; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + } + + switch ($this->Nk) { + case 4: // 128 + $this->key_size = 16; + break; + case 5: // 160 + case 6: // 192 + $this->key_size = 24; + break; + case 7: // 224 + case 8: // 256 + $this->key_size = 32; + } + + $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0)); + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, 16), 16, chr(0)); + + if (!isset($this->enmcrypt)) { + $mode = $this->mode; + //$mode = $this->mode == CRYPT_AES_MODE_CTR ? MCRYPT_MODE_ECB : $this->mode; + + $this->demcrypt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', $mode, ''); + $this->enmcrypt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', $mode, ''); + } // else should mcrypt_generic_deinit be called? + + mcrypt_generic_init($this->demcrypt, $this->key, $this->iv); + mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv); + + $this->changed = false; + } + + /** + * Encrypts a block + * + * Optimized over Crypt_Rijndael's implementation by means of loop unrolling. + * + * @see Crypt_Rijndael::_encryptBlock() + * @access private + * @param String $in + * @return String + */ + function _encryptBlock($in) + { + $state = unpack('N*word', $in); + + $Nr = $this->Nr; + $w = $this->w; + $t0 = $this->t0; + $t1 = $this->t1; + $t2 = $this->t2; + $t3 = $this->t3; + + // addRoundKey and reindex $state + $state = array( + $state['word1'] ^ $w[0][0], + $state['word2'] ^ $w[0][1], + $state['word3'] ^ $w[0][2], + $state['word4'] ^ $w[0][3] + ); + + // shiftRows + subWord + mixColumns + addRoundKey + // we could loop unroll this and use if statements to do more rounds as necessary, but, in my tests, that yields + // only a marginal improvement. since that also, imho, hinders the readability of the code, i've opted not to do it. + for ($round = 1; $round < $this->Nr; $round++) { + $state = array( + $t0[$state[0] & 0xFF000000] ^ $t1[$state[1] & 0x00FF0000] ^ $t2[$state[2] & 0x0000FF00] ^ $t3[$state[3] & 0x000000FF] ^ $w[$round][0], + $t0[$state[1] & 0xFF000000] ^ $t1[$state[2] & 0x00FF0000] ^ $t2[$state[3] & 0x0000FF00] ^ $t3[$state[0] & 0x000000FF] ^ $w[$round][1], + $t0[$state[2] & 0xFF000000] ^ $t1[$state[3] & 0x00FF0000] ^ $t2[$state[0] & 0x0000FF00] ^ $t3[$state[1] & 0x000000FF] ^ $w[$round][2], + $t0[$state[3] & 0xFF000000] ^ $t1[$state[0] & 0x00FF0000] ^ $t2[$state[1] & 0x0000FF00] ^ $t3[$state[2] & 0x000000FF] ^ $w[$round][3] + ); + + } + + // subWord + $state = array( + $this->_subWord($state[0]), + $this->_subWord($state[1]), + $this->_subWord($state[2]), + $this->_subWord($state[3]) + ); + + // shiftRows + addRoundKey + $state = array( + ($state[0] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[3] & 0x000000FF) ^ $this->w[$this->Nr][0], + ($state[1] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[0] & 0x000000FF) ^ $this->w[$this->Nr][1], + ($state[2] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[1] & 0x000000FF) ^ $this->w[$this->Nr][2], + ($state[3] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[2] & 0x000000FF) ^ $this->w[$this->Nr][3] + ); + + return pack('N*', $state[0], $state[1], $state[2], $state[3]); + } + + /** + * Decrypts a block + * + * Optimized over Crypt_Rijndael's implementation by means of loop unrolling. + * + * @see Crypt_Rijndael::_decryptBlock() + * @access private + * @param String $in + * @return String + */ + function _decryptBlock($in) + { + $state = unpack('N*word', $in); + + $Nr = $this->Nr; + $dw = $this->dw; + $dt0 = $this->dt0; + $dt1 = $this->dt1; + $dt2 = $this->dt2; + $dt3 = $this->dt3; + + // addRoundKey and reindex $state + $state = array( + $state['word1'] ^ $dw[$this->Nr][0], + $state['word2'] ^ $dw[$this->Nr][1], + $state['word3'] ^ $dw[$this->Nr][2], + $state['word4'] ^ $dw[$this->Nr][3] + ); + + + // invShiftRows + invSubBytes + invMixColumns + addRoundKey + for ($round = $this->Nr - 1; $round > 0; $round--) { + $state = array( + $dt0[$state[0] & 0xFF000000] ^ $dt1[$state[3] & 0x00FF0000] ^ $dt2[$state[2] & 0x0000FF00] ^ $dt3[$state[1] & 0x000000FF] ^ $dw[$round][0], + $dt0[$state[1] & 0xFF000000] ^ $dt1[$state[0] & 0x00FF0000] ^ $dt2[$state[3] & 0x0000FF00] ^ $dt3[$state[2] & 0x000000FF] ^ $dw[$round][1], + $dt0[$state[2] & 0xFF000000] ^ $dt1[$state[1] & 0x00FF0000] ^ $dt2[$state[0] & 0x0000FF00] ^ $dt3[$state[3] & 0x000000FF] ^ $dw[$round][2], + $dt0[$state[3] & 0xFF000000] ^ $dt1[$state[2] & 0x00FF0000] ^ $dt2[$state[1] & 0x0000FF00] ^ $dt3[$state[0] & 0x000000FF] ^ $dw[$round][3] + ); + } + + // invShiftRows + invSubWord + addRoundKey + $state = array( + $this->_invSubWord(($state[0] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[1] & 0x000000FF)) ^ $dw[0][0], + $this->_invSubWord(($state[1] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[2] & 0x000000FF)) ^ $dw[0][1], + $this->_invSubWord(($state[2] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[3] & 0x000000FF)) ^ $dw[0][2], + $this->_invSubWord(($state[3] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[0] & 0x000000FF)) ^ $dw[0][3] + ); + + return pack('N*', $state[0], $state[1], $state[2], $state[3]); + } +} + +// vim: ts=4:sw=4:et: +// vim6: fdl=1: +?> diff --git a/sources/libraries/phpseclib/Crypt/Rijndael.php b/sources/libraries/phpseclib/Crypt/Rijndael.php new file mode 100644 index 0000000..b5aef38 --- /dev/null +++ b/sources/libraries/phpseclib/Crypt/Rijndael.php @@ -0,0 +1,1479 @@ + + * setKey('abcdefghijklmnop'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $rijndael->decrypt($rijndael->encrypt($plaintext)); + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_Rijndael + * @author Jim Wigginton + * @copyright MMVIII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: Rijndael.php,v 1.12 2010/02/09 06:10:26 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access public + * @see Crypt_Rijndael::encrypt() + * @see Crypt_Rijndael::decrypt() + */ +/** + * Encrypt / decrypt using the Counter mode. + * + * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 + */ +define('CRYPT_RIJNDAEL_MODE_CTR', -1); +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_RIJNDAEL_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_RIJNDAEL_MODE_CBC', 2); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 + */ +define('CRYPT_RIJNDAEL_MODE_CFB', 3); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29 + */ +define('CRYPT_RIJNDAEL_MODE_OFB', 4); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_Rijndael::Crypt_Rijndael() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_RIJNDAEL_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_RIJNDAEL_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of Rijndael. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_Rijndael + */ +class Crypt_Rijndael { + /** + * The Encryption Mode + * + * @see Crypt_Rijndael::Crypt_Rijndael() + * @var Integer + * @access private + */ + var $mode; + + /** + * The Key + * + * @see Crypt_Rijndael::setKey() + * @var String + * @access private + */ + var $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + + /** + * The Initialization Vector + * + * @see Crypt_Rijndael::setIV() + * @var String + * @access private + */ + var $iv = ''; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var String + * @access private + */ + var $encryptIV = ''; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var String + * @access private + */ + var $decryptIV = ''; + + /** + * Continuous Buffer status + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var Boolean + * @access private + */ + var $continuousBuffer = false; + + /** + * Padding status + * + * @see Crypt_Rijndael::enablePadding() + * @var Boolean + * @access private + */ + var $padding = true; + + /** + * Does the key schedule need to be (re)calculated? + * + * @see setKey() + * @see setBlockLength() + * @see setKeyLength() + * @var Boolean + * @access private + */ + var $changed = true; + + /** + * Has the key length explicitly been set or should it be derived from the key, itself? + * + * @see setKeyLength() + * @var Boolean + * @access private + */ + var $explicit_key_length = false; + + /** + * The Key Schedule + * + * @see _setup() + * @var Array + * @access private + */ + var $w; + + /** + * The Inverse Key Schedule + * + * @see _setup() + * @var Array + * @access private + */ + var $dw; + + /** + * The Block Length + * + * @see setBlockLength() + * @var Integer + * @access private + * @internal The max value is 32, the min value is 16. All valid values are multiples of 4. Exists in conjunction with + * $Nb because we need this value and not $Nb to pad strings appropriately. + */ + var $block_size = 16; + + /** + * The Block Length divided by 32 + * + * @see setBlockLength() + * @var Integer + * @access private + * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size + * because the encryption / decryption / key schedule creation requires this number and not $block_size. We could + * derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu + * of that, we'll just precompute it once. + * + */ + var $Nb = 4; + + /** + * The Key Length + * + * @see setKeyLength() + * @var Integer + * @access private + * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $key_size + * because the encryption / decryption / key schedule creation requires this number and not $key_size. We could + * derive this from $key_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu + * of that, we'll just precompute it once. + */ + var $key_size = 16; + + /** + * The Key Length divided by 32 + * + * @see setKeyLength() + * @var Integer + * @access private + * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4 + */ + var $Nk = 4; + + /** + * The Number of Rounds + * + * @var Integer + * @access private + * @internal The max value is 14, the min value is 10. + */ + var $Nr; + + /** + * Shift offsets + * + * @var Array + * @access private + */ + var $c; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t0; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t1; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t2; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t3; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt0; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt1; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt2; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt3; + + /** + * Is the mode one that is paddable? + * + * @see Crypt_Rijndael::Crypt_Rijndael() + * @var Boolean + * @access private + */ + var $paddable = false; + + /** + * Encryption buffer for CTR, OFB and CFB modes + * + * @see Crypt_Rijndael::encrypt() + * @var String + * @access private + */ + var $enbuffer = array('encrypted' => '', 'xor' => ''); + + /** + * Decryption buffer for CTR, OFB and CFB modes + * + * @see Crypt_Rijndael::decrypt() + * @var String + * @access private + */ + var $debuffer = array('ciphertext' => ''); + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_RIJNDAEL_MODE_ECB or CRYPT_RIJNDAEL_MODE_CBC. If not explictly set, CRYPT_RIJNDAEL_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_Rijndael + * @access public + */ + function Crypt_Rijndael($mode = CRYPT_RIJNDAEL_MODE_CBC) + { + switch ($mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + case CRYPT_RIJNDAEL_MODE_CBC: + $this->paddable = true; + $this->mode = $mode; + break; + case CRYPT_RIJNDAEL_MODE_CTR: + case CRYPT_RIJNDAEL_MODE_CFB: + case CRYPT_RIJNDAEL_MODE_OFB: + $this->mode = $mode; + break; + default: + $this->paddable = true; + $this->mode = CRYPT_RIJNDAEL_MODE_CBC; + } + + $t3 = &$this->t3; + $t2 = &$this->t2; + $t1 = &$this->t1; + $t0 = &$this->t0; + + $dt3 = &$this->dt3; + $dt2 = &$this->dt2; + $dt1 = &$this->dt1; + $dt0 = &$this->dt0; + + // according to (section 5.2.1), + // precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so + // those are the names we'll use. + $t3 = array( + 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491, + 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC, + 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB, + 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B, + 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83, + 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A, + 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F, + 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA, + 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B, + 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713, + 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6, + 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85, + 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411, + 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B, + 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1, + 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF, + 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E, + 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6, + 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B, + 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD, + 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8, + 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2, + 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049, + 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810, + 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197, + 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F, + 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C, + 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927, + 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733, + 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5, + 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0, + 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C + ); + + $dt3 = array( + 0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B, + 0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5, + 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B, + 0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E, + 0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D, + 0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9, + 0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66, + 0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED, + 0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4, + 0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD, + 0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60, + 0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79, + 0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C, + 0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24, + 0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C, + 0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814, + 0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B, + 0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084, + 0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077, + 0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22, + 0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F, + 0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582, + 0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB, + 0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF, + 0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035, + 0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17, + 0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46, + 0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D, + 0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A, + 0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678, + 0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF, + 0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0 + ); + + for ($i = 0; $i < 256; $i++) { + $t2[$i << 8] = (($t3[$i] << 8) & 0xFFFFFF00) | (($t3[$i] >> 24) & 0x000000FF); + $t1[$i << 16] = (($t3[$i] << 16) & 0xFFFF0000) | (($t3[$i] >> 16) & 0x0000FFFF); + $t0[$i << 24] = (($t3[$i] << 24) & 0xFF000000) | (($t3[$i] >> 8) & 0x00FFFFFF); + + $dt2[$i << 8] = (($this->dt3[$i] << 8) & 0xFFFFFF00) | (($dt3[$i] >> 24) & 0x000000FF); + $dt1[$i << 16] = (($this->dt3[$i] << 16) & 0xFFFF0000) | (($dt3[$i] >> 16) & 0x0000FFFF); + $dt0[$i << 24] = (($this->dt3[$i] << 24) & 0xFF000000) | (($dt3[$i] >> 8) & 0x00FFFFFF); + } + } + + /** + * Sets the key. + * + * Keys can be of any length. Rijndael, itself, requires the use of a key that's between 128-bits and 256-bits long and + * whose length is a multiple of 32. If the key is less than 256-bits and the key length isn't set, we round the length + * up to the closest valid key length, padding $key with null bytes. If the key is more than 256-bits, we trim the + * excess bits. + * + * If the key is not explicitly set, it'll be assumed to be all null bytes. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->key = $key; + $this->changed = true; + } + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, $this->block_size), $this->block_size, chr(0)); + } + + /** + * Sets the key length + * + * Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to + * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount. + * + * @access public + * @param Integer $length + */ + function setKeyLength($length) + { + $length >>= 5; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + + $this->explicit_key_length = true; + $this->changed = true; + } + + /** + * Sets the password. + * + * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows: + * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2}: + * $hash, $salt, $count + * Set $dkLen by calling setKeyLength() + * + * @param String $password + * @param optional String $method + * @access public + */ + function setPassword($password, $method = 'pbkdf2') + { + $key = ''; + + switch ($method) { + default: // 'pbkdf2' + list(, , $hash, $salt, $count) = func_get_args(); + if (!isset($hash)) { + $hash = 'sha1'; + } + // WPA and WPA use the SSID as the salt + if (!isset($salt)) { + $salt = 'phpseclib/salt'; + } + // RFC2898#section-4.2 uses 1,000 iterations by default + // WPA and WPA2 use 4,096. + if (!isset($count)) { + $count = 1000; + } + + if (!class_exists('Crypt_Hash')) { + require_once('Crypt/Hash.php'); + } + + $i = 1; + while (strlen($key) < $this->key_size) { // $dkLen == $this->key_size + //$dk.= $this->_pbkdf($password, $salt, $count, $i++); + $hmac = new Crypt_Hash(); + $hmac->setHash($hash); + $hmac->setKey($password); + $f = $u = $hmac->hash($salt . pack('N', $i++)); + for ($j = 2; $j <= $count; $j++) { + $u = $hmac->hash($u); + $f^= $u; + } + $key.= $f; + } + } + + $this->setKey(substr($key, 0, $this->key_size)); + } + + /** + * Sets the block length + * + * Valid block lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to + * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount. + * + * @access public + * @param Integer $length + */ + function setBlockLength($length) + { + $length >>= 5; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nb = $length; + $this->block_size = $length << 2; + $this->changed = true; + } + + /** + * Generate CTR XOR encryption key + * + * Encrypt the output of this and XOR it against the ciphertext / plaintext to get the + * plaintext / ciphertext in CTR mode. + * + * @see Crypt_Rijndael::decrypt() + * @see Crypt_Rijndael::encrypt() + * @access public + * @param Integer $length + * @param String $iv + */ + function _generate_xor($length, &$iv) + { + $xor = ''; + $block_size = $this->block_size; + $num_blocks = floor(($length + ($block_size - 1)) / $block_size); + for ($i = 0; $i < $num_blocks; $i++) { + $xor.= $iv; + for ($j = 4; $j <= $block_size; $j+=4) { + $temp = substr($iv, -$j, 4); + switch ($temp) { + case "\xFF\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x00\x00\x00\x00", -$j, 4); + break; + case "\x7F\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x80\x00\x00\x00", -$j, 4); + break 2; + default: + extract(unpack('Ncount', $temp)); + $iv = substr_replace($iv, pack('N', $count + 1), -$j, 4); + break 2; + } + } + } + + return $xor; + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other Rjindael + * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's + * necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_Rijndael::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + $this->_setup(); + if ($this->paddable) { + $plaintext = $this->_pad($plaintext); + } + + $block_size = $this->block_size; + $buffer = &$this->enbuffer; + $continuousBuffer = $this->continuousBuffer; + $ciphertext = ''; + switch ($this->mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $block_size)); + } + break; + case CRYPT_RIJNDAEL_MODE_CBC: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + $block = $this->_encryptBlock($block ^ $xor); + $xor = $block; + $ciphertext.= $block; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + break; + case CRYPT_RIJNDAEL_MODE_CTR: + $xor = $this->encryptIV; + if (!empty($buffer['encrypted'])) { + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + $buffer['encrypted'].= $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + $key = $this->_string_shift($buffer['encrypted'], $block_size); + $ciphertext.= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + $key = $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + $ciphertext.= $block ^ $key; + } + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + if ($start = strlen($plaintext) % $block_size) { + $buffer['encrypted'] = substr($key, $start) . $buffer['encrypted']; + } + } + break; + case CRYPT_RIJNDAEL_MODE_CFB: + if (!empty($buffer['xor'])) { + $ciphertext = $plaintext ^ $buffer['xor']; + $iv = $buffer['encrypted'] . $ciphertext; + $start = strlen($ciphertext); + $buffer['encrypted'].= $ciphertext; + $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext)); + } else { + $ciphertext = ''; + $iv = $this->encryptIV; + $start = 0; + } + + for ($i = $start; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + $xor = $this->_encryptBlock($iv); + $iv = $block ^ $xor; + if ($continuousBuffer && strlen($iv) != $block_size) { + $buffer = array( + 'encrypted' => $iv, + 'xor' => substr($xor, strlen($iv)) + ); + } + $ciphertext.= $iv; + } + + if ($this->continuousBuffer) { + $this->encryptIV = $iv; + } + break; + case CRYPT_RIJNDAEL_MODE_OFB: + $xor = $this->encryptIV; + if (strlen($buffer)) { + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $xor = $this->_encryptBlock($xor); + $buffer.= $xor; + $key = $this->_string_shift($buffer, $block_size); + $ciphertext.= substr($plaintext, $i, $block_size) ^ $key; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $xor = $this->_encryptBlock($xor); + $ciphertext.= substr($plaintext, $i, $block_size) ^ $xor; + } + $key = $xor; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + if ($start = strlen($plaintext) % $block_size) { + $buffer = substr($key, $start) . $buffer; + } + } + } + + return $ciphertext; + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until + * it is. + * + * @see Crypt_Rijndael::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + $this->_setup(); + + if ($this->paddable) { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, strlen($ciphertext) + ($this->block_size - strlen($ciphertext) % $this->block_size) % $this->block_size, chr(0)); + } + + $block_size = $this->block_size; + $buffer = &$this->debuffer; + $continuousBuffer = $this->continuousBuffer; + $plaintext = ''; + switch ($this->mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $block_size)); + } + break; + case CRYPT_RIJNDAEL_MODE_CBC: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + $plaintext.= $this->_decryptBlock($block) ^ $xor; + $xor = $block; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + break; + case CRYPT_RIJNDAEL_MODE_CTR: + $xor = $this->decryptIV; + if (!empty($buffer['ciphertext'])) { + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + $buffer['ciphertext'].= $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + $key = $this->_string_shift($buffer['ciphertext'], $block_size); + $plaintext.= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + $key = $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + $plaintext.= $block ^ $key; + } + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + if ($start = strlen($ciphertext) % $block_size) { + $buffer['ciphertext'] = substr($key, $start) . $buffer['encrypted']; + } + } + break; + case CRYPT_RIJNDAEL_MODE_CFB: + if (!empty($buffer['ciphertext'])) { + $plaintext = $ciphertext ^ substr($this->decryptIV, strlen($buffer['ciphertext'])); + $buffer['ciphertext'].= substr($ciphertext, 0, strlen($plaintext)); + if (strlen($buffer['ciphertext']) == $block_size) { + $xor = $this->_encryptBlock($buffer['ciphertext']); + $buffer['ciphertext'] = ''; + } + $start = strlen($plaintext); + $block = $this->decryptIV; + } else { + $plaintext = ''; + $xor = $this->_encryptBlock($this->decryptIV); + $start = 0; + } + + for ($i = $start; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + $plaintext.= $block ^ $xor; + if ($continuousBuffer && strlen($block) != $block_size) { + $buffer['ciphertext'].= $block; + $block = $xor; + } else if (strlen($block) == $block_size) { + $xor = $this->_encryptBlock($block); + } + } + if ($this->continuousBuffer) { + $this->decryptIV = $block; + } + break; + case CRYPT_RIJNDAEL_MODE_OFB: + $xor = $this->decryptIV; + if (strlen($buffer)) { + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $xor = $this->_encryptBlock($xor); + $buffer.= $xor; + $key = $this->_string_shift($buffer, $block_size); + $plaintext.= substr($ciphertext, $i, $block_size) ^ $key; + } + } else { + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $xor = $this->_encryptBlock($xor); + $plaintext.= substr($ciphertext, $i, $block_size) ^ $xor; + } + $key = $xor; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + if ($start = strlen($ciphertext) % $block_size) { + $buffer = substr($key, $start) . $buffer; + } + } + } + + return $this->paddable ? $this->_unpad($plaintext) : $plaintext; + } + + /** + * Encrypts a block + * + * @access private + * @param String $in + * @return String + */ + function _encryptBlock($in) + { + $state = array(); + $words = unpack('N*word', $in); + + $w = $this->w; + $t0 = $this->t0; + $t1 = $this->t1; + $t2 = $this->t2; + $t3 = $this->t3; + $Nb = $this->Nb; + $Nr = $this->Nr; + $c = $this->c; + + // addRoundKey + $i = 0; + foreach ($words as $word) { + $state[] = $word ^ $w[0][$i++]; + } + + // fips-197.pdf#page=19, "Figure 5. Pseudo Code for the Cipher", states that this loop has four components - + // subBytes, shiftRows, mixColumns, and addRoundKey. fips-197.pdf#page=30, "Implementation Suggestions Regarding + // Various Platforms" suggests that performs enhanced implementations are described in Rijndael-ammended.pdf. + // Rijndael-ammended.pdf#page=20, "Implementation aspects / 32-bit processor", discusses such an optimization. + // Unfortunately, the description given there is not quite correct. Per aes.spec.v316.pdf#page=19 [1], + // equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well. + + // [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf + $temp = array(); + for ($round = 1; $round < $Nr; $round++) { + $i = 0; // $c[0] == 0 + $j = $c[1]; + $k = $c[2]; + $l = $c[3]; + + while ($i < $this->Nb) { + $temp[$i] = $t0[$state[$i] & 0xFF000000] ^ + $t1[$state[$j] & 0x00FF0000] ^ + $t2[$state[$k] & 0x0000FF00] ^ + $t3[$state[$l] & 0x000000FF] ^ + $w[$round][$i]; + $i++; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + + for ($i = 0; $i < $Nb; $i++) { + $state[$i] = $temp[$i]; + } + } + + // subWord + for ($i = 0; $i < $Nb; $i++) { + $state[$i] = $this->_subWord($state[$i]); + } + + // shiftRows + addRoundKey + $i = 0; // $c[0] == 0 + $j = $c[1]; + $k = $c[2]; + $l = $c[3]; + while ($i < $this->Nb) { + $temp[$i] = ($state[$i] & 0xFF000000) ^ + ($state[$j] & 0x00FF0000) ^ + ($state[$k] & 0x0000FF00) ^ + ($state[$l] & 0x000000FF) ^ + $w[$Nr][$i]; + $i++; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + $state = $temp; + + array_unshift($state, 'N*'); + + return call_user_func_array('pack', $state); + } + + /** + * Decrypts a block + * + * @access private + * @param String $in + * @return String + */ + function _decryptBlock($in) + { + $state = array(); + $words = unpack('N*word', $in); + + $num_states = count($state); + $dw = $this->dw; + $dt0 = $this->dt0; + $dt1 = $this->dt1; + $dt2 = $this->dt2; + $dt3 = $this->dt3; + $Nb = $this->Nb; + $Nr = $this->Nr; + $c = $this->c; + + // addRoundKey + $i = 0; + foreach ($words as $word) { + $state[] = $word ^ $dw[$Nr][$i++]; + } + + $temp = array(); + for ($round = $Nr - 1; $round > 0; $round--) { + $i = 0; // $c[0] == 0 + $j = $Nb - $c[1]; + $k = $Nb - $c[2]; + $l = $Nb - $c[3]; + + while ($i < $Nb) { + $temp[$i] = $dt0[$state[$i] & 0xFF000000] ^ + $dt1[$state[$j] & 0x00FF0000] ^ + $dt2[$state[$k] & 0x0000FF00] ^ + $dt3[$state[$l] & 0x000000FF] ^ + $dw[$round][$i]; + $i++; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + + for ($i = 0; $i < $Nb; $i++) { + $state[$i] = $temp[$i]; + } + } + + // invShiftRows + invSubWord + addRoundKey + $i = 0; // $c[0] == 0 + $j = $Nb - $c[1]; + $k = $Nb - $c[2]; + $l = $Nb - $c[3]; + + while ($i < $Nb) { + $temp[$i] = $dw[0][$i] ^ + $this->_invSubWord(($state[$i] & 0xFF000000) | + ($state[$j] & 0x00FF0000) | + ($state[$k] & 0x0000FF00) | + ($state[$l] & 0x000000FF)); + $i++; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + + $state = $temp; + + array_unshift($state, 'N*'); + + return call_user_func_array('pack', $state); + } + + /** + * Setup Rijndael + * + * Validates all the variables and calculates $Nr - the number of rounds that need to be performed - and $w - the key + * key schedule. + * + * @access private + */ + function _setup() + { + // Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field. + // See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse + static $rcon = array(0, + 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, + 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000, + 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000, + 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000, + 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000, + 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000 + ); + + if (!$this->changed) { + return; + } + + if (!$this->explicit_key_length) { + // we do >> 2, here, and not >> 5, as we do above, since strlen($this->key) tells us the number of bytes - not bits + $length = strlen($this->key) >> 2; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + } + + $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0)); + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, chr(0)); + + // see Rijndael-ammended.pdf#page=44 + $this->Nr = max($this->Nk, $this->Nb) + 6; + + // shift offsets for Nb = 5, 7 are defined in Rijndael-ammended.pdf#page=44, + // "Table 8: Shift offsets in Shiftrow for the alternative block lengths" + // shift offsets for Nb = 4, 6, 8 are defined in Rijndael-ammended.pdf#page=14, + // "Table 2: Shift offsets for different block lengths" + switch ($this->Nb) { + case 4: + case 5: + case 6: + $this->c = array(0, 1, 2, 3); + break; + case 7: + $this->c = array(0, 1, 2, 4); + break; + case 8: + $this->c = array(0, 1, 3, 4); + } + + $key = $this->key; + + $w = array_values(unpack('N*words', $key)); + + $length = $this->Nb * ($this->Nr + 1); + for ($i = $this->Nk; $i < $length; $i++) { + $temp = $w[$i - 1]; + if ($i % $this->Nk == 0) { + // according to , "the size of an integer is platform-dependent". + // on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine, + // 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and' + // with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is. + $temp = (($temp << 8) & 0xFFFFFF00) | (($temp >> 24) & 0x000000FF); // rotWord + $temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk]; + } else if ($this->Nk > 6 && $i % $this->Nk == 4) { + $temp = $this->_subWord($temp); + } + $w[$i] = $w[$i - $this->Nk] ^ $temp; + } + + // convert the key schedule from a vector of $Nb * ($Nr + 1) length to a matrix with $Nr + 1 rows and $Nb columns + // and generate the inverse key schedule. more specifically, + // according to (section 5.3.3), + // "The key expansion for the Inverse Cipher is defined as follows: + // 1. Apply the Key Expansion. + // 2. Apply InvMixColumn to all Round Keys except the first and the last one." + // also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher" + $temp = array(); + for ($i = $row = $col = 0; $i < $length; $i++, $col++) { + if ($col == $this->Nb) { + if ($row == 0) { + $this->dw[0] = $this->w[0]; + } else { + // subWord + invMixColumn + invSubWord = invMixColumn + $j = 0; + while ($j < $this->Nb) { + $dw = $this->_subWord($this->w[$row][$j]); + $temp[$j] = $this->dt0[$dw & 0xFF000000] ^ + $this->dt1[$dw & 0x00FF0000] ^ + $this->dt2[$dw & 0x0000FF00] ^ + $this->dt3[$dw & 0x000000FF]; + $j++; + } + $this->dw[$row] = $temp; + } + + $col = 0; + $row++; + } + $this->w[$row][$col] = $w[$i]; + } + + $this->dw[$row] = $this->w[$row]; + + $this->changed = false; + } + + /** + * Performs S-Box substitutions + * + * @access private + */ + function _subWord($word) + { + static $sbox0, $sbox1, $sbox2, $sbox3; + + if (empty($sbox0)) { + $sbox0 = array( + 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 + ); + + $sbox1 = array(); + $sbox2 = array(); + $sbox3 = array(); + + for ($i = 0; $i < 256; $i++) { + $sbox1[$i << 8] = $sbox0[$i] << 8; + $sbox2[$i << 16] = $sbox0[$i] << 16; + $sbox3[$i << 24] = $sbox0[$i] << 24; + } + } + + return $sbox0[$word & 0x000000FF] | + $sbox1[$word & 0x0000FF00] | + $sbox2[$word & 0x00FF0000] | + $sbox3[$word & 0xFF000000]; + } + + /** + * Performs inverse S-Box substitutions + * + * @access private + */ + function _invSubWord($word) + { + static $sbox0, $sbox1, $sbox2, $sbox3; + + if (empty($sbox0)) { + $sbox0 = array( + 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D + ); + + $sbox1 = array(); + $sbox2 = array(); + $sbox3 = array(); + + for ($i = 0; $i < 256; $i++) { + $sbox1[$i << 8] = $sbox0[$i] << 8; + $sbox2[$i << 16] = $sbox0[$i] << 16; + $sbox3[$i << 24] = $sbox0[$i] << 24; + } + } + + return $sbox0[$word & 0x000000FF] | + $sbox1[$word & 0x0000FF00] | + $sbox2[$word & 0x00FF0000] | + $sbox3[$word & 0xFF000000]; + } + + /** + * Pad "packets". + * + * Rijndael works by encrypting between sixteen and thirty-two bytes at a time, provided that number is also a multiple + * of four. If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to + * pad the input so that it is of the proper length. + * + * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH, + * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping + * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is + * transmitted separately) + * + * @see Crypt_Rijndael::disablePadding() + * @access public + */ + function enablePadding() + { + $this->padding = true; + } + + /** + * Do not pad packets. + * + * @see Crypt_Rijndael::enablePadding() + * @access public + */ + function disablePadding() + { + $this->padding = false; + } + + /** + * Pads a string + * + * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize. + * $block_size - (strlen($text) % $block_size) bytes are added, each of which is equal to + * chr($block_size - (strlen($text) % $block_size) + * + * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless + * and padding will, hence forth, be enabled. + * + * @see Crypt_Rijndael::_unpad() + * @access private + */ + function _pad($text) + { + $length = strlen($text); + + if (!$this->padding) { + if ($length % $this->block_size == 0) { + return $text; + } else { + user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})", E_USER_NOTICE); + $this->padding = true; + } + } + + $pad = $this->block_size - ($length % $this->block_size); + + return str_pad($text, $length + $pad, chr($pad)); + } + + /** + * Unpads a string. + * + * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong + * and false will be returned. + * + * @see Crypt_Rijndael::_pad() + * @access private + */ + function _unpad($text) + { + if (!$this->padding) { + return $text; + } + + $length = ord($text[strlen($text) - 1]); + + if (!$length || $length > $this->block_size) { + return false; + } + + return substr($text, 0, -$length); + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $rijndael->encrypt(substr($plaintext, 0, 16)); + * echo $rijndael->encrypt(substr($plaintext, 16, 16)); + * + * + * echo $rijndael->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $rijndael->encrypt(substr($plaintext, 0, 16)); + * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16))); + * + * + * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_Rijndael() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_Rijndael::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + $this->continuousBuffer = false; + $this->encryptIV = $this->iv; + $this->decryptIV = $this->iv; + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } +} + +// vim: ts=4:sw=4:et: +// vim6: fdl=1: +?> diff --git a/sources/libraries/plugin_interface.lib.php b/sources/libraries/plugin_interface.lib.php new file mode 100644 index 0000000..be7e593 --- /dev/null +++ b/sources/libraries/plugin_interface.lib.php @@ -0,0 +1,507 @@ +'; + $default = PMA_pluginGetDefault($section, $cfgname); + foreach ($list as $plugin) { + $plugin_name = strtolower(substr(get_class($plugin), strlen($section))); + $ret .= 'getProperties(); + $text = null; + if ($properties != null) { + $text = $properties->getText(); + } + $ret .= ' value="' . $plugin_name . '">' + . PMA_getString($text) + . '' . "\n"; + } + $ret .= '' . "\n"; + + // Whether each plugin has to be saved as a file + foreach ($list as $plugin) { + $plugin_name = strtolower(substr(get_class($plugin), strlen($section))); + $ret .= ''. "\n"; + } + + 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 &$propertyGroup options property main group instance + * @param boolean $is_subgroup if this group is a subgroup + * + * @return string table row with option + */ +function PMA_pluginGetOneOption( + $section, + $plugin_name, + &$propertyGroup, + $is_subgroup = false +) { + $ret = "\n"; + + if (! $is_subgroup) { + // for subgroup headers + if (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 .= '

      ' . PMA_getString($text) . '

      '; + } + $ret .= '
        '; + } + } + + if (! isset($properties)) { + $not_subgroup_header = true; + if (method_exists($propertyGroup, 'getProperties')) { + $properties = $propertyGroup->getProperties(); + } + } + + if (isset($properties)) { + foreach ($properties as $propertyItem) { + $property_class = get_class($propertyItem); + // if the property is a subgroup, we deal with it recursively + if (strpos($property_class, "Subgroup")) { + // for subgroups + // each subgroup can have a header, which may also be a form element + $subgroup_header = $propertyItem->getSubgroupHeader(); + if (isset($subgroup_header)) { + $ret .= PMA_pluginGetOneOption( + $section, + $plugin_name, + $subgroup_header + ); + } + + $ret .= '
      • getName() . '">'; + } else { + $ret .= '>'; + } + + $ret .= PMA_pluginGetOneOption( + $section, + $plugin_name, + $propertyItem, + true + ); + } else { + // single property item + switch ($property_class) { + case "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 "DocPropertyItem": + echo "DocPropertyItem"; + break; + case "HiddenPropertyItem": + $ret .= '
      • '; + break; + case "MessageOnlyPropertyItem": + $ret .= '
      • ' . "\n"; + $ret .= '

        ' . PMA_getString($propertyItem->getText()) . '

        '; + break; + case "RadioPropertyItem": + $default = PMA_pluginGetDefault( + $section, + $plugin_name . '_' . $propertyItem->getName() + ); + foreach ($propertyItem->getValues() as $key => $val) { + $ret .= '
      • ' + . PMA_getString($val) . '
      • '; + } + break; + case "SelectPropertyItem": + $ret .= '
      • ' . "\n"; + $ret .= ''; + $ret .= ''; + break; + case "TextPropertyItem": + case "NumberPropertyItem": + $ret .= '
      • ' . "\n"; + $ret .= ''; + $ret .= 'getSize() != null + ? ' size="' . $propertyItem->getSize() . '"' + : '') + . ($propertyItem->getLen() != null + ? ' maxlength="' . $propertyItem->getLen() . '"' + : '') + . ' />'; + break; + default:; + } + } + } + } + + 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 .= PMA_Util::showMySQLDocu( + $doc[1], + false, + $doc[2] + ); + } elseif (count($doc) == 1) { + $ret .= PMA_Util::showDocu('faq', $doc[0]); + } else { + $ret .= PMA_Util::showMySQLDocu( + $doc[1] + ); + } + } + } + + // Close the list element after $doc link is displayed + if (isset($property_class)) { + if ($property_class == 'BoolPropertyItem' + || $property_class == 'MessageOnlyPropertyItem' + || $property_class == 'SelectPropertyItem' + || $property_class == 'TextPropertyItem' + ) { + $ret .= ''; + } + } + $ret .= "\n"; + 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 + */ +function PMA_pluginGetOptions($section, &$list) +{ + $ret = ''; + // Options for plugins that support them + foreach ($list as $plugin) { + $properties = $plugin->getProperties(); + if ($properties != null) { + $text = $properties->getText(); + $options = $properties->getOptions(); + } + + $plugin_name = strtolower(substr(get_class($plugin), strlen($section))); + $ret .= '
      '; + $ret .= '

      ' . PMA_getString($text) . '

      '; + + $no_options = true; + if ($options != null && count($options) > 0) { + foreach ($options->getProperties() + as $propertyMainGroup + ) { + // check for hidden properties + $no_options = true; + foreach ($propertyMainGroup->getProperties() as $propertyItem) { + if (strcmp("HiddenPropertyItem", get_class($propertyItem))) { + $no_options = false; + break; + } + } + + $ret .= PMA_pluginGetOneOption( + $section, + $plugin_name, + $propertyMainGroup + ); + } + } + + if ($no_options) { + $ret .= '

      ' . __('This format has no options') . '

      '; + } + $ret .= '
      '; + } + return $ret; +} diff --git a/sources/libraries/plugins/AuthenticationPlugin.class.php b/sources/libraries/plugins/AuthenticationPlugin.class.php new file mode 100644 index 0000000..3ddf55e --- /dev/null +++ b/sources/libraries/plugins/AuthenticationPlugin.class.php @@ -0,0 +1,51 @@ + authentication failed + * + * @return boolean + */ + abstract public function authFails(); +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/ExportPlugin.class.php b/sources/libraries/plugins/ExportPlugin.class.php new file mode 100644 index 0000000..ecfde15 --- /dev/null +++ b/sources/libraries/plugins/ExportPlugin.class.php @@ -0,0 +1,206 @@ +properties; + } + + /** + * Sets the export plugins properties and is implemented by each export + * plugin + * + * @return void + */ + abstract protected function setProperties(); +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/ImportPlugin.class.php b/sources/libraries/plugins/ImportPlugin.class.php new file mode 100644 index 0000000..4ef4deb --- /dev/null +++ b/sources/libraries/plugins/ImportPlugin.class.php @@ -0,0 +1,59 @@ +properties; + } + + /** + * Sets the export plugins properties and is implemented by each import + * plugin + * + * @return void + */ + abstract protected function setProperties(); +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/PluginManager.class.php b/sources/libraries/plugins/PluginManager.class.php new file mode 100644 index 0000000..3485981 --- /dev/null +++ b/sources/libraries/plugins/PluginManager.class.php @@ -0,0 +1,132 @@ +_storage = new SplObjectStorage(); + } + + /** + * Attaches an SplObserver so that it can be notified of updates + * + * @param SplObserver $observer The SplObserver to attach + * + * @return void + */ + function attach (SplObserver $observer ) + { + $this->_storage->attach($observer); + } + + /** + * Detaches an observer from the subject to no longer notify it of updates + * + * @param SplObserver $observer The SplObserver to detach + * + * @return void + */ + function detach (SplObserver $observer) + { + $this->_storage->detach($observer); + } + + /** + * It is called after setStatus() was run by a certain plugin, and has + * the role of sending a notification to all of the plugins in $_storage, + * by calling the update() method for each of them. + * + * @todo implement + * @return void + */ + function notify () + { + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the list with all the plugins that attach to it + * + * @return SplObjectStorage + */ + public function getStorage() + { + return $this->_storage; + } + + /** + * Setter for $_storage + * + * @param SplObjectStorage $_storage the list with all the plugins that + * attach to it + * + * @return void + */ + public function setStorage($_storage) + { + $this->_storage = $_storage; + } + + /** + * Gets the information about the current plugin state + * It is called by all the plugins in $_storage in their update() method + * + * @return string + */ + public function getStatus() + { + return $this->_status; + } + + /** + * Setter for $_status + * If a plugin changes its status, this has to be remembered in order to + * notify the rest of the plugins that they should update + * + * @param string $_status contains information about the current plugin state + * + * @return void + */ + public function setStatus($_status) + { + $this->_status = $_status; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/PluginObserver.class.php b/sources/libraries/plugins/PluginObserver.class.php new file mode 100644 index 0000000..82a2fdc --- /dev/null +++ b/sources/libraries/plugins/PluginObserver.class.php @@ -0,0 +1,91 @@ +_pluginManager = $pluginManager; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * TODO Declare this function abstract, removing its body, + * as soon as we drop support for PHP 5.3.x. + * See bug #3625 + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + * + * @throws Exception + */ + public function update (SplSubject $subject) + { + throw new Exception( + 'PluginObserver::update must be overridden in child classes.' + ); + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the PluginManager instance that contains the list with all the + * plugins that attached to it + * + * @return PluginManager + */ + public function getPluginManager() + { + return $this->_pluginManager; + } + + /** + * Setter for $_pluginManager + * + * @param PluginManager $_pluginManager the private instance that it will + * attach to + * + * @return void + */ + public function setPluginManager($_pluginManager) + { + $this->_pluginManager = $_pluginManager; + } +} +?> diff --git a/sources/libraries/plugins/TransformationsInterface.int.php b/sources/libraries/plugins/TransformationsInterface.int.php new file mode 100644 index 0000000..1bcb8af --- /dev/null +++ b/sources/libraries/plugins/TransformationsInterface.int.php @@ -0,0 +1,49 @@ + diff --git a/sources/libraries/plugins/TransformationsPlugin.class.php b/sources/libraries/plugins/TransformationsPlugin.class.php new file mode 100644 index 0000000..02ed816 --- /dev/null +++ b/sources/libraries/plugins/TransformationsPlugin.class.php @@ -0,0 +1,51 @@ + diff --git a/sources/libraries/plugins/UploadInterface.int.php b/sources/libraries/plugins/UploadInterface.int.php new file mode 100644 index 0000000..ab660d6 --- /dev/null +++ b/sources/libraries/plugins/UploadInterface.int.php @@ -0,0 +1,36 @@ +upload plugins + * + * @package PhpMyAdmin + */ +if (! defined('PHPMYADMIN')) { + exit; +} + +/** + * 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); +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/auth/AuthenticationConfig.class.php b/sources/libraries/plugins/auth/AuthenticationConfig.class.php new file mode 100644 index 0000000..6f7e4e4 --- /dev/null +++ b/sources/libraries/plugins/auth/AuthenticationConfig.class.php @@ -0,0 +1,174 @@ + authentication failed + * + * @global string the MySQL error message PHP returns + * @global string the connection type (persistent or not) + * @global string the MySQL server port to use + * @global string the MySQL socket port to use + * @global array the current server settings + * @global string the font face to use in case of failure + * @global string the default font size to use in case of failure + * @global string the big font size to use in case of failure + * @global boolean tell the "PMA_mysqlDie()" function headers have been + * sent + * + * @return boolean always true (no return indeed) + */ + public function authFails() + { + $conn_error = $GLOBALS['dbi']->getError(); + if (! $conn_error) { + $conn_error = __('Cannot connect: invalid settings.'); + } + + /* HTML header */ + $response = PMA_Response::getInstance(); + $response->getFooter()->setMinimal(); + $header = $response->getHeader(); + $header->setBodyId('loginform'); + $header->setTitle(__('Access denied')); + $header->disableMenu(); + 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 + include_once './libraries/select_server.lib.php'; + 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 PMA_Util::mysqlDie( + $conn_error, '', true, '', false + ); + } + $GLOBALS['error_handler']->dispUserErrors(); + echo '
      ' . "\n"; + echo '' + . __('Retry to connect') + . '' . "\n"; + echo '
      ' . "\n"; + echo PMA_selectServer(true, true); + echo '
      ' . "\n"; + if (!defined('TESTSUITE')) { + exit; + } + return true; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } +} diff --git a/sources/libraries/plugins/auth/AuthenticationCookie.class.php b/sources/libraries/plugins/auth/AuthenticationCookie.class.php new file mode 100644 index 0000000..1ee8d59 --- /dev/null +++ b/sources/libraries/plugins/auth/AuthenticationCookie.class.php @@ -0,0 +1,800 @@ +setCookie( + 'pma_mcrypt_iv', + base64_encode($iv) + ); + } +} + +/** + * Handles the cookie authentication method + * + * @package PhpMyAdmin-Authentication + */ +class AuthenticationCookie extends AuthenticationPlugin +{ + /** + * Displays authentication form + * + * this function MUST exit/quit the application + * + * @global string $conn_error the last connection error + * + * @return boolean|void + */ + public function auth() + { + global $conn_error; + + $response = PMA_Response::getInstance(); + if ($response->isAjax()) { + $response->isSuccess(false); + + $login_link = '

      [ ' . + sprintf( + '', + $GLOBALS['cfg']['PmaAbsoluteUri'], + __('Log in') + ) + . ' ]'; + + if (! empty($conn_error)) { + + $conn_error .= $login_link; + + $response->addJSON( + 'message', + PMA_Message::error( + $conn_error + ) + ); + } else { + $response->addJSON( + 'message', + PMA_Message::error( + __('Your session has expired. Please log in again.') . + $login_link + ) + ); + } + if (defined('TESTSUITE')) { + return true; + } else { + exit; + } + } + + /* Perform logout to custom URL */ + if (! empty($_REQUEST['old_usr']) + && ! empty($GLOBALS['cfg']['Server']['LogoutURL']) + ) { + PMA_sendHeaderLocation($GLOBALS['cfg']['Server']['LogoutURL']); + 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 = $GLOBALS['PHP_AUTH_USER']; + $default_server = $GLOBALS['pma_auth_server']; + $autocomplete = ''; + } else { + $default_user = ''; + $default_server = ''; + // skip the IE autocomplete feature. + $autocomplete = ' autocomplete="off"'; + } + + $response->getFooter()->setMinimal(); + $header = $response->getHeader(); + $header->setBodyId('loginform'); + $header->setTitle('phpMyAdmin'); + $header->disableMenu(); + $header->disableWarnings(); + + if (file_exists(CUSTOM_HEADER_FILE)) { + include CUSTOM_HEADER_FILE; + } + echo ' +
      + +

      '; + echo sprintf( + __('Welcome to %s'), + 'phpMyAdmin' + ); + echo "

      "; + + // Show error message + if (! empty($conn_error)) { + PMA_Message::rawError($conn_error)->display(); + } + + echo "\n"; + + echo "
      "; + // Displays the languages form + if (empty($GLOBALS['cfg']['Lang'])) { + include_once './libraries/display_select_lang.lib.php'; + // use fieldset, don't show doc link + echo PMA_getLanguageSelectorHtml(true, false); + } + echo '
      +
      + + '; + + // BEGIN Swekey Integration + Swekey_login('input_username', 'input_go'); + // END Swekey Integration + + if ($GLOBALS['error_handler']->hasDisplayErrors()) { + echo '
      '; + $GLOBALS['error_handler']->dispErrors(); + echo '
      '; + } + echo '
      '; + if (file_exists(CUSTOM_FOOTER_FILE)) { + include CUSTOM_FOOTER_FILE; + } + if (! defined('TESTSUITE')) { + exit; + } else { + return true; + } + } + + /** + * Gets advanced authentication settings + * + * 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 + * auth() which displays login form + * + * it returns true if all seems ok which usually leads to auth_set_user() + * + * it directly switches to authFails() if user inactivity timout is reached + * + * @todo AllowArbitraryServer on does not imply that the user wants an + * arbitrary server, or? so we should also check if this is filled + * and not only if allowed + * + * @return boolean whether we get authentication settings or not + */ + public function authCheck() + { + global $conn_error; + + // Initialization + /** + * @global $GLOBALS['pma_auth_server'] the user provided server to + * connect to + */ + $GLOBALS['pma_auth_server'] = ''; + + $GLOBALS['PHP_AUTH_USER'] = $GLOBALS['PHP_AUTH_PW'] = ''; + $GLOBALS['from_cookie'] = false; + + // BEGIN Swekey Integration + if (! Swekey_Auth_check()) { + return false; + } + // END Swekey Integration + + if (defined('PMA_CLEAR_COOKIES')) { + foreach ($GLOBALS['cfg']['Servers'] as $key => $val) { + $GLOBALS['PMA_Config']->removeCookie('pmaPass-' . $key); + $GLOBALS['PMA_Config']->removeCookie('pmaServer-' . $key); + $GLOBALS['PMA_Config']->removeCookie('pmaUser-' . $key); + } + return false; + } + + // We already have one correct captcha. + $skip = false; + if ( isset($_SESSION['last_valid_captcha']) + && $_SESSION['last_valid_captcha'] + ) { + $skip = true; + } + + // Verify Captcha if it is required. + if ( !empty($GLOBALS['cfg']['CaptchaLoginPrivateKey']) + && !empty($GLOBALS['cfg']['CaptchaLoginPublicKey']) + && !$skip + ) { + if ( !empty($_POST["recaptcha_challenge_field"]) + && !empty($_POST["recaptcha_response_field"]) + ) { + include_once 'libraries/plugins/auth/recaptchalib.php'; + + // Use private key to verify captcha status. + $resp = recaptcha_check_answer( + $GLOBALS['cfg']['CaptchaLoginPrivateKey'], + $_SERVER["REMOTE_ADDR"], + $_POST["recaptcha_challenge_field"], + $_POST["recaptcha_response_field"] + ); + + // Check if the captcha entered is valid, if not stop the login. + if ( !$resp->is_valid ) { + $conn_error = __('Entered captcha is wrong, try again!'); + $_SESSION['last_valid_captcha'] = false; + return false; + } else { + $_SESSION['last_valid_captcha'] = true; + } + } elseif (! empty($_POST["recaptcha_challenge_field"]) + && empty($_POST["recaptcha_response_field"]) + ) { + $conn_error = __('Please enter correct captcha!'); + return false; + } else { + if (! isset($_SESSION['last_valid_captcha']) + || ! $_SESSION['last_valid_captcha'] + ) { + return false; + } + } + } + + if (! empty($_REQUEST['old_usr'])) { + // The user wants to be logged out + // -> delete his choices that were stored in session + + // according to the PHP manual we should do this before the destroy: + //$_SESSION = array(); + + if (! defined('TESTSUITE')) { + session_destroy(); + } + // -> delete password cookie(s) + if ($GLOBALS['cfg']['LoginCookieDeleteAll']) { + foreach ($GLOBALS['cfg']['Servers'] as $key => $val) { + $GLOBALS['PMA_Config']->removeCookie('pmaPass-' . $key); + if (isset($_COOKIE['pmaPass-' . $key])) { + unset($_COOKIE['pmaPass-' . $key]); + } + } + } else { + $GLOBALS['PMA_Config']->removeCookie( + 'pmaPass-' . $GLOBALS['server'] + ); + if (isset($_COOKIE['pmaPass-' . $GLOBALS['server']])) { + unset($_COOKIE['pmaPass-' . $GLOBALS['server']]); + } + } + } + + if (! empty($_REQUEST['pma_username'])) { + // The user just logged in + $GLOBALS['PHP_AUTH_USER'] = $_REQUEST['pma_username']; + $GLOBALS['PHP_AUTH_PW'] = empty($_REQUEST['pma_password']) + ? '' + : $_REQUEST['pma_password']; + if ($GLOBALS['cfg']['AllowArbitraryServer'] + && isset($_REQUEST['pma_servername']) + ) { + $GLOBALS['pma_auth_server'] = $_REQUEST['pma_servername']; + } + return true; + } + + // At the end, try to set the $GLOBALS['PHP_AUTH_USER'] + // and $GLOBALS['PHP_AUTH_PW'] variables from cookies + + // servername + if ($GLOBALS['cfg']['AllowArbitraryServer'] + && ! empty($_COOKIE['pmaServer-' . $GLOBALS['server']]) + ) { + $GLOBALS['pma_auth_server'] + = $_COOKIE['pmaServer-' . $GLOBALS['server']]; + } + + // username + if (empty($_COOKIE['pmaUser-' . $GLOBALS['server']])) { + return false; + } + + $GLOBALS['PHP_AUTH_USER'] = $this->blowfishDecrypt( + $_COOKIE['pmaUser-' . $GLOBALS['server']], + $this->_getBlowfishSecret() + ); + + // user was never logged in since session start + if (empty($_SESSION['last_access_time'])) { + return false; + } + + // User inactive too long + $last_access_time = time() - $GLOBALS['cfg']['LoginCookieValidity']; + if ($_SESSION['last_access_time'] < $last_access_time + ) { + PMA_Util::cacheUnset('is_create_db_priv', true); + PMA_Util::cacheUnset('is_process_priv', true); + PMA_Util::cacheUnset('is_reload_priv', true); + PMA_Util::cacheUnset('db_to_create', true); + PMA_Util::cacheUnset('dbs_where_create_table_allowed', true); + $GLOBALS['no_activity'] = true; + $this->authFails(); + if (! defined('TESTSUITE')) { + exit; + } else { + return false; + } + } + + // password + if (empty($_COOKIE['pmaPass-' . $GLOBALS['server']])) { + return false; + } + + $GLOBALS['PHP_AUTH_PW'] = $this->blowfishDecrypt( + $_COOKIE['pmaPass-' . $GLOBALS['server']], + $this->_getBlowfishSecret() + ); + + if ($GLOBALS['PHP_AUTH_PW'] == "\xff(blank)") { + $GLOBALS['PHP_AUTH_PW'] = ''; + } + + $GLOBALS['from_cookie'] = true; + + return true; + } + + /** + * Set the user and password after last checkings if required + * + * @return boolean always true + */ + public function authSetUser() + { + global $cfg; + + // Ensures valid authentication mode, 'only_db', bookmark database and + // table names and relation table name are used + if ($cfg['Server']['user'] != $GLOBALS['PHP_AUTH_USER']) { + foreach ($cfg['Servers'] as $idx => $current) { + if ($current['host'] == $cfg['Server']['host'] + && $current['port'] == $cfg['Server']['port'] + && $current['socket'] == $cfg['Server']['socket'] + && $current['ssl'] == $cfg['Server']['ssl'] + && $current['connect_type'] == $cfg['Server']['connect_type'] + && $current['user'] == $GLOBALS['PHP_AUTH_USER'] + ) { + $GLOBALS['server'] = $idx; + $cfg['Server'] = $current; + break; + } + } // end foreach + } // end if + + 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); + } + $cfg['Server']['user'] = $GLOBALS['PHP_AUTH_USER']; + $cfg['Server']['password'] = $GLOBALS['PHP_AUTH_PW']; + + // Avoid showing the password in phpinfo()'s output + unset($GLOBALS['PHP_AUTH_PW']); + unset($_SERVER['PHP_AUTH_PW']); + + $_SESSION['last_access_time'] = time(); + + // Name and password cookies need to be refreshed each time + // Duration = one month for username + $GLOBALS['PMA_Config']->setCookie( + 'pmaUser-' . $GLOBALS['server'], + $this->blowfishEncrypt( + $cfg['Server']['user'], + $this->_getBlowfishSecret() + ) + ); + + // Duration = as configured + $GLOBALS['PMA_Config']->setCookie( + 'pmaPass-' . $GLOBALS['server'], + $this->blowfishEncrypt( + ! empty($cfg['Server']['password']) + ? $cfg['Server']['password'] : "\xff(blank)", + $this->_getBlowfishSecret() + ), + null, + $GLOBALS['cfg']['LoginCookieStore'] + ); + + // Set server cookies if required (once per session) and, in this case, + // force reload to ensure the client accepts cookies + if (! $GLOBALS['from_cookie']) { + if ($GLOBALS['cfg']['AllowArbitraryServer']) { + if (! empty($GLOBALS['pma_auth_server'])) { + // Duration = one month for servername + $GLOBALS['PMA_Config']->setCookie( + 'pmaServer-' . $GLOBALS['server'], + $cfg['Server']['host'] + ); + } else { + // Delete servername cookie + $GLOBALS['PMA_Config']->removeCookie( + 'pmaServer-' . $GLOBALS['server'] + ); + } + } + + // URL where to go: + $redirect_url = $cfg['PmaAbsoluteUri'] . 'index.php'; + + // any parameters to pass? + $url_params = array(); + if (strlen($GLOBALS['db'])) { + $url_params['db'] = $GLOBALS['db']; + } + if (strlen($GLOBALS['table'])) { + $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. + */ + PMA_Util::clearUserCache(); + + PMA_Response::getInstance()->disable(); + + PMA_sendHeaderLocation( + $redirect_url . PMA_URL_getCommon($url_params, '&'), + true + ); + if (! defined('TESTSUITE')) { + exit; + } else { + return false; + } + } // end if + + return true; + + } + + /** + * User is not allowed to login to MySQL -> authentication failed + * + * prepares error message and switches to auth() which display the error + * and the login form + * + * this function MUST exit/quit the application, + * currently doen by call to auth() + * + * @return void + */ + public function authFails() + { + global $conn_error; + + // Deletes password cookie and displays the login form + $GLOBALS['PMA_Config']->removeCookie('pmaPass-' . $GLOBALS['server']); + + if (! empty($GLOBALS['login_without_password_is_forbidden'])) { + $conn_error = __( + 'Login without a password is forbidden by configuration' + . ' (see AllowNoPassword)' + ); + } elseif (! empty($GLOBALS['allowDeny_forbidden'])) { + $conn_error = __('Access denied'); + } elseif (! empty($GLOBALS['no_activity'])) { + $conn_error = sprintf( + __('No activity within %s seconds; please log in again.'), + $GLOBALS['cfg']['LoginCookieValidity'] + ); + } elseif ($GLOBALS['dbi']->getError()) { + $conn_error = '#' . $GLOBALS['errno'] . ' ' + . __('Cannot log in to the MySQL server'); + } else { + $conn_error = __('Cannot log in to the MySQL server'); + } + + // needed for PHP-CGI (not need for FastCGI or mod-php) + header('Cache-Control: no-store, no-cache, must-revalidate'); + header('Pragma: no-cache'); + + $this->auth(); + } + + /** + * Returns blowfish secret or generates one if needed. + * + * @return string + */ + private function _getBlowfishSecret() + { + if (empty($GLOBALS['cfg']['blowfish_secret'])) { + if (empty($_SESSION['auto_blowfish_secret'])) { + // this returns 23 characters + $_SESSION['auto_blowfish_secret'] = uniqid('', true); + } + return $_SESSION['auto_blowfish_secret']; + } else { + // apply md5() to work around too long secrets (returns 32 characters) + return md5($GLOBALS['cfg']['blowfish_secret']); + } + } + + /** + * Encryption using blowfish algorithm (mcrypt) + * or phpseclib's AES if mcrypt not available + * + * @param string $data original data + * @param string $secret the secret + * + * @return string the encrypted result + */ + public function blowfishEncrypt($data, $secret) + { + global $iv; + if (! function_exists('mcrypt_encrypt')) { + /** + * This library uses mcrypt when available, so + * we could always call it instead of having an + * if/then/else logic, however the include_once + * call is costly + */ + include_once "./libraries/phpseclib/Crypt/AES.php"; + $cipher = new Crypt_AES(CRYPT_AES_MODE_ECB); + $cipher->setKey($secret); + return base64_encode($cipher->encrypt($data)); + } else { + return base64_encode( + mcrypt_encrypt( + MCRYPT_BLOWFISH, + $secret, + $data, + MCRYPT_MODE_CBC, + $iv + ) + ); + } + } + + /** + * Decryption using blowfish algorithm (mcrypt) + * or phpseclib's AES if mcrypt not available + * + * @param string $encdata encrypted data + * @param string $secret the secret + * + * @return string original data + */ + public function blowfishDecrypt($encdata, $secret) + { + global $iv; + if (! function_exists('mcrypt_encrypt')) { + include_once "./libraries/phpseclib/Crypt/AES.php"; + $cipher = new Crypt_AES(CRYPT_AES_MODE_ECB); + $cipher->setKey($secret); + return $cipher->decrypt(base64_decode($encdata)); + } else { + $data = base64_decode($encdata); + $decrypted = mcrypt_decrypt( + MCRYPT_BLOWFISH, + $secret, + $data, + MCRYPT_MODE_CBC, + $iv + ); + return trim($decrypted); + } + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } +} diff --git a/sources/libraries/plugins/auth/AuthenticationHttp.class.php b/sources/libraries/plugins/auth/AuthenticationHttp.class.php new file mode 100644 index 0000000..75273b1 --- /dev/null +++ b/sources/libraries/plugins/auth/AuthenticationHttp.class.php @@ -0,0 +1,261 @@ +getFooter()->setMinimal(); + $header = $response->getHeader(); + $header->setTitle(__('Access denied')); + $header->disableMenu(); + $header->setBodyId('loginform'); + + $response->addHTML('

      '); + $response->addHTML(sprintf(__('Welcome to %s'), ' phpMyAdmin')); + $response->addHTML('

      '); + $response->addHTML('

      '); + $response->addHTML( + PMA_Message::error( + __('Wrong username/password. Access denied.') + ) + ); + $response->addHTML('

      '); + + if (file_exists(CUSTOM_FOOTER_FILE)) { + include CUSTOM_FOOTER_FILE; + } + + if (! defined('TESTSUITE')) { + exit; + } else { + return false; + } + } + + /** + * Gets advanced authentication settings + * + * @global string $PHP_AUTH_USER the username if register_globals is on + * @global string $PHP_AUTH_PW the password if register_globals is on + * @global array the array of server variables if + * register_globals is off + * @global array the array of environment variables if + * register_globals is off + * @global string the username for the ? server + * @global string the password for the ? server + * @global string the username for the WebSite Professional + * server + * @global string the password for the WebSite Professional + * server + * @global string the username of the user who logs out + * + * @return boolean whether we get authentication settings or not + */ + public function authCheck() + { + global $PHP_AUTH_USER, $PHP_AUTH_PW; + + // Grabs the $PHP_AUTH_USER variable whatever are the values of the + // 'register_globals' and the 'variables_order' directives + if (empty($PHP_AUTH_USER)) { + if (PMA_getenv('PHP_AUTH_USER')) { + $PHP_AUTH_USER = PMA_getenv('PHP_AUTH_USER'); + } elseif (PMA_getenv('REMOTE_USER')) { + // CGI, might be encoded, see below + $PHP_AUTH_USER = PMA_getenv('REMOTE_USER'); + } elseif (PMA_getenv('REDIRECT_REMOTE_USER')) { + // CGI, might be encoded, see below + $PHP_AUTH_USER = PMA_getenv('REDIRECT_REMOTE_USER'); + } elseif (PMA_getenv('AUTH_USER')) { + // WebSite Professional + $PHP_AUTH_USER = PMA_getenv('AUTH_USER'); + } elseif (PMA_getenv('HTTP_AUTHORIZATION') + && false === strpos(PMA_getenv('HTTP_AUTHORIZATION'), '<') + ) { + // IIS, might be encoded, see below; also prevent XSS + $PHP_AUTH_USER = PMA_getenv('HTTP_AUTHORIZATION'); + } elseif (PMA_getenv('Authorization')) { + // FastCGI, might be encoded, see below + $PHP_AUTH_USER = PMA_getenv('Authorization'); + } + } + // Grabs the $PHP_AUTH_PW variable whatever are the values of the + // 'register_globals' and the 'variables_order' directives + if (empty($PHP_AUTH_PW)) { + if (PMA_getenv('PHP_AUTH_PW')) { + $PHP_AUTH_PW = PMA_getenv('PHP_AUTH_PW'); + } elseif (PMA_getenv('REMOTE_PASSWORD')) { + // Apache/CGI + $PHP_AUTH_PW = PMA_getenv('REMOTE_PASSWORD'); + } elseif (PMA_getenv('AUTH_PASSWORD')) { + // WebSite Professional + $PHP_AUTH_PW = PMA_getenv('AUTH_PASSWORD'); + } + } + + // 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($PHP_AUTH_USER, 0, 6), 'Basic ') == 0) { + $usr_pass = base64_decode(substr($PHP_AUTH_USER, 6)); + if (! empty($usr_pass)) { + $colon = strpos($usr_pass, ':'); + if ($colon) { + $PHP_AUTH_USER = substr($usr_pass, 0, $colon); + $PHP_AUTH_PW = substr($usr_pass, $colon + 1); + } + unset($colon); + } + unset($usr_pass); + } + + // 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($PHP_AUTH_USER) && $old_usr == $PHP_AUTH_USER) + ) { + $PHP_AUTH_USER = ''; + // -> delete user's choices that were stored in session + if (! defined('TESTSUITE')) { + session_destroy(); + } + } + + // Returns whether we get authentication settings or not + if (empty($PHP_AUTH_USER)) { + return false; + } else { + return true; + } + } + + /** + * Set the user and password after last checkings if required + * + * @global array $cfg the valid servers settings + * @global integer $server the id of the current server + * @global array the current server settings + * @global string $PHP_AUTH_USER the current username + * @global string $PHP_AUTH_PW the current password + * + * @return boolean always true + */ + public function authSetUser() + { + global $cfg, $server; + global $PHP_AUTH_USER, $PHP_AUTH_PW; + + // Ensures valid authentication mode, 'only_db', bookmark database and + // table names and relation table name are used + if ($cfg['Server']['user'] != $PHP_AUTH_USER) { + $servers_cnt = count($cfg['Servers']); + for ($i = 1; $i <= $servers_cnt; $i++) { + if (isset($cfg['Servers'][$i]) + && ($cfg['Servers'][$i]['host'] == $cfg['Server']['host'] + && $cfg['Servers'][$i]['user'] == $PHP_AUTH_USER) + ) { + $server = $i; + $cfg['Server'] = $cfg['Servers'][$i]; + break; + } + } // end for + } // end if + + $cfg['Server']['user'] = $PHP_AUTH_USER; + $cfg['Server']['password'] = $PHP_AUTH_PW; + + // Avoid showing the password in phpinfo()'s output + unset($GLOBALS['PHP_AUTH_PW']); + unset($_SERVER['PHP_AUTH_PW']); + + return true; + } + + /** + * User is not allowed to login to MySQL -> authentication failed + * + * @return boolean always true (no return indeed) + */ + public function authFails() + { + $error = $GLOBALS['dbi']->getError(); + if ($error && $GLOBALS['errno'] != 1045) { + PMA_fatalError($error); + } else { + $this->auth(); + return true; + } + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } +} diff --git a/sources/libraries/plugins/auth/AuthenticationSignon.class.php b/sources/libraries/plugins/auth/AuthenticationSignon.class.php new file mode 100644 index 0000000..4586af9 --- /dev/null +++ b/sources/libraries/plugins/auth/AuthenticationSignon.class.php @@ -0,0 +1,299 @@ + authentication failed + * + * @return boolean always true (no return indeed) + */ + public function authFails() + { + /* Session name */ + $session_name = $GLOBALS['cfg']['Server']['SignonSession']; + + /* Does session exist? */ + if (isset($_COOKIE[$session_name])) { + /* End current session */ + if (! defined('TESTSUITE')) { + session_write_close(); + } + + /* Load single signon session */ + session_name($session_name); + session_id($_COOKIE[$session_name]); + if (! defined('TESTSUITE')) { + session_start(); + } + + /* Set error message */ + if (! empty($GLOBALS['login_without_password_is_forbidden'])) { + $_SESSION['PMA_single_signon_error_message'] = __( + 'Login without a password is forbidden by configuration ' + . '(see AllowNoPassword)' + ); + } elseif (! empty($GLOBALS['allowDeny_forbidden'])) { + $_SESSION['PMA_single_signon_error_message'] = __('Access denied'); + } elseif (! empty($GLOBALS['no_activity'])) { + $_SESSION['PMA_single_signon_error_message'] = sprintf( + __('No activity within %s seconds; please log in again.'), + $GLOBALS['cfg']['LoginCookieValidity'] + ); + } elseif ($GLOBALS['dbi']->getError()) { + $_SESSION['PMA_single_signon_error_message'] = PMA_sanitize( + $GLOBALS['dbi']->getError() + ); + } else { + $_SESSION['PMA_single_signon_error_message'] = __( + 'Cannot log in to the MySQL server' + ); + } + } + $this->auth(); + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } +} diff --git a/sources/libraries/plugins/auth/recaptchalib.php b/sources/libraries/plugins/auth/recaptchalib.php new file mode 100644 index 0000000..32c4f4d --- /dev/null +++ b/sources/libraries/plugins/auth/recaptchalib.php @@ -0,0 +1,277 @@ + $value ) + $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; + + // Cut the last '&' + $req=substr($req,0,strlen($req)-1); + return $req; +} + + + +/** + * Submits an HTTP POST to a reCAPTCHA server + * @param string $host + * @param string $path + * @param array $data + * @param int port + * @return array response + */ +function _recaptcha_http_post($host, $path, $data, $port = 80) { + + $req = _recaptcha_qsencode ($data); + + $http_request = "POST $path HTTP/1.0\r\n"; + $http_request .= "Host: $host\r\n"; + $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; + $http_request .= "Content-Length: " . strlen($req) . "\r\n"; + $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; + $http_request .= "\r\n"; + $http_request .= $req; + + $response = ''; + if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { + die ('Could not open socket'); + } + + fwrite($fs, $http_request); + + while ( !feof($fs) ) + $response .= fgets($fs, 1160); // One TCP-IP packet + fclose($fs); + $response = explode("\r\n\r\n", $response, 2); + + return $response; +} + + + +/** + * Gets the challenge HTML (javascript and non-javascript version). + * This is called from the browser, and the resulting reCAPTCHA HTML widget + * is embedded within the HTML form it was called from. + * @param string $pubkey A public key for reCAPTCHA + * @param string $error The error given by reCAPTCHA (optional, default is null) + * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false) + + * @return string - The HTML to be embedded in the user's form. + */ +function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) +{ + if ($pubkey == null || $pubkey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + + if ($use_ssl) { + $server = RECAPTCHA_API_SECURE_SERVER; + } else { + $server = RECAPTCHA_API_SERVER; + } + + $errorpart = ""; + if ($error) { + $errorpart = "&error=" . $error; + } + return ' + + '; +} + + + + +/** + * A ReCaptchaResponse is returned from recaptcha_check_answer() + */ +class ReCaptchaResponse { + var $is_valid; + var $error; +} + + +/** + * Calls an HTTP POST function to verify if the user's guess was correct + * @param string $privkey + * @param string $remoteip + * @param string $challenge + * @param string $response + * @param array $extra_params an array of extra variables to post to the server + * @return ReCaptchaResponse + */ +function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) +{ + if ($privkey == null || $privkey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + + if ($remoteip == null || $remoteip == '') { + die ("For security reasons, you must pass the remote ip to reCAPTCHA"); + } + + + + //discard spam submissions + if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { + $recaptcha_response = new ReCaptchaResponse(); + $recaptcha_response->is_valid = false; + $recaptcha_response->error = 'incorrect-captcha-sol'; + return $recaptcha_response; + } + + $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify", + array ( + 'privatekey' => $privkey, + 'remoteip' => $remoteip, + 'challenge' => $challenge, + 'response' => $response + ) + $extra_params + ); + + $answers = explode ("\n", $response [1]); + $recaptcha_response = new ReCaptchaResponse(); + + if (trim ($answers [0]) == 'true') { + $recaptcha_response->is_valid = true; + } + else { + $recaptcha_response->is_valid = false; + $recaptcha_response->error = $answers [1]; + } + return $recaptcha_response; + +} + +/** + * gets a URL where the user can sign up for reCAPTCHA. If your application + * has a configuration page where you enter a key, you should provide a link + * using this function. + * @param string $domain The domain where the page is hosted + * @param string $appname The name of your application + */ +function recaptcha_get_signup_url ($domain = null, $appname = null) { + return "https://www.google.com/recaptcha/admin/create?" . _recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname)); +} + +function _recaptcha_aes_pad($val) { + $block_size = 16; + $numpad = $block_size - (strlen ($val) % $block_size); + return str_pad($val, strlen ($val) + $numpad, chr($numpad)); +} + +/* Mailhide related code */ + +function _recaptcha_aes_encrypt($val,$ky) { + if (! function_exists ("mcrypt_encrypt")) { + die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed."); + } + $mode=MCRYPT_MODE_CBC; + $enc=MCRYPT_RIJNDAEL_128; + $val=_recaptcha_aes_pad($val); + return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); +} + + +function _recaptcha_mailhide_urlbase64 ($x) { + return strtr(base64_encode ($x), '+/', '-_'); +} + +/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */ +function recaptcha_mailhide_url($pubkey, $privkey, $email) { + if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) { + die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " . + "you can do so at http://www.google.com/recaptcha/mailhide/apikey"); + } + + + $ky = pack('H*', $privkey); + $cryptmail = _recaptcha_aes_encrypt ($email, $ky); + + return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail); +} + +/** + * gets the parts of the email to expose to the user. + * eg, given johndoe@example,com return ["john", "example.com"]. + * the email is then displayed as john...@example.com + */ +function _recaptcha_mailhide_email_parts ($email) { + $arr = preg_split("/@/", $email ); + + if (strlen ($arr[0]) <= 4) { + $arr[0] = substr ($arr[0], 0, 1); + } else if (strlen ($arr[0]) <= 6) { + $arr[0] = substr ($arr[0], 0, 3); + } else { + $arr[0] = substr ($arr[0], 0, 4); + } + return $arr; +} + +/** + * Gets html to display an email address given a public an private key. + * to get a key, go to: + * + * http://www.google.com/recaptcha/mailhide/apikey + */ +function recaptcha_mailhide_html($pubkey, $privkey, $email) { + $emailparts = _recaptcha_mailhide_email_parts ($email); + $url = recaptcha_mailhide_url ($pubkey, $privkey, $email); + + return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]); + +} + + +?> diff --git a/sources/libraries/plugins/auth/swekey/authentication.inc.php b/sources/libraries/plugins/auth/swekey/authentication.inc.php new file mode 100644 index 0000000..1977f88 --- /dev/null +++ b/sources/libraries/plugins/auth/swekey/authentication.inc.php @@ -0,0 +1,172 @@ + + + diff --git a/sources/libraries/plugins/auth/swekey/musbe-ca.crt b/sources/libraries/plugins/auth/swekey/musbe-ca.crt new file mode 100644 index 0000000..2a31ad1 --- /dev/null +++ b/sources/libraries/plugins/auth/swekey/musbe-ca.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIJAMjw7QcLWCd6MA0GCSqGSIb3DQEBBQUAMGsxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRQwEgYDVQQKEwtNdXNiZSwgSW5j +LjESMBAGA1UEAxMJbXVzYmUuY29tMR0wGwYJKoZIhvcNAQkBFg5pbmZvQG11c2Jl +LmNvbTAeFw0wODA5MDQxNDE2MTNaFw0zNzEyMjExNDE2MTNaMGsxCzAJBgNVBAYT +AlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRQwEgYDVQQKEwtNdXNiZSwgSW5jLjES +MBAGA1UEAxMJbXVzYmUuY29tMR0wGwYJKoZIhvcNAQkBFg5pbmZvQG11c2JlLmNv +bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOBhOljxVzQfK4gted2I +d3BemcjW4abAUOzn3KYWXpPO5xIfVeXNDGkDbyH+X+7fo94sX25/ewuKNFDSOcvo +tXHq7uQenTHB35r+a+LY81KceUHgW90a3XsqPAkwAjyYcgo3zmM2DtLvw+5Yod8T +wAHk9m3qavnQ1uk99jBTwL7RZ9jIZHh9pFCL93uJc2obtd8O96Iycbn2q0w/AWbb ++eUVWIHzvLtfPvROeL3lJzr/Uz5LjKapxJ3qyqASflfHpnj9pU8l6g2TQ6Hg5KT5 +tLFkRe7uGhOfRtOQ/+NjaWrEuNCFnpyN4Q5Fv+5qA1Ip1IpH0200sWbAf/k2u0Qp +Sx0CAwEAAaOB0DCBzTAdBgNVHQ4EFgQUczJrQ7hCvtsnzcqiDIZ/GSn/CiwwgZ0G +A1UdIwSBlTCBkoAUczJrQ7hCvtsnzcqiDIZ/GSn/Ciyhb6RtMGsxCzAJBgNVBAYT +AlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRQwEgYDVQQKEwtNdXNiZSwgSW5jLjES +MBAGA1UEAxMJbXVzYmUuY29tMR0wGwYJKoZIhvcNAQkBFg5pbmZvQG11c2JlLmNv +bYIJAMjw7QcLWCd6MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGxk +8xzIljeBDQWWVRr0NEALVSv3i09V4jAKkyEOfmZ8lKMKJi0atwbtjrXTzLnNYj+Q +pyUbyY/8ItWvV7pnVxMiF9qcer7e9X4vw358GZuMVE/da1nWxz+CwzTm5oO30RzA +antM9bISFFr9lJq69bDWOnCUi1IG8DSL3TxtlABso7S4vqiZ+sB33l6k1K4a/Njb +QkU9UejKhKkVVZTsOrumfnOJ4MCmPfX8Y/AY2o670y5HnzpxerIYziCVzApPVrW7 +sKH0tuVGturMfQOKgstYe4/m9glBTeTLMkjD+6MJC2ONBD7GAiOO95gNl5M1fzJQ +FEe5CJ7DCYl0GdmLXXw= +-----END CERTIFICATE----- diff --git a/sources/libraries/plugins/auth/swekey/swekey.auth.lib.php b/sources/libraries/plugins/auth/swekey/swekey.auth.lib.php new file mode 100644 index 0000000..9c36efc --- /dev/null +++ b/sources/libraries/plugins/auth/swekey/swekey.auth.lib.php @@ -0,0 +1,302 @@ + + + \n"; + // if (file_exists($caFile)) + // echo "\n"; + } + + if (file_exists($caFile)) { + Swekey_SetCAFile($caFile); + } elseif (! empty($caFile) + && (substr($_SESSION['SWEKEY']['CONF_SERVER_CHECK'], 0, 8) == "https://")) { + return "Internal Error: CA File $caFile not found"; + } + + $result = null; + $swekey_id = $_GET['swekey_id']; + $swekey_otp = $_GET['swekey_otp']; + + if (isset($swekey_id)) { + unset($_SESSION['SWEKEY']['AUTHENTICATED_SWEKEY']); + if (! isset($_SESSION['SWEKEY']['RND_TOKEN'])) { + unset($swekey_id); + } else { + if (strlen($swekey_id) == 32) { + $res = Swekey_CheckOtp($swekey_id, $_SESSION['SWEKEY']['RND_TOKEN'], $swekey_otp); + unset($_SESSION['SWEKEY']['RND_TOKEN']); + if (! $res) { + $result = __('Hardware authentication failed!') . ' (' . Swekey_GetLastError() . ')'; + } else { + $_SESSION['SWEKEY']['AUTHENTICATED_SWEKEY'] = $swekey_id; + $_SESSION['SWEKEY']['FORCE_USER'] = $_SESSION['SWEKEY']['VALID_SWEKEYS'][$swekey_id]; + return null; + } + } else { + $result = __('No valid authentication key plugged'); + if ($_SESSION['SWEKEY']['CONF_DEBUG']) { + $result .= "
      " . htmlspecialchars($swekey_id); + } + unset($_SESSION['SWEKEY']['CONF_LOADED']); // reload the conf file + } + } + } else { + unset($_SESSION['SWEKEY']); + } + + $_SESSION['SWEKEY']['RND_TOKEN'] = Swekey_GetFastRndToken(); + if (strlen($_SESSION['SWEKEY']['RND_TOKEN']) != 64) { + $result = __('Hardware authentication failed!') . ' (' . Swekey_GetLastError() . ')'; + unset($_SESSION['SWEKEY']['CONF_LOADED']); // reload the conf file + } + + if (! isset($swekey_id)) { + ?> + + display(); + if ($GLOBALS['error_handler']->hasDisplayErrors()) { + echo '
      '; + $GLOBALS['error_handler']->dispErrors(); + echo '
      '; + } + } + + if (isset($_SESSION['SWEKEY']) && $_SESSION['SWEKEY']['ENABLED']) { + echo ''; + } +} + +if (!empty($_GET['session_to_unset'])) { + session_write_close(); + session_id($_GET['session_to_unset']); + session_start(); + $_SESSION = array(); + session_write_close(); + session_destroy(); + exit; +} + +if (isset($_GET['swekey_reset'])) { + unset($_SESSION['SWEKEY']); +} + +?> diff --git a/sources/libraries/plugins/auth/swekey/swekey.php b/sources/libraries/plugins/auth/swekey/swekey.php new file mode 100644 index 0000000..d495d45 --- /dev/null +++ b/sources/libraries/plugins/auth/swekey/swekey.php @@ -0,0 +1,522 @@ +"; + +/** + * Servers addresses + * Use the Swekey_SetXxxServer($server) functions to set them + */ + +global $gSwekeyCheckServer; +if (! isset($gSwekeyCheckServer)) { + $gSwekeyCheckServer = SWEKEY_DEFAULT_CHECK_SERVER; +} + +global $gSwekeyRndTokenServer; +if (! isset($gSwekeyRndTokenServer)) { + $gSwekeyRndTokenServer = SWEKEY_DEFAULT_RND_SERVER; +} + +global $gSwekeyStatusServer; +if (! isset($gSwekeyStatusServer)) { + $gSwekeyStatusServer = SWEKEY_DEFAULT_STATUS_SERVER; +} + +global $gSwekeyCA; + +global $gSwekeyTokenCacheEnabled; +if (! isset($gSwekeyTokenCacheEnabled)) { + $gSwekeyTokenCacheEnabled = true; +} + +/** + * Change the address of the Check server. + * If $server is empty the default value 'http://auth-check.musbe.net' will be used + * + * @param server The protocol and hostname to use + * + * @access public + */ +function Swekey_SetCheckServer($server) +{ + global $gSwekeyCheckServer; + if (empty($server)) { + $gSwekeyCheckServer = SWEKEY_DEFAULT_CHECK_SERVER; + } else { + $gSwekeyCheckServer = $server; + } +} + +/** + * Change the address of the Random Token Generator server. + * If $server is empty the default value 'http://auth-rnd-gen.musbe.net' will be used + * + * @param server The protocol and hostname to use + * + * @access public + */ +function Swekey_SetRndTokenServer($server) +{ + global $gSwekeyRndTokenServer; + if (empty($server)) { + $gSwekeyRndTokenServer = SWEKEY_DEFAULT_RND_SERVER; + } else { + $gSwekeyRndTokenServer = $server; + } +} + +/** + * Change the address of the Satus server. + * If $server is empty the default value 'http://auth-status.musbe.net' will be used + * + * @param server The protocol and hostname to use + * + * @access public + */ +function Swekey_SetStatusServer($server) +{ + global $gSwekeyStatusServer; + if (empty($server)) { + $gSwekeyStatusServer = SWEKEY_DEFAULT_STATUS_SERVER; + } else { + $gSwekeyStatusServer = $server; + } +} + +/** + * Change the certificat file in case of the the severs use https instead of http + * + * @param cafile The path of the crt file to use + * + * @access public + */ +function Swekey_SetCAFile($cafile) +{ + global $gSwekeyCA; + $gSwekeyCA = $cafile; +} + +/** + * Enable or disable the random token caching + * Because everybody has full access to the cache file, it can be a DOS vulnerability + * So disable it if you are running in a non secure enviromnement + * + * @param $enable + * + * @access public + */ +function Swekey_EnableTokenCache($enable) +{ + global $gSwekeyTokenCacheEnabled; + $gSwekeyTokenCacheEnabled = ! empty($enable); +} + + +/** + * Return the last error. + * + * @return The Last Error + * @access public + */ +function Swekey_GetLastError() +{ + global $gSwekeyLastError; + return $gSwekeyLastError; +} + +/** + * Return the last result. + * + * @return The Last Error + * @access public + */ +function Swekey_GetLastResult() +{ + global $gSwekeyLastResult; + return $gSwekeyLastResult; +} + +/** + * Send a synchronous request to the server. + * This function manages timeout then will not block if one of the server is down + * + * @param url The url to get + * @param response_code The response code + * + * @return The body of the response or "" in case of error + * @access private + */ +function Swekey_HttpGet($url, &$response_code) +{ + global $gSwekeyLastError; + $gSwekeyLastError = 0; + global $gSwekeyLastResult; + $gSwekeyLastResult = ""; + + // use curl if available + if (function_exists('curl_init')) { + $sess = curl_init($url); + if (substr($url, 0, 8) == "https://") { + global $gSwekeyCA; + + if (! empty($gSwekeyCA)) { + if (file_exists($gSwekeyCA)) { + if (! curl_setopt($sess, CURLOPT_CAINFO, $gSwekeyCA)) { + error_log("SWEKEY_ERROR:Could not set CA file : ".curl_error($sess)); + } else { + $caFileOk = true; + } + } else { + error_log("SWEKEY_ERROR:Could not find CA file $gSwekeyCA getting $url"); + } + } + + curl_setopt($sess, CURLOPT_SSL_VERIFYHOST, '2'); + curl_setopt($sess, CURLOPT_SSL_VERIFYPEER, '2'); + curl_setopt($sess, CURLOPT_CONNECTTIMEOUT, '20'); + curl_setopt($sess, CURLOPT_TIMEOUT, '20'); + } else { + curl_setopt($sess, CURLOPT_CONNECTTIMEOUT, '3'); + curl_setopt($sess, CURLOPT_TIMEOUT, '5'); + } + + curl_setopt($sess, CURLOPT_RETURNTRANSFER, '1'); + $res=curl_exec($sess); + $response_code = curl_getinfo($sess, CURLINFO_HTTP_CODE); + $curlerr = curl_error($sess); + curl_close($sess); + + if ($response_code == 200) { + $gSwekeyLastResult = $res; + return $res; + } + + if (! empty($response_code)) { + $gSwekeyLastError = $response_code; + error_log("SWEKEY_ERROR:Error $gSwekeyLastError ($curlerr) getting $url"); + return ""; + } + + $response_code = 408; // Request Timeout + $gSwekeyLastError = $response_code; + error_log("SWEKEY_ERROR:Error $curlerr getting $url"); + return ""; + } + + // use pecl_http if available + if (class_exists('HttpRequest')) { + // retry if one of the server is down + for ($num=1; $num <= 3; $num++ ) { + $r = new HttpRequest($url); + $options = array('timeout' => '3'); + + if (substr($url, 0, 6) == "https:") { + $sslOptions = array(); + $sslOptions['verifypeer'] = true; + $sslOptions['verifyhost'] = true; + + $capath = __FILE__; + $name = strrchr($capath, '/'); + // windows + if (empty($name)) { + $name = strrchr($capath, '\\'); + } + $capath = substr($capath, 0, strlen($capath) - strlen($name) + 1).'musbe-ca.crt'; + + if (! empty($gSwekeyCA)) { + $sslOptions['cainfo'] = $gSwekeyCA; + } + + $options['ssl'] = $sslOptions; + } + + $r->setOptions($options); + + // try + { + $reply = $r->send(); + $res = $reply->getBody(); + $info = $r->getResponseInfo(); + $response_code = $info['response_code']; + if ($response_code != 200) { + $gSwekeyLastError = $response_code; + error_log("SWEKEY_ERROR:Error ".$gSwekeyLastError." getting ".$url); + return ""; + } + + + $gSwekeyLastResult = $res; + return $res; + } + // catch (HttpException $e) + // { + // error_log("SWEKEY_WARNING:HttpException ".$e." getting ".$url); + // } + } + + $response_code = 408; // Request Timeout + $gSwekeyLastError = $response_code; + error_log("SWEKEY_ERROR:Error ".$gSwekeyLastError." getting ".$url); + return ""; + } + + global $http_response_header; + $res = @file_get_contents($url); + $response_code = substr($http_response_header[0], 9, 3); //HTTP/1.0 + if ($response_code == 200) { + $gSwekeyLastResult = $res; + return $res; + } + + $gSwekeyLastError = $response_code; + error_log("SWEKEY_ERROR:Error ".$response_code." getting ".$url); + return ""; +} + +/** + * Get a Random Token from a Token Server + * The RT is a 64 vhars hexadecimal value + * You should better use Swekey_GetFastRndToken() for performance + * @access public + */ +function Swekey_GetRndToken() +{ + global $gSwekeyRndTokenServer; + return Swekey_HttpGet($gSwekeyRndTokenServer.'/FULL-RND-TOKEN', $response_code); +} + +/** + * Get a Half Random Token from a Token Server + * The RT is a 64 vhars hexadecimal value + * Use this value if you want to make your own Swekey_GetFastRndToken() + * @access public + */ +function Swekey_GetHalfRndToken() +{ + global $gSwekeyRndTokenServer; + return Swekey_HttpGet($gSwekeyRndTokenServer.'/HALF-RND-TOKEN', $response_code); +} + +/** + * Get a Half Random Token + * The RT is a 64 vhars hexadecimal value + * This function get a new random token and reuse it. + * Token are refetched from the server only once every 30 seconds. + * You should always use this function to get half random token. + * @access public + */ +function Swekey_GetFastHalfRndToken() +{ + global $gSwekeyTokenCacheEnabled; + + $res = ""; + $cachefile = ""; + + // We check if we have a valid RT is the session + if (isset($_SESSION['rnd-token-date'])) { + if (time() - $_SESSION['rnd-token-date'] < 30) { + $res = $_SESSION['rnd-token']; + } + } + + // If not we try to get it from a temp file (PHP >= 5.2.1 only) + if (strlen($res) != 32 && $gSwekeyTokenCacheEnabled) { + if (function_exists('sys_get_temp_dir')) { + $tempdir = sys_get_temp_dir(); + $cachefile = $tempdir."/swekey-rnd-token-".get_current_user(); + $modif = filemtime($cachefile); + if ($modif != false) { + if (time() - $modif < 30) { + $res = @file_get_contents($cachefile); + if (strlen($res) != 32) { + $res = ""; + } else { + $_SESSION['rnd-token'] = $res; + $_SESSION['rnd-token-date'] = $modif; + } + } + } + } + } + + // If we don't have a valid RT here we have to get it from the server + if (strlen($res) != 32) { + $res = substr(Swekey_GetHalfRndToken(), 0, 32); + $_SESSION['rnd-token'] = $res; + $_SESSION['rnd-token-date'] = time(); + if (! empty($cachefile)) { + // we unlink the file so no possible tempfile race attack + unlink($cachefile); + $file = fopen($cachefile, "x"); + if ($file != false) { + @fwrite($file, $res); + @fclose($file); + } + } + } + + return $res."00000000000000000000000000000000"; +} + +/** + * Get a Random Token + * The RT is a 64 vhars hexadecimal value + * This function generates a unique random token for each call but call the + * server only once every 30 seconds. + * You should always use this function to get random token. + * @access public + */ +function Swekey_GetFastRndToken() +{ + $res = Swekey_GetFastHalfRndToken(); + if (strlen($res) == 64) { + return substr($res, 0, 32).strtoupper(md5("Musbe Authentication Key" . mt_rand() . date(DATE_ATOM))); + } + return ""; +} + + +/** + * Checks that an OTP generated by a Swekey is valid + * + * @param id The id of the swekey + * @param rt The random token used to generate the otp + * @param otp The otp generated by the swekey + * + * @return true or false + * @access public + */ +function Swekey_CheckOtp($id, $rt, $otp) +{ + global $gSwekeyCheckServer; + $res = Swekey_HttpGet($gSwekeyCheckServer.'/CHECK-OTP/'.$id.'/'.$rt.'/'.$otp, $response_code); + return $response_code == 200 && $res == "OK"; +} + +/** + * Values that are associated with a key. + * The following values can be returned by the Swekey_GetStatus() function + */ +define("SWEKEY_STATUS_OK", 0); +define("SWEKEY_STATUS_NOT_FOUND", 1); // The key does not exist in the db +define("SWEKEY_STATUS_INACTIVE", 2); // The key has never been activated +define("SWEKEY_STATUS_LOST", 3); // The user has lost his key +define("SWEKEY_STATUS_STOLEN", 4); // The key was stolen +define("SWEKEY_STATUS_FEE_DUE", 5); // The annual fee was not paid +define("SWEKEY_STATUS_OBSOLETE", 6); // The hardware is no longer supported +define("SWEKEY_STATUS_UNKOWN", 201); // We could not connect to the authentication server + +/** + * Values that are associated with a key. + * The Javascript Api can also return the following values + */ +define("SWEKEY_STATUS_REPLACED", 100); // This key has been replaced by a backup key +define("SWEKEY_STATUS_BACKUP_KEY", 101); // This key is a backup key that is not activated yet +define("SWEKEY_STATUS_NOTPLUGGED", 200); // This key is not plugged in the computer + + +/** + * Return the text corresponding to the integer status of a key + * + * @param status The status + * + * @return The text corresponding to the status + * @access public + */ +function Swekey_GetStatusStr($status) +{ + switch($status) + { + case SWEKEY_STATUS_OK : + return 'OK'; + case SWEKEY_STATUS_NOT_FOUND : + return 'Key does not exist in the db'; + case SWEKEY_STATUS_INACTIVE : + return 'Key not activated'; + case SWEKEY_STATUS_LOST : + return 'Key was lost'; + case SWEKEY_STATUS_STOLEN : + return 'Key was stolen'; + case SWEKEY_STATUS_FEE_DUE : + return 'The annual fee was not paid'; + case SWEKEY_STATUS_OBSOLETE : + return 'Key no longer supported'; + case SWEKEY_STATUS_REPLACED : + return 'This key has been replaced by a backup key'; + case SWEKEY_STATUS_BACKUP_KEY : + return 'This key is a backup key that is not activated yet'; + case SWEKEY_STATUS_NOTPLUGGED : + return 'This key is not plugged in the computer'; + case SWEKEY_STATUS_UNKOWN : + return 'Unknow Status, could not connect to the authentication server'; + } + return 'unknown status '.$status; +} + +/** + * If your web site requires a key to login you should check that the key + * is still valid (has not been lost or stolen) before requiring it. + * A key can be authenticated only if its status is SWEKEY_STATUS_OK + * + * @param id The id of the swekey + * + * @return The status of the swekey + * @access public + */ +function Swekey_GetStatus($id) +{ + global $gSwekeyStatusServer; + $res = Swekey_HttpGet($gSwekeyStatusServer.'/GET-STATUS/'.$id, $response_code); + if ($response_code == 200) { + return intval($res); + } + return SWEKEY_STATUS_UNKOWN; +} + +?> diff --git a/sources/libraries/plugins/export/ExportCodegen.class.php b/sources/libraries/plugins/export/ExportCodegen.class.php new file mode 100644 index 0000000..4495e7e --- /dev/null +++ b/sources/libraries/plugins/export/ExportCodegen.class.php @@ -0,0 +1,423 @@ +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() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/HiddenPropertyItem.class.php"; + include_once "$props/options/items/SelectPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem(); + $leaf->setName("structure_or_data"); + $generalOptions->addProperty($leaf); + $leaf = new SelectPropertyItem(); + $leaf->setName("format"); + $leaf->setText(__('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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * 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 + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + $CG_FORMATS = $this->_getCgFormats(); + $CG_HANDLERS = $this->_getCgHandlers(); + + $format = $GLOBALS['codegen_format']; + if (isset($CG_FORMATS[$format])) { + return PMA_exportOutputHandler( + $this->$CG_HANDLERS[$format]($db, $table, $crlf) + ); + } + return PMA_exportOutputHandler(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 + * + * @return string containing C# code lines, separated by "\n" + */ + private function _handleNHibernateCSBody($db, $table, $crlf) + { + $lines = array(); + + $result = $GLOBALS['dbi']->query( + sprintf( + 'DESC %s.%s', PMA_Util::backquote($db), + PMA_Util::backquote($table) + ) + ); + if ($result) { + $tableProperties = array(); + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $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); + $lines[] = '{'; + $lines[] = ' #region ' . ExportCodegen::cgMakeIdentifier($table); + $lines[] = ' public class ' . ExportCodegen::cgMakeIdentifier($table); + $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) . '() { }'; + $temp = array(); + foreach ($tableProperties as $tableProperty) { + if (! $tableProperty->isPK()) { + $temp[] = $tableProperty->formatCs( + '#dotNetPrimitiveType# #name#' + ); + } + } + $lines[] = ' public ' + . ExportCodegen::cgMakeIdentifier($table) + . '(' + . 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("\n", $lines); + } + + /** + * XML Handler + * + * @param string $db database name + * @param string $table table name + * @param string $crlf line separator + * + * @return string containing XML code lines, separated by "\n" + */ + private function _handleNHibernateXMLBody($db, $table, $crlf) + { + $lines = array(); + $lines[] = ''; + $lines[] = ''; + $lines[] = ' '; + $result = $GLOBALS['dbi']->query( + sprintf( + "DESC %s.%s", PMA_Util::backquote($db), + PMA_Util::backquote($table) + ) + ); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $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("\n", $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($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($CG_HANDLERS) + { + $this->_cgHandlers = $CG_HANDLERS; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/export/ExportCsv.class.php b/sources/libraries/plugins/export/ExportCsv.class.php new file mode 100644 index 0000000..22cbd63 --- /dev/null +++ b/sources/libraries/plugins/export/ExportCsv.class.php @@ -0,0 +1,321 @@ +setProperties(); + } + + /** + * Sets the export CSV properties + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/TextPropertyItem.class.php"; + include_once "$props/options/items/BoolPropertyItem.class.php"; + include_once "$props/options/items/HiddenPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + // create leaf items and add them to the group + $leaf = new TextPropertyItem(); + $leaf->setName("separator"); + $leaf->setText(__('Columns separated with:')); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("enclosed"); + $leaf->setText(__('Columns enclosed with:')); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("escaped"); + $leaf->setText(__('Columns escaped with:')); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("terminated"); + $leaf->setText(__('Lines terminated with:')); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName('null'); + $leaf->setText(__('Replace NULL with:')); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName('removeCRLF'); + $leaf->setText( + __('Remove carriage return/line feed characters within columns') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName('columns'); + $leaf->setText(__('Put columns names in the first row')); + $generalOptions->addProperty($leaf); + $leaf = new HiddenPropertyItem(); + $leaf->setName('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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * 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) || 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 + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + global $what, $csv_terminated, $csv_separator, $csv_enclosed, $csv_escaped; + + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, null, PMA_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++) { + if ($csv_enclosed == '') { + $schema_insert .= stripslashes( + $GLOBALS['dbi']->fieldName($result, $i) + ); + } else { + $schema_insert .= $csv_enclosed + . str_replace( + $csv_enclosed, + $csv_escaped . $csv_enclosed, + stripslashes($GLOBALS['dbi']->fieldName($result, $i)) + ) + . $csv_enclosed; + } + $schema_insert .= $csv_separator; + } // end for + $schema_insert = trim(substr($schema_insert, 0, -1)); + if (! PMA_exportOutputHandler($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 (! PMA_exportOutputHandler($schema_insert . $csv_terminated)) { + return false; + } + } // end while + $GLOBALS['dbi']->freeResult($result); + + return true; + } +} +?> diff --git a/sources/libraries/plugins/export/ExportExcel.class.php b/sources/libraries/plugins/export/ExportExcel.class.php new file mode 100644 index 0000000..1bb1060 --- /dev/null +++ b/sources/libraries/plugins/export/ExportExcel.class.php @@ -0,0 +1,105 @@ +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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + // create primary items and add them to the group + $leaf = new TextPropertyItem(); + $leaf->setName('null'); + $leaf->setText(__('Replace NULL with:')); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName('removeCRLF'); + $leaf->setText( + __('Remove carriage return/line feed characters within columns') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName('columns'); + $leaf->setText(__('Put columns names in the first row')); + $generalOptions->addProperty($leaf); + $leaf = new SelectPropertyItem(); + $leaf->setName('edition'); + $leaf->setValues( + array( + 'win' => 'Windows', + 'mac_excel2003' => 'Excel 2003 / Macintosh', + 'mac_excel2008' => 'Excel 2008 / Macintosh' + ) + ); + $leaf->setText(__('Excel edition:')); + $generalOptions->addProperty($leaf); + $leaf = new HiddenPropertyItem(); + $leaf->setName('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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/export/ExportHtmlword.class.php b/sources/libraries/plugins/export/ExportHtmlword.class.php new file mode 100644 index 0000000..edd24aa --- /dev/null +++ b/sources/libraries/plugins/export/ExportHtmlword.class.php @@ -0,0 +1,647 @@ +setProperties(); + } + + /** + * Sets the export HTML-Word properties + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/RadioPropertyItem.class.php"; + include_once "$props/options/items/TextPropertyItem.class.php"; + include_once "$props/options/items/BoolPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // what to dump (structure/data/both) + $dumpWhat = new OptionsPropertyMainGroup(); + $dumpWhat->setName("dump_what"); + $dumpWhat->setText(__('Dump table')); + // create primary items and add them to the group + $leaf = new RadioPropertyItem(); + $leaf->setName("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(); + $dataOptions->setName("dump_what"); + $dataOptions->setText(__('Data dump options')); + $dataOptions->setForce('structure'); + // create primary items and add them to the group + $leaf = new TextPropertyItem(); + $leaf->setName("null"); + $leaf->setText(__('Replace NULL with:')); + $dataOptions->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName("columns"); + $leaf->setText(__('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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader () + { + global $charset_of_file; + + return PMA_exportOutputHandler( + ' + + + + + + + ' + ); + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter () + { + return PMA_exportOutputHandler(''); + } + + /** + * Outputs database header + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + return PMA_exportOutputHandler( + '

      ' . __('Database') . ' ' . htmlspecialchars($db) . '

      ' + ); + } + + /** + * 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 + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + global $what; + + if (! PMA_exportOutputHandler( + '

      ' + . __('Dumping data for table') . ' ' . htmlspecialchars($table) + . '

      ' + )) { + return false; + } + if (! PMA_exportOutputHandler( + '' + )) { + return false; + } + + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, null, PMA_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++) { + $schema_insert .= ''; + } // end for + $schema_insert .= ''; + if (! PMA_exportOutputHandler($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 (! PMA_exportOutputHandler($schema_insert)) { + return false; + } + } // end while + $GLOBALS['dbi']->freeResult($result); + if (! PMA_exportOutputHandler('
      ')) { + 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 + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf) + { + $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) { + $schema_insert .= $this->formatOneColumnDefinition( + $column, + $unique_keys + ); + $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 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 + * 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 + * + * @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 + ) { + // 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 + if ($do_relation && ! empty($cfgRelation['relation'])) { + // Find which tables are related with the current one and write it in + // an array + $res_rel = PMA_getForeigners($db, $table); + + if ($res_rel && count($res_rel) > 0) { + $have_rel = true; + } else { + $have_rel = false; + } + } else { + $have_rel = false; + } // end if + + /** + * Displays the table structure + */ + $schema_insert .= ''; + + $columns_cnt = 4; + if ($do_relation && $have_rel) { + $columns_cnt++; + } + if ($do_comments && $cfgRelation['commwork']) { + $columns_cnt++; + } + if ($do_mime && $cfgRelation['mimework']) { + $columns_cnt++; + } + + $schema_insert .= ''; + $schema_insert .= ''; + $schema_insert .= ''; + $schema_insert .= ''; + $schema_insert .= ''; + if ($do_relation && $have_rel) { + $schema_insert .= ''; + } + if ($do_comments) { + $schema_insert .= ''; + $comments = PMA_getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $schema_insert .= ''; + $mime_map = PMA_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) { + $schema_insert .= $this->formatOneColumnDefinition( + $column, + $unique_keys + ); + $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 .= '
      ' + . (isset($res_rel[$field_name]) + ? htmlspecialchars( + $res_rel[$field_name]['foreign_table'] + . ' (' . $res_rel[$field_name]['foreign_field'] + . ')' + ) + : '') . '' + . (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 + * + * @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 + ) { + $dump = ''; + + switch($export_mode) { + case 'create_table': + $dump .= '

      ' + . __('Table structure for table') . ' ' . htmlspecialchars($table) + . '

      '; + $dump .= $this->getTableDef( + $db, $table, $crlf, $error_url, $do_relation, $do_comments, $do_mime, + $dates + ); + break; + case 'triggers': + $dump = ''; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + if ($triggers) { + $dump .= '

      ' + . __('Triggers') . ' ' . htmlspecialchars($table) + . '

      '; + $dump .= $this->getTriggers($db, $table); + } + break; + case 'create_view': + $dump .= '

      ' + . __('Structure for view') . ' ' . htmlspecialchars($table) + . '

      '; + $dump .= $this->getTableDef( + $db, $table, $crlf, $error_url, $do_relation, $do_comments, $do_mime, + $dates, true, true + ); + break; + case 'stand_in': + $dump .= '

      ' + . __('Stand-in structure for view') . ' ' . htmlspecialchars($table) + . '

      '; + // export a stand-in definition to resolve view dependencies + $dump .= $this->getTableDefStandIn($db, $table, $crlf); + } // end switch + + return PMA_exportOutputHandler($dump); + } + + /** + * Formats the definition for one column + * + * @param array $column info about this column + * @param array $unique_keys unique keys of the table + * + * @return string Formatted column definition + */ + protected function formatOneColumnDefinition( + $column, $unique_keys + ) { + $definition = ''; + + $extracted_columnspec + = PMA_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($column['Field']) . $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/sources/libraries/plugins/export/ExportJson.class.php b/sources/libraries/plugins/export/ExportJson.class.php new file mode 100644 index 0000000..59bc477 --- /dev/null +++ b/sources/libraries/plugins/export/ExportJson.class.php @@ -0,0 +1,210 @@ +setProperties(); + } + + /** + * Sets the export JSON properties + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/HiddenPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem(); + $leaf->setName("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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader () + { + PMA_exportOutputHandler( + '/**' . $GLOBALS['crlf'] + . ' Export to JSON plugin for PHPMyAdmin' . $GLOBALS['crlf'] + . ' @version 0.1' . $GLOBALS['crlf'] + . ' */' . $GLOBALS['crlf'] . $GLOBALS['crlf'] + ); + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter () + { + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + PMA_exportOutputHandler('// Database \'' . $db . '\'' . $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 + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + $result = $GLOBALS['dbi']->query( + $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED + ); + $columns_cnt = $GLOBALS['dbi']->numFields($result); + + for ($i = 0; $i < $columns_cnt; $i++) { + $columns[$i] = stripslashes($GLOBALS['dbi']->fieldName($result, $i)); + } + unset($i); + + $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 . '.' . $table . $crlf . $crlf; + $buffer .= '['; + } else { + $buffer = ', '; + } + + if (! PMA_exportOutputHandler($buffer)) { + return false; + } + + $data = array(); + + for ($i = 0; $i < $columns_cnt; $i++) { + $data[$columns[$i]] = $record[$i]; + } + + if (! PMA_exportOutputHandler(json_encode($data))) { + return false; + } + } + + if ($record_cnt) { + if (! PMA_exportOutputHandler(']')) { + return false; + } + } + + $GLOBALS['dbi']->freeResult($result); + return true; + } +} +?> diff --git a/sources/libraries/plugins/export/ExportLatex.class.php b/sources/libraries/plugins/export/ExportLatex.class.php new file mode 100644 index 0000000..dbd55df --- /dev/null +++ b/sources/libraries/plugins/export/ExportLatex.class.php @@ -0,0 +1,657 @@ +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; + } + + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/BoolPropertyItem.class.php"; + include_once "$props/options/items/RadioPropertyItem.class.php"; + include_once "$props/options/items/TextPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + // create primary items and add them to the group + $leaf = new BoolPropertyItem(); + $leaf->setName("caption"); + $leaf->setText(__('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(); + $dumpWhat->setName("dump_what"); + $dumpWhat->setText(__('Dump table')); + // create primary items and add them to the group + $leaf = new RadioPropertyItem(); + $leaf->setName("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(); + $structureOptions->setName("structure"); + $structureOptions->setText(__('Object creation options')); + $structureOptions->setForce('data'); + // create primary items and add them to the group + $leaf = new TextPropertyItem(); + $leaf->setName("structure_caption"); + $leaf->setText(__('Table caption:')); + $leaf->setDoc('faq6-27'); + $structureOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("structure_continued_caption"); + $leaf->setText(__('Table caption (continued):')); + $leaf->setDoc('faq6-27'); + $structureOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("structure_label"); + $leaf->setText(__('Label key:')); + $leaf->setDoc('faq6-27'); + $structureOptions->addProperty($leaf); + if (! empty($GLOBALS['cfgRelation']['relation'])) { + $leaf = new BoolPropertyItem(); + $leaf->setName("relation"); + $leaf->setText(__('Display foreign key relationships')); + $structureOptions->addProperty($leaf); + } + $leaf = new BoolPropertyItem(); + $leaf->setName("comments"); + $leaf->setText(__('Display comments')); + $structureOptions->addProperty($leaf); + if (! empty($GLOBALS['cfgRelation']['mimework'])) { + $leaf = new BoolPropertyItem(); + $leaf->setName("mime"); + $leaf->setText(__('Display MIME types')); + $structureOptions->addProperty($leaf); + } + // add the main group to the root group + $exportSpecificOptions->addProperty($structureOptions); + } + + // data options main group + $dataOptions = new OptionsPropertyMainGroup(); + $dataOptions->setName("data"); + $dataOptions->setText(__('Data dump options')); + $dataOptions->setForce('structure'); + // create primary items and add them to the group + $leaf = new BoolPropertyItem(); + $leaf->setName("columns"); + $leaf->setText(__('Put columns names in the first row:')); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("data_caption"); + $leaf->setText(__('Table caption:')); + $leaf->setDoc('faq6-27'); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("data_continued_caption"); + $leaf->setText(__('Table caption (continued):')); + $leaf->setDoc('faq6-27'); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("data_label"); + $leaf->setText(__('Label key:')); + $leaf->setDoc('faq6-27'); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName('null'); + $leaf->setText(__('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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader () + { + global $crlf; + global $cfg; + + $head = '% phpMyAdmin LaTeX Dump' . $crlf + . '% version ' . PMA_VERSION . $crlf + . '% http://www.phpmyadmin.net' . $crlf + . '%' . $crlf + . '% ' . __('Host:') . ' ' . $cfg['Server']['host']; + if (! empty($cfg['Server']['port'])) { + $head .= ':' . $cfg['Server']['port']; + } + $head .= $crlf + . '% ' . __('Generation Time:') . ' ' + . PMA_Util::localisedDate() . $crlf + . '% ' . __('Server version:') . ' ' . PMA_MYSQL_STR_VERSION . $crlf + . '% ' . __('PHP Version:') . ' ' . phpversion() . $crlf; + return PMA_exportOutputHandler($head); + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter () + { + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + global $crlf; + $head = '% ' . $crlf + . '% ' . __('Database:') . ' ' . '\'' . $db . '\'' . $crlf + . '% ' . $crlf; + return PMA_exportOutputHandler($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 + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + $result = $GLOBALS['dbi']->tryQuery( + $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED + ); + + $columns_cnt = $GLOBALS['dbi']->numFields($result); + for ($i = 0; $i < $columns_cnt; $i++) { + $columns[$i] = $GLOBALS['dbi']->fieldName($result, $i); + } + unset($i); + + $buffer = $crlf . '%' . $crlf . '% ' . __('Data:') . ' ' . $table + . $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{' + . PMA_Util::expandUserString( + $GLOBALS['latex_data_caption'], + array( + 'texEscape', + get_class($this), + 'libraries/plugins/export/' . get_class($this) . ".class.php" + ), + array('table' => $table, 'database' => $db) + ) + . '} \\label{' + . PMA_Util::expandUserString( + $GLOBALS['latex_data_label'], + null, + array('table' => $table, 'database' => $db) + ) + . '} \\\\'; + } + if (! PMA_exportOutputHandler($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[$i])) . '}} & '; + } + + $buffer = substr($buffer, 0, -2) . '\\\\ \\hline \hline '; + if (! PMA_exportOutputHandler($buffer . ' \\endfirsthead ' . $crlf)) { + return false; + } + if (isset($GLOBALS['latex_caption'])) { + if (! PMA_exportOutputHandler( + '\\caption{' + . PMA_Util::expandUserString( + $GLOBALS['latex_data_continued_caption'], + array( + 'texEscape', + get_class($this), + 'libraries/plugins/export/' + . get_class($this) . ".class.php" + ), + array('table' => $table, 'database' => $db) + ) + . '} \\\\ ' + )) { + return false; + } + } + if (! PMA_exportOutputHandler($buffer . '\\endhead \\endfoot' . $crlf)) { + return false; + } + } else { + if (! PMA_exportOutputHandler('\\\\ \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 (! PMA_exportOutputHandler($buffer)) { + return false; + } + } + + $buffer = ' \\end{longtable}' . $crlf; + if (! PMA_exportOutputHandler($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 + * + * @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 + ) { + 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 + if ($do_relation && ! empty($cfgRelation['relation'])) { + // Find which tables are related with the current one and write it in + // an array + $res_rel = PMA_getForeigners($db, $table); + + if ($res_rel && count($res_rel) > 0) { + $have_rel = true; + } else { + $have_rel = false; + } + } else { + $have_rel = false; + } // end if + + /** + * Displays the table structure + */ + $buffer = $crlf . '%' . $crlf . '% ' . __('Structure:') . ' ' . $table + . $crlf . '%' . $crlf . ' \\begin{longtable}{'; + if (! PMA_exportOutputHandler($buffer)) { + return false; + } + + $columns_cnt = 4; + $alignment = '|l|c|c|c|'; + if ($do_relation && $have_rel) { + $columns_cnt++; + $alignment .= 'l|'; + } + if ($do_comments) { + $columns_cnt++; + $alignment .= 'l|'; + } + if ($do_mime && $cfgRelation['mimework']) { + $columns_cnt++; + $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 = PMA_getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $header .= ' & \\multicolumn{1}{|c|}{\\textbf{MIME}}'; + $mime_map = PMA_getMIME($db, $table, true); + } + + // Table caption for first page and label + if (isset($GLOBALS['latex_caption'])) { + $buffer .= ' \\caption{' + . PMA_Util::expandUserString( + $GLOBALS['latex_structure_caption'], + array( + 'texEscape', + get_class($this), + 'libraries/plugins/export/' . get_class($this) . ".class.php" + ), + array('table' => $table, 'database' => $db) + ) + . '} \\label{' + . PMA_Util::expandUserString( + $GLOBALS['latex_structure_label'], + null, + array('table' => $table, 'database' => $db) + ) + . '} \\\\' . $crlf; + } + $buffer .= $header . ' \\\\ \\hline \\hline' . $crlf + . '\\endfirsthead' . $crlf; + // Table caption on next pages + if (isset($GLOBALS['latex_caption'])) { + $buffer .= ' \\caption{' + . PMA_Util::expandUserString( + $GLOBALS['latex_structure_continued_caption'], + array( + 'texEscape', + get_class($this), + 'libraries/plugins/export/' . get_class($this) . ".class.php" + ), + array('table' => $table, 'database' => $db) + ) + . '} \\\\ ' . $crlf; + } + $buffer .= $header . ' \\\\ \\hline \\hline \\endhead \\endfoot ' . $crlf; + + if (! PMA_exportOutputHandler($buffer)) { + return false; + } + + $fields = $GLOBALS['dbi']->getColumns($db, $table); + foreach ($fields as $row) { + $extracted_columnspec + = PMA_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 = $row['Field']; + + $local_buffer = $field_name . "\000" . $type . "\000" + . (($row['Null'] == '' || $row['Null'] == 'NO') + ? __('No') : __('Yes')) + . "\000" . (isset($row['Default']) ? $row['Default'] : ''); + + if ($do_relation && $have_rel) { + $local_buffer .= "\000"; + if (isset($res_rel[$field_name])) { + $local_buffer .= $res_rel[$field_name]['foreign_table'] . ' (' + . $res_rel[$field_name]['foreign_field'] . ')'; + } + } + 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=strpos($local_buffer, "\000"); + $local_buffer = '\\textit{' + . substr($local_buffer, 0, $pos) + . '}' . substr($local_buffer, $pos); + } + if (in_array($field_name, $unique_keys)) { + $pos=strpos($local_buffer, "\000"); + $local_buffer = '\\textbf{' + . substr($local_buffer, 0, $pos) + . '}' . substr($local_buffer, $pos); + } + $buffer = str_replace("\000", ' & ', $local_buffer); + $buffer .= ' \\\\ \\hline ' . $crlf; + + if (! PMA_exportOutputHandler($buffer)) { + return false; + } + } // end while + + $buffer = ' \\end{longtable}' . $crlf; + return PMA_exportOutputHandler($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/sources/libraries/plugins/export/ExportMediawiki.class.php b/sources/libraries/plugins/export/ExportMediawiki.class.php new file mode 100644 index 0000000..7038144 --- /dev/null +++ b/sources/libraries/plugins/export/ExportMediawiki.class.php @@ -0,0 +1,366 @@ +setProperties(); + } + + /** + * Sets the export MediaWiki properties + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/groups/OptionsPropertySubgroup.class.php"; + include_once "$props/options/items/MessageOnlyPropertyItem.class.php"; + include_once "$props/options/items/RadioPropertyItem.class.php"; + include_once "$props/options/items/BoolPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + $generalOptions->setText(__('Dump table')); + + // what to dump (structure/data/both) + $subgroup = new OptionsPropertySubgroup(); + $subgroup->setName("dump_table"); + $subgroup->setText("Dump table"); + $leaf = new RadioPropertyItem(); + $leaf->setName('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(); + $leaf->setName("caption"); + $leaf->setText(__('Export table names')); + $generalOptions->addProperty($leaf); + + // export table headers + $leaf = new BoolPropertyItem(); + $leaf->setName("headers"); + $leaf->setText(__('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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * 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 + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + 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 + * + * @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 + ) { + 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 " + . PMA_Util::backquote($table) + ); + + // Begin the table construction + $output .= "{| class=\"wikitable\" style=\"text-align:center;\"" + . $this->_exportCRLF(); + + // Add the table name + if ($GLOBALS['mediawiki_caption']) { + $output .= "|+'''" . $table . "'''" . $this->_exportCRLF(); + } + + // Add the table headers + if ($GLOBALS['mediawiki_headers']) { + $output .= "|- style=\"background:#ffdead;\"" . $this->_exportCRLF(); + $output .= "! style=\"background:#ffffff\" | " + . $this->_exportCRLF(); + for ($i = 0; $i < $row_cnt; ++$i) { + $output .= " | " . $columns[$i]['Field']. $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 PMA_exportOutputHandler($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 + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query + ) { + // Print data comment + $output = $this->_exportComment( + "Table data for ". PMA_Util::backquote($table) + ); + + // 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 ($GLOBALS['mediawiki_caption']) { + $output .= "|+'''" . $table . "'''" . $this->_exportCRLF(); + } + + // Add the table headers + if ($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) { + $output .= " ! " . $column . "" . $this->_exportCRLF(); + } + } + } + + // Get the table data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, null, PMA_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 PMA_exportOutputHandler($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 http://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/sources/libraries/plugins/export/ExportOds.class.php b/sources/libraries/plugins/export/ExportOds.class.php new file mode 100644 index 0000000..f3d40d2 --- /dev/null +++ b/sources/libraries/plugins/export/ExportOds.class.php @@ -0,0 +1,336 @@ +setProperties(); + } + + /** + * Sets the export ODS properties + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/TextPropertyItem.class.php"; + include_once "$props/options/items/BoolPropertyItem.class.php"; + include_once "$props/options/items/HiddenPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + // create primary items and add them to the group + $leaf = new TextPropertyItem(); + $leaf->setName("null"); + $leaf->setText(__('Replace NULL with:')); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName("columns"); + $leaf->setText(__('Put columns names in the first row')); + $generalOptions->addProperty($leaf); + $leaf = new HiddenPropertyItem(); + $leaf->setName("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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * 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'] .= '' + . '' + . ''; + if (! PMA_exportOutputHandler( + PMA_createOpenDocument( + 'application/vnd.oasis.opendocument.spreadsheet', + $GLOBALS['ods_buffer'] + ) + )) { + return false; + } + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + global $what; + + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, null, PMA_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++) { + $GLOBALS['ods_buffer'] .= + '' + . '' + . htmlspecialchars( + stripslashes($GLOBALS['dbi']->fieldName($result, $i)) + ) + . '' + . ''; + } // 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 (! 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/sources/libraries/plugins/export/ExportOdt.class.php b/sources/libraries/plugins/export/ExportOdt.class.php new file mode 100644 index 0000000..5df6f65 --- /dev/null +++ b/sources/libraries/plugins/export/ExportOdt.class.php @@ -0,0 +1,734 @@ +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; + } + + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/TextPropertyItem.class.php"; + include_once "$props/options/items/BoolPropertyItem.class.php"; + include_once "$props/options/items/HiddenPropertyItem.class.php"; + include_once "$props/options/items/RadioPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // what to dump (structure/data/both) main group + $dumpWhat = new OptionsPropertyMainGroup(); + $dumpWhat->setName("general_opts"); + $dumpWhat->setText(__('Dump table')); + // create primary items and add them to the group + $leaf = new RadioPropertyItem(); + $leaf->setName("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(); + $structureOptions->setName("structure"); + $structureOptions->setText(__('Object creation options')); + $structureOptions->setForce('data'); + // create primary items and add them to the group + if (! empty($GLOBALS['cfgRelation']['relation'])) { + $leaf = new BoolPropertyItem(); + $leaf->setName("relation"); + $leaf->setText(__('Display foreign key relationships')); + $structureOptions->addProperty($leaf); + } + $leaf = new BoolPropertyItem(); + $leaf->setName("comments"); + $leaf->setText(__('Display comments')); + $structureOptions->addProperty($leaf); + if (! empty($GLOBALS['cfgRelation']['mimework'])) { + $leaf = new BoolPropertyItem(); + $leaf->setName("mime"); + $leaf->setText(__('Display MIME types')); + $structureOptions->addProperty($leaf); + } + // add the main group to the root group + $exportSpecificOptions->addProperty($structureOptions); + } + + // data options main group + $dataOptions = new OptionsPropertyMainGroup(); + $dataOptions->setName("data"); + $dataOptions->setText(__('Data dump options')); + $dataOptions->setForce('structure'); + // create primary items and add them to the group + $leaf = new BoolPropertyItem(); + $leaf->setName("columns"); + $leaf->setText(__('Put columns names in the first row')); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName('null'); + $leaf->setText(__('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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * 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 (! PMA_exportOutputHandler( + PMA_createOpenDocument( + 'application/vnd.oasis.opendocument.text', + $GLOBALS['odt_buffer'] + ) + )) { + return false; + } + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + $GLOBALS['odt_buffer'] .= + '' + . __('Database') . ' ' . htmlspecialchars($db) + . ''; + 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 + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + global $what; + + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, null, PMA_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) + . '' + . '' + . ''; + + // If required, get fields name at the first line + if (isset($GLOBALS[$what . '_columns'])) { + $GLOBALS['odt_buffer'] .= ''; + for ($i = 0; $i < $fields_cnt; $i++) { + $GLOBALS['odt_buffer'] .= + '' + . '' + . htmlspecialchars( + stripslashes($GLOBALS['dbi']->fieldName($result, $i)) + ) + . '' + . ''; + } // 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 (! 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 + * + * @return bool true + */ + public function getTableDefStandIn($db, $view, $crlf) + { + /** + * 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) { + $GLOBALS['odt_buffer'] .= $this->formatOneColumnDefinition($column); + $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 + * + * @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 + ) { + global $cfgRelation; + + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + // Check if we can use Relations + if ($do_relation && ! empty($cfgRelation['relation'])) { + // Find which tables are related with the current one and write it in + // an array + $res_rel = PMA_getForeigners($db, $table); + + if ($res_rel && count($res_rel) > 0) { + $have_rel = true; + } else { + $have_rel = false; + } + } else { + $have_rel = false; + } // end if + + /** + * 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 = PMA_getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $GLOBALS['odt_buffer'] .= '' + . '' . __('MIME type') . '' + . ''; + $mime_map = PMA_getMIME($db, $table, true); + } + $GLOBALS['odt_buffer'] .= ''; + + $columns = $GLOBALS['dbi']->getColumns($db, $table); + foreach ($columns as $column) { + $field_name = $column['Field']; + $GLOBALS['odt_buffer'] .= $this->formatOneColumnDefinition($column); + + if ($do_relation && $have_rel) { + if (isset($res_rel[$field_name])) { + $GLOBALS['odt_buffer'] .= + '' + . '' + . htmlspecialchars( + $res_rel[$field_name]['foreign_table'] + . ' (' . $res_rel[$field_name]['foreign_field'] . ')' + ) + . '' + . ''; + } + } + 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 + * + * @return bool true + */ + protected function getTriggers($db, $table) + { + $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 + * + * @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 + ) { + switch($export_mode) { + case 'create_table': + $GLOBALS['odt_buffer'] .= + '' + . __('Table structure for table') . ' ' . + htmlspecialchars($table) + . ''; + $this->getTableDef( + $db, $table, $crlf, $error_url, $do_relation, $do_comments, + $do_mime, $dates + ); + break; + case 'triggers': + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + if ($triggers) { + $GLOBALS['odt_buffer'] .= + '' + . __('Triggers') . ' ' + . htmlspecialchars($table) + . ''; + $this->getTriggers($db, $table); + } + break; + case 'create_view': + $GLOBALS['odt_buffer'] .= + '' + . __('Structure for view') . ' ' + . htmlspecialchars($table) + . ''; + $this->getTableDef( + $db, $table, $crlf, $error_url, $do_relation, $do_comments, + $do_mime, $dates, true, true + ); + break; + case 'stand_in': + $GLOBALS['odt_buffer'] .= + '' + . __('Stand-in structure for view') . ' ' + . htmlspecialchars($table) + . ''; + // export a stand-in definition to resolve view dependencies + $this->getTableDefStandIn($db, $table, $crlf); + } // end switch + + return true; + } // end of the '$this->exportStructure' function + + /** + * Formats the definition for one column + * + * @param array $column info about this column + * + * @return string Formatted column definition + */ + protected function formatOneColumnDefinition($column) + { + $field_name = $column['Field']; + $definition = ''; + $definition .= '' + . '' . htmlspecialchars($field_name) . '' + . ''; + + $extracted_columnspec + = PMA_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/sources/libraries/plugins/export/ExportPdf.class.php b/sources/libraries/plugins/export/ExportPdf.class.php new file mode 100644 index 0000000..ffabb96 --- /dev/null +++ b/sources/libraries/plugins/export/ExportPdf.class.php @@ -0,0 +1,268 @@ +initSpecificVariables(); + + $this->setProperties(); + } + + /** + * Initialize the local variables that are used for export PDF + * + * @return void + */ + protected function initSpecificVariables() + { + $this->_setPdfReportTitle(""); + $this->_setPdf(new PMA_ExportPdf('L', 'pt', 'A3')); + } + + /** + * Sets the export PDF properties + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/MessageOnlyPropertyItem.class.php"; + include_once "$props/options/items/TextPropertyItem.class.php"; + include_once "$props/options/items/HiddenPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + // create primary items and add them to the group + $leaf = new MessageOnlyPropertyItem(); + $leaf->setName("explanation"); + $leaf->setText( + __('(Generates a report containing the data of a single table)') + ); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("report_title"); + $leaf->setText(__('Report title:')); + $generalOptions->addProperty($leaf); + $leaf = new HiddenPropertyItem(); + $leaf->setName("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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * 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(): + if (! PMA_exportOutputHandler($pdf->getPDFData())) { + return false; + } + + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + $pdf = $this->_getPdf(); + + $attr = array('currentDb' => $db, 'currentTable' => $table); + $pdf->setAttributes($attr); + $pdf->mysqlReport($sql_query); + + return true; + } // end of the 'PMA_exportData()' function + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the PMA_ExportPdf instance + * + * @return PMA_ExportPdf + */ + private function _getPdf() + { + return $this->_pdf; + } + + /** + * Instantiates the PMA_ExportPdf class + * + * @param string $pdf PMA_ExportPdf 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; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/export/ExportPhparray.class.php b/sources/libraries/plugins/export/ExportPhparray.class.php new file mode 100644 index 0000000..78d8cbe --- /dev/null +++ b/sources/libraries/plugins/export/ExportPhparray.class.php @@ -0,0 +1,229 @@ +setProperties(); + } + + /** + * Sets the export PHP Array properties + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/HiddenPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem(); + $leaf->setName("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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader () + { + PMA_exportOutputHandler( + 'query( + $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED + ); + + $columns_cnt = $GLOBALS['dbi']->numFields($result); + for ($i = 0; $i < $columns_cnt; $i++) { + $columns[$i] = stripslashes($GLOBALS['dbi']->fieldName($result, $i)); + } + unset($i); + + // fix variable names (based on + // http://www.php.net/manual/language.variables.basics.php) + if (! preg_match( + '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', + $table + )) { + // fix invalid characters in variable names by replacing them with + // underscores + $tablefixed = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '_', $table); + + // variable name must not start with a number or dash... + if (preg_match('/^[a-zA-Z_\x7f-\xff]/', $tablefixed) == false) { + $tablefixed = '_' . $tablefixed; + } + } else { + $tablefixed = $table; + } + + $buffer = ''; + $record_cnt = 0; + // Output table name as comment + $buffer .= $crlf . '// ' + . PMA_Util::backquote($db) . '.' + . PMA_Util::backquote($table) . $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 (! PMA_exportOutputHandler($buffer)) { + return false; + } + + $GLOBALS['dbi']->freeResult($result); + return true; + } +} +?> diff --git a/sources/libraries/plugins/export/ExportSql.class.php b/sources/libraries/plugins/export/ExportSql.class.php new file mode 100644 index 0000000..1151943 --- /dev/null +++ b/sources/libraries/plugins/export/ExportSql.class.php @@ -0,0 +1,2017 @@ +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) { + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/groups/OptionsPropertySubgroup.class.php"; + include_once "$props/options/items/BoolPropertyItem.class.php"; + include_once "$props/options/items/MessageOnlyPropertyItem.class.php"; + include_once "$props/options/items/RadioPropertyItem.class.php"; + include_once "$props/options/items/SelectPropertyItem.class.php"; + include_once "$props/options/items/TextPropertyItem.class.php"; + include_once "$props/options/items/NumberPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + + // comments + $subgroup = new OptionsPropertySubgroup(); + $subgroup->setName("include_comments"); + $leaf = new BoolPropertyItem(); + $leaf->setName('include_comments'); + $leaf->setText( + __( + 'Display comments (includes info such as export' + . ' timestamp, PHP version, and server version)' + ) + ); + $subgroup->setSubgroupHeader($leaf); + + $leaf = new TextPropertyItem(); + $leaf->setName('header_comment'); + $leaf->setText( + __('Additional custom header comment (\n splits lines):') + ); + $subgroup->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName('dates'); + $leaf->setText( + __( + 'Include a timestamp of when databases were created, last' + . ' updated, and last checked' + ) + ); + $subgroup->addProperty($leaf); + if (! empty($GLOBALS['cfgRelation']['relation'])) { + $leaf = new BoolPropertyItem(); + $leaf->setName('relation'); + $leaf->setText(__('Display foreign key relationships')); + $subgroup->addProperty($leaf); + } + if (! empty($GLOBALS['cfgRelation']['mimework'])) { + $leaf = new BoolPropertyItem(); + $leaf->setName('mime'); + $leaf->setText(__('Display MIME types')); + $subgroup->addProperty($leaf); + } + $generalOptions->addProperty($subgroup); + + // enclose in a transaction + $leaf = new BoolPropertyItem(); + $leaf->setName("use_transaction"); + $leaf->setText(__('Enclose export in a transaction')); + $leaf->setDoc( + array( + 'programs', + 'mysqldump', + 'option_mysqldump_single-transaction' + ) + ); + $generalOptions->addProperty($leaf); + + // disable foreign key checks + $leaf = new BoolPropertyItem(); + $leaf->setName("disable_fk"); + $leaf->setText(__('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(); + $leaf->setName("views_as_tables"); + $leaf->setText(__('Export views as tables')); + $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(); + $leaf->setName("compatibility"); + $leaf->setText( + __( + '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); + } + + // server export options + if ($plugin_param['export_type'] == 'server') { + $leaf = new BoolPropertyItem(); + $leaf->setName("drop_database"); + $leaf->setText( + sprintf(__('Add %s statement'), 'DROP DATABASE') + ); + $generalOptions->addProperty($leaf); + } + + // what to dump (structure/data/both) + $subgroup = new OptionsPropertySubgroup(); + $subgroup->setName("dump_table"); + $subgroup->setText("Dump table"); + $leaf = new RadioPropertyItem(); + $leaf->setName('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(); + $structureOptions->setName("structure"); + $structureOptions->setText(__('Object creation options')); + $structureOptions->setForce('data'); + + // begin SQL Statements + $subgroup = new OptionsPropertySubgroup(); + $leaf = new MessageOnlyPropertyItem(); + $leaf->setName('add_statements'); + $leaf->setText(__('Add statements:')); + $subgroup->setSubgroupHeader($leaf); + + if ($plugin_param['export_type'] != 'table') { + $leaf = new BoolPropertyItem(); + $leaf->setName('create_database'); + $create_clause = 'CREATE DATABASE / USE'; + $leaf->setText(sprintf(__('Add %s statement'), $create_clause)); + $subgroup->addProperty($leaf); + } + + if ($plugin_param['export_type'] == 'table') { + if (PMA_Table::isView($GLOBALS['db'], $GLOBALS['table'])) { + $drop_clause = 'DROP VIEW'; + } else { + $drop_clause = 'DROP TABLE'; + } + } else { + if (PMA_DRIZZLE) { + $drop_clause = 'DROP TABLE'; + } else { + $drop_clause = 'DROP TABLE / VIEW / PROCEDURE' + . ' / FUNCTION'; + if (PMA_MYSQL_INT_VERSION > 50100) { + $drop_clause .= ' / EVENT'; + } + } + } + $leaf = new BoolPropertyItem(); + $leaf->setName('drop_table'); + $leaf->setText(sprintf(__('Add %s statement'), $drop_clause)); + $subgroup->addProperty($leaf); + // Drizzle doesn't support procedures and functions + if (! PMA_DRIZZLE) { + $leaf = new BoolPropertyItem(); + $leaf->setName('procedure_function'); + $leaf->setText( + sprintf( + __('Add %s statement'), + 'CREATE PROCEDURE / FUNCTION' + . (PMA_MYSQL_INT_VERSION > 50100 + ? ' / EVENT' : '') + ) + ); + $subgroup->addProperty($leaf); + } + + // begin CREATE TABLE statements + $subgroup_create_table = new OptionsPropertySubgroup(); + $leaf = new BoolPropertyItem(); + $leaf->setName('create_table_statements'); + $leaf->setText(__('CREATE TABLE options:')); + $subgroup_create_table->setSubgroupHeader($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName('if_not_exists'); + $leaf->setText('IF NOT EXISTS'); + $subgroup_create_table->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName('auto_increment'); + $leaf->setText('AUTO_INCREMENT'); + $subgroup_create_table->addProperty($leaf); + $subgroup->addProperty($subgroup_create_table); + $structureOptions->addProperty($subgroup); + + $leaf = new BoolPropertyItem(); + $leaf->setName("backquotes"); + $leaf->setText( + __( + '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(); + $dataOptions->setName("data"); + $dataOptions->setText(__('Data creation options')); + $dataOptions->setForce('structure'); + $leaf = new BoolPropertyItem(); + $leaf->setName("truncate"); + $leaf->setText(__('Truncate table before insert')); + $dataOptions->addProperty($leaf); + + // begin SQL Statements + $subgroup = new OptionsPropertySubgroup(); + $leaf = new MessageOnlyPropertyItem(); + $leaf->setText(__('Instead of INSERT statements, use:')); + $subgroup->setSubgroupHeader($leaf); + // Not supported in Drizzle + if (! PMA_DRIZZLE) { + $leaf = new BoolPropertyItem(); + $leaf->setName("delayed"); + $leaf->setText(__('INSERT DELAYED statements')); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'insert_delayed' + ) + ); + $subgroup->addProperty($leaf); + } + $leaf = new BoolPropertyItem(); + $leaf->setName("ignore"); + $leaf->setText(__('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(); + $leaf->setName("type"); + $leaf->setText(__('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(); + $leaf->setText(__('Syntax to use when inserting data:')); + $subgroup->setSubgroupHeader($leaf); + $leaf = new RadioPropertyItem(); + $leaf->setName("insert_syntax"); + $leaf->setText(__('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) 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(); + $leaf->setName("max_query_size"); + $leaf->setText(__('Maximal length of created query')); + $dataOptions->addProperty($leaf); + + // Dump binary columns in hexadecimal + $leaf = new BoolPropertyItem(); + $leaf->setName("hex_for_blob"); + $leaf->setText( + __( + 'Dump binary columns in hexadecimal notation' + . ' (for example, "abc" becomes 0x616263)' + ) + ); + $dataOptions->addProperty($leaf); + + // Drizzle works only with UTC timezone + if (! PMA_DRIZZLE) { + // Dump time in UTC + $leaf = new BoolPropertyItem(); + $leaf->setName("utc_time"); + $leaf->setText( + __( + '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; + } + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Exports routines (procedures and functions) + * + * @param string $db Database + * + * @return bool Whether it succeeded + */ + public function exportRoutines($db) + { + global $crlf; + + $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->_exportComment() + . $this->_exportComment(__('Procedures')) + . $this->_exportComment(); + + foreach ($procedure_names as $procedure_name) { + if (! empty($GLOBALS['sql_drop_table'])) { + $text .= 'DROP PROCEDURE IF EXISTS ' + . PMA_Util::backquote($procedure_name) + . $delimiter . $crlf; + } + $text .= $GLOBALS['dbi'] + ->getDefinition($db, 'PROCEDURE', $procedure_name) + . $delimiter . $crlf . $crlf; + } + } + + if ($function_names) { + $text .= + $this->_exportComment() + . $this->_exportComment(__('Functions')) + . $this->_exportComment(); + + foreach ($function_names as $function_name) { + if (! empty($GLOBALS['sql_drop_table'])) { + $text .= 'DROP FUNCTION IF EXISTS ' + . PMA_Util::backquote($function_name) + . $delimiter . $crlf; + } + $text .= $GLOBALS['dbi'] + ->getDefinition($db, 'FUNCTION', $function_name) + . $delimiter . $crlf . $crlf; + } + } + + if ($procedure_names || $function_names) { + $text .= 'DELIMITER ;' . $crlf; + } + + if (! empty($text)) { + return PMA_exportOutputHandler($text); + } else { + 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 http://dev.mysql.com/doc/refman/5.0/en/ansi-diff-comments.html + return '--' . (empty($text) ? '' : ' ') . $text . $GLOBALS['crlf']; + } else { + 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']; + } else { + return ''; + } + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + global $crlf, $mysql_charset_map; + + $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 + $charset_of_file = isset($GLOBALS['charset_of_file']) + ? $GLOBALS['charset_of_file'] : ''; + if (! empty($GLOBALS['asfile']) + && isset($mysql_charset_map[$charset_of_file]) + && ! PMA_DRIZZLE + ) { + $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; + } + + /* Restore timezone */ + if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) { + $GLOBALS['dbi']->query('SET time_zone = "' . $GLOBALS['old_tz'] . '"'); + } + + return PMA_exportOutputHandler($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; + global $mysql_charset_map; + + 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('http://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:') . ' ' + . PMA_Util::localisedDate() + ) + . $this->_exportComment( + __('Server version:') . ' ' . PMA_MYSQL_STR_VERSION + ) + . $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') + && ! PMA_DRIZZLE + ) { + $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']) && ! PMA_DRIZZLE) { + // 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_of_file']) + && isset($mysql_charset_map[$GLOBALS['charset_of_file']]) + ) { + // we got a charset from the export dialog + $set_names = $mysql_charset_map[$GLOBALS['charset_of_file']]; + } else { + // by default we use the connection charset + $set_names = $mysql_charset_map['utf-8']; + } + $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; + } + + return PMA_exportOutputHandler($head); + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + global $crlf; + + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + if (isset($GLOBALS['sql_drop_database'])) { + if (! PMA_exportOutputHandler( + 'DROP DATABASE ' + . (isset($GLOBALS['sql_backquotes']) + ? PMA_Util::backquoteCompat($db, $compat) : $db) + . ';' . $crlf + )) { + return false; + } + } + if (isset($GLOBALS['sql_create_database'])) { + $create_query = 'CREATE DATABASE IF NOT EXISTS ' + . (isset($GLOBALS['sql_backquotes']) + ? PMA_Util::backquoteCompat($db, $compat) : $db); + $collation = PMA_getDbCollation($db); + if (PMA_DRIZZLE) { + $create_query .= ' COLLATE ' . $collation; + } else { + if (strpos($collation, '_')) { + $create_query .= ' DEFAULT CHARACTER SET ' + . substr($collation, 0, strpos($collation, '_')) + . ' COLLATE ' . $collation; + } else { + $create_query .= ' DEFAULT CHARACTER SET ' . $collation; + } + } + $create_query .= ';' . $crlf; + if (! PMA_exportOutputHandler($create_query)) { + return false; + } + if (isset($GLOBALS['sql_backquotes']) + && ((isset($GLOBALS['sql_compatibility']) + && $GLOBALS['sql_compatibility'] == 'NONE') + || PMA_DRIZZLE) + ) { + $result = PMA_exportOutputHandler( + 'USE ' . PMA_Util::backquoteCompat($db, $compat) + . ';' . $crlf + ); + } else { + $result = PMA_exportOutputHandler('USE ' . $db . ';' . $crlf); + } + return $result; + } else { + return true; + } + } + + /** + * Outputs database header + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db) + { + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + $head = $this->_exportComment() + . $this->_exportComment( + __('Database:') . ' ' + . (isset($GLOBALS['sql_backquotes']) + ? PMA_Util::backquoteCompat($db, $compat) + : '\'' . $db . '\'') + ) + . $this->_exportComment(); + return PMA_exportOutputHandler($head); + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + global $crlf; + + $result = true; + if (isset($GLOBALS['sql_constraints'])) { + $result = PMA_exportOutputHandler($GLOBALS['sql_constraints']); + unset($GLOBALS['sql_constraints']); + } + + if (($GLOBALS['sql_structure_or_data'] == 'structure' + || $GLOBALS['sql_structure_or_data'] == 'structure_and_data') + && isset($GLOBALS['sql_procedure_function']) + ) { + $text = ''; + $delimiter = '$$'; + + if (PMA_MYSQL_INT_VERSION > 50100) { + $event_names = $GLOBALS['dbi']->fetchResult( + 'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE' + . ' EVENT_SCHEMA= \'' + . PMA_Util::sqlAddSlashes($db, true) + . '\';' + ); + } else { + $event_names = array(); + } + + 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 ' + . PMA_Util::backquote($event_name) + . $delimiter . $crlf; + } + $text .= $GLOBALS['dbi'] + ->getDefinition($db, 'EVENT', $event_name) + . $delimiter . $crlf . $crlf; + } + + $text .= 'DELIMITER ;' . $crlf; + } + + if (! empty($text)) { + $result = PMA_exportOutputHandler($text); + } + } + return $result; + } + + /** + * 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 + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf) + { + $create_query = ''; + if (! empty($GLOBALS['sql_drop_table'])) { + $create_query .= 'DROP VIEW IF EXISTS ' + . PMA_Util::backquote($view) + . ';' . $crlf; + } + + $create_query .= 'CREATE TABLE '; + + if (isset($GLOBALS['sql_if_not_exists']) + && $GLOBALS['sql_if_not_exists'] + ) { + $create_query .= 'IF NOT EXISTS '; + } + $create_query .= PMA_Util::backquote($view) . ' (' . $crlf; + $tmp = array(); + $columns = $GLOBALS['dbi']->getColumnsFull($db, $view); + foreach ($columns as $column_name => $definition) { + $tmp[] = PMA_Util::backquote($column_name) . ' ' . + $definition['Type'] . $crlf; + } + $create_query .= implode(',', $tmp) . ');'; + 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 + * + * @return string resulting schema + */ + private function _getTableDefForView( + $db, + $view, + $crlf, + $add_semicolon = true + ) { + $create_query = "CREATE TABLE"; + if (isset($GLOBALS['sql_if_not_exists'])) { + $create_query .= " IF NOT EXISTS "; + } + $create_query .= PMA_Util::backquote($view) . "(" . $crlf; + + $columns = $GLOBALS['dbi']->getColumns($db, $view, null, true); + + $firstCol = true; + foreach ($columns as $column) { + $extracted_columnspec = PMA_Util::extractColumnSpec($column['Type']); + + if (! $firstCol) { + $create_query .= "," . $crlf; + } + $create_query .= " " . PMA_Util::backquote($column['Field']); + $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 '" + . PMA_Util::sqlAddSlashes($column['Default']) . "'"; + } else if ($column['Null'] == 'YES') { + $create_query .= " DEFAULT NULL"; + } + if (! empty($column['Comment'])) { + $create_query .= " COMMENT '" + . PMA_Util::sqlAddSlashes($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 + * + * @return string resulting schema + */ + public function getTableDef( + $db, + $table, + $crlf, + $error_url, + $show_dates = false, + $add_semicolon = true, + $view = false + ) { + global $sql_drop_table, $sql_backquotes, $sql_constraints, + $sql_constraints_query, $sql_drop_foreign_keys; + + $schema_create = ''; + $auto_increment = ''; + $new_crlf = $crlf; + + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + + // need to use PMA_DatabaseInterface::QUERY_STORE + // with $GLOBALS['dbi']->numRows() in mysqli + $result = $GLOBALS['dbi']->query( + 'SHOW TABLE STATUS FROM ' . PMA_Util::backquote($db) + . ' LIKE \'' . PMA_Util::sqlAddSlashes($table, true) . '\'', + null, + PMA_DatabaseInterface::QUERY_STORE + ); + if ($result != false) { + if ($GLOBALS['dbi']->numRows($result) > 0) { + $tmpres = $GLOBALS['dbi']->fetchAssoc($result); + if (PMA_DRIZZLE && $show_dates) { + // Drizzle doesn't give Create_time and Update_time in + // SHOW TABLE STATUS, add it + $sql ="SELECT + TABLE_CREATION_TIME AS Create_time, + TABLE_UPDATE_TIME AS Update_time + FROM data_dictionary.TABLES + WHERE TABLE_SCHEMA = '" + . PMA_Util::sqlAddSlashes($db) . "' + AND TABLE_NAME = '" + . PMA_Util::sqlAddSlashes($table) . "'"; + $tmpres = array_merge( + $GLOBALS['dbi']->fetchSingleRow($sql), $tmpres + ); + } + // 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 + // It's required for Drizzle because SHOW CREATE TABLE uses + // the value from table's creation time + 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:') . ' ' + . PMA_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:') . ' ' + . PMA_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:') . ' ' + . PMA_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) && ! PMA_Table::isView($db, $table)) { + $schema_create .= 'DROP TABLE IF EXISTS ' + . PMA_Util::backquote($table, $sql_backquotes) . ';' + . $crlf; + } + + // Complete table dump, + // Whether to quote table and column names or not + // Drizzle always quotes names + if (! PMA_DRIZZLE) { + 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, PMA_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 + $result = $GLOBALS['dbi']->tryQuery( + 'SHOW CREATE TABLE ' . PMA_Util::backquote($db) . '.' + . PMA_Util::backquote($table) + ); + // an error can happen, for example the table is crashed + $tmp_error = $GLOBALS['dbi']->getError(); + if ($tmp_error) { + return $this->_exportComment(__('in use') . '(' . $tmp_error . ')'); + } + + 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 (strpos($create_query, "(\r\n ")) { + $create_query = str_replace("\r\n", $crlf, $create_query); + } elseif (strpos($create_query, "(\n ")) { + $create_query = str_replace("\n", $crlf, $create_query); + } elseif (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( + '/' . PMA_Util::backquote($db) . '\./', + '', + $create_query + ); + } + + // Should we use IF NOT EXISTS? + if (isset($GLOBALS['sql_if_not_exists'])) { + $create_query = preg_replace( + '/^CREATE TABLE/', + 'CREATE TABLE IF NOT EXISTS', + $create_query + ); + } + + if ($compat == 'MSSQL') { + $create_query = $this->_makeCreateTableMSSQLCompatible( + $create_query + ); + } + + // Drizzle (checked on 2011.03.13) returns ROW_FORMAT surrounded + // with quotes, which is not accepted by parser + if (PMA_DRIZZLE) { + $create_query = preg_replace( + '/ROW_FORMAT=\'(\S+)\'/', + 'ROW_FORMAT=$1', + $create_query + ); + } + + // are there any constraints to cut out? + if (preg_match('@CONSTRAINT|FOREIGN[\s]+KEY@', $create_query)) { + + // Split the query into lines, so we can easily handle it. + // We know lines are separated by $crlf (done few lines above). + $sql_lines = explode($crlf, $create_query); + $sql_count = count($sql_lines); + + // lets find first line with constraints + for ($i = 0; $i < $sql_count; $i++) { + if (preg_match( + '@^[\s]*(CONSTRAINT|FOREIGN[\s]+KEY)@', + $sql_lines[$i] + )) { + break; + } + } + + // If we really found a constraint + if ($i != $sql_count) { + + // remove, from the end of create statement + $sql_lines[$i - 1] = preg_replace( + '@,$@', + '', + $sql_lines[$i - 1] + ); + + // prepare variable for constraints + if (! isset($sql_constraints)) { + if (isset($GLOBALS['no_constraints_comments'])) { + $sql_constraints = ''; + } else { + $sql_constraints = $crlf + . $this->_exportComment() + . $this->_exportComment( + __('Constraints for dumped tables') + ) + . $this->_exportComment(); + } + } + + // comments for current table + if (! isset($GLOBALS['no_constraints_comments'])) { + $sql_constraints .= $crlf + . $this->_exportComment() + . $this->_exportComment( + __('Constraints for table') + . ' ' + . PMA_Util::backquoteCompat($table, $compat) + ) + . $this->_exportComment(); + } + + // let's do the work + $sql_constraints_query .= 'ALTER TABLE ' + . PMA_Util::backquoteCompat($table, $compat) + . $crlf; + $sql_constraints .= 'ALTER TABLE ' + . PMA_Util::backquoteCompat($table, $compat) + . $crlf; + $sql_drop_foreign_keys .= 'ALTER TABLE ' + . PMA_Util::backquoteCompat($db, $compat) . '.' + . PMA_Util::backquoteCompat($table, $compat) + . $crlf; + + $first = true; + for ($j = $i; $j < $sql_count; $j++) { + if (preg_match( + '@CONSTRAINT|FOREIGN[\s]+KEY@', + $sql_lines[$j] + )) { + if (! $first) { + $sql_constraints .= $crlf; + } + if (strpos($sql_lines[$j], 'CONSTRAINT') === false) { + $tmp_str = preg_replace( + '/(FOREIGN[\s]+KEY)/', + 'ADD \1', + $sql_lines[$j] + ); + $sql_constraints_query .= $tmp_str; + $sql_constraints .= $tmp_str; + } else { + $tmp_str = preg_replace( + '/(CONSTRAINT)/', + 'ADD \1', + $sql_lines[$j] + ); + $sql_constraints_query .= $tmp_str; + $sql_constraints .= $tmp_str; + preg_match( + '/(CONSTRAINT)([\s])([\S]*)([\s])/', + $sql_lines[$j], + $matches + ); + if (! $first) { + $sql_drop_foreign_keys .= ', '; + } + $sql_drop_foreign_keys .= 'DROP FOREIGN KEY ' + . $matches[3]; + } + $first = false; + } else { + break; + } + } + $sql_constraints .= ';' . $crlf; + $sql_constraints_query .= ';'; + + $create_query = implode( + $crlf, + array_slice($sql_lines, 0, $i) + ) + . $crlf + . implode( + $crlf, + array_slice($sql_lines, $j, $sql_count - 1) + ); + unset($sql_lines); + } + } + $schema_create .= $create_query; + } + + // remove a possible "AUTO_INCREMENT = value" clause + // that could be there starting with MySQL 5.0.24 + // in Drizzle it's useless as it contains the value given at table + // creation time + $schema_create = preg_replace( + '/AUTO_INCREMENT\s*=\s*([0-9])+/', + '', + $schema_create + ); + + $schema_create .= ($compat != 'MSSQL') ? $auto_increment : ''; + + $GLOBALS['dbi']->freeResult($result); + return $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 + * + * @return string resulting comments + */ + private function _getTableComments( + $db, + $table, + $crlf, + $do_relation = false, + $do_mime = false + ) { + global $cfgRelation, $sql_backquotes; + + $schema_create = ''; + + // Check if we can use Relations + if ($do_relation && ! empty($cfgRelation['relation'])) { + // Find which tables are related with the current one and write it in + // an array + $res_rel = PMA_getForeigners($db, $table); + + if ($res_rel && count($res_rel) > 0) { + $have_rel = true; + } else { + $have_rel = false; + } + } else { + $have_rel = false; + } // end if + + if ($do_mime && $cfgRelation['mimework']) { + if (! ($mime_map = PMA_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'). ' ' + . PMA_Util::backquote($table, $sql_backquotes) . ':' + ); + @reset($mime_map); + foreach ($mime_map as $mime_field => $mime) { + $schema_create .= + $this->_exportComment( + ' ' + . PMA_Util::backquote($mime_field, $sql_backquotes) + ) + . $this->_exportComment( + ' ' + . PMA_Util::backquote( + $mime['mimetype'], + $sql_backquotes + ) + ); + } + $schema_create .= $this->_exportComment(); + } + + if ($have_rel) { + $schema_create .= $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('RELATIONS FOR TABLE') . ' ' + . PMA_Util::backquote($table, $sql_backquotes) + . ':' + ); + foreach ($res_rel as $rel_field => $rel) { + $schema_create .= + $this->_exportComment( + ' ' + . PMA_Util::backquote($rel_field, $sql_backquotes) + ) + . $this->_exportComment( + ' ' + . PMA_Util::backquote( + $rel['foreign_table'], + $sql_backquotes + ) + . ' -> ' + . PMA_Util::backquote( + $rel['foreign_field'], + $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 + * + * @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 + ) { + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + + $formatted_table_name = (isset($GLOBALS['sql_backquotes'])) + ? PMA_Util::backquoteCompat($table, $compat) + : '\'' . $table . '\''; + $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); + $dump .= $this->_getTableComments($db, $table, $crlf, $relation, $mime); + break; + case 'triggers': + $dump = ''; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + if ($triggers) { + $dump .= $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('Triggers') . ' ' . $formatted_table_name + ) + . $this->_exportComment(); + $delimiter = '//'; + foreach ($triggers as $trigger) { + $dump .= $trigger['drop'] . ';' . $crlf; + $dump .= 'DELIMITER ' . $delimiter . $crlf; + $dump .= $trigger['create']; + $dump .= 'DELIMITER ;' . $crlf; + } + } + 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 ' + . PMA_Util::backquote($table) . ';' . $crlf; + } + $dump .= $this->getTableDef( + $db, $table, $crlf, $error_url, $dates, true, true + ); + } 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 ' + . PMA_Util::backquote($table) . ';' . $crlf; + } + $dump .= $this->_getTableDefForView( + $db, $table, $crlf, true + ); + } + break; + case 'stand_in': + $dump .= + $this->_exportComment( + __('Stand-in structure for view') . ' ' . $formatted_table_name + ) + . $this->_exportComment(); + // export a stand-in definition to resolve view dependencies + $dump .= $this->getTableDefStandIn($db, $table, $crlf); + } // 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 PMA_exportOutputHandler($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 + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + global $current_row, $sql_backquotes; + + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + + $formatted_table_name = (isset($GLOBALS['sql_backquotes'])) + ? PMA_Util::backquoteCompat($table, $compat) + : '\'' . $table . '\''; + + // 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 (PMA_Table::isView($db, $table) + && 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(); + + if (! PMA_exportOutputHandler($head)) { + return false; + } + return true; + } + + // analyze the query to get the true column names, not the aliases + // (this fixes an undefined index, also if Complete inserts + // are used, we did not get the true column name in case of aliases) + $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($sql_query)); + + $result = $GLOBALS['dbi']->tryQuery( + $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED + ); + // a possible error: the table has crashed + $tmp_error = $GLOBALS['dbi']->getError(); + if ($tmp_error) { + return PMA_exportOutputHandler( + $this->_exportComment( + __('Error reading data:') . ' (' . $tmp_error . ')' + ) + ); + } + + if ($result != false) { + $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); + } + + for ($j = 0; $j < $fields_cnt; $j++) { + if (isset($analyzed_sql[0]['select_expr'][$j]['column'])) { + $field_set[$j] = PMA_Util::backquoteCompat( + $analyzed_sql[0]['select_expr'][$j]['column'], + $compat, + $sql_backquotes + ); + } else { + $field_set[$j] = PMA_Util::backquoteCompat( + $fields_meta[$j]->name, + $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 .= PMA_Util::backquoteCompat( + $table, + $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 ' + . PMA_Util::backquoteCompat( + $table, + $compat, + $sql_backquotes + ) . ";"; + $truncatehead = $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('Truncate table before insert') . ' ' + . $formatted_table_name + ) + . $this->_exportComment() + . $crlf; + PMA_exportOutputHandler($truncatehead); + PMA_exportOutputHandler($truncate); + } else { + $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 ' + . PMA_Util::backquoteCompat( + $table, + $compat, + $sql_backquotes + ) + // avoid EOL blank + . ' (' . $fields . ') VALUES'; + } else { + $schema_insert = $sql_command . $insert_delayed .' INTO ' + . PMA_Util::backquoteCompat( + $table, + $compat, + $sql_backquotes + ) + . ' VALUES'; + } + } + + //\x08\\x09, not required + $search = array("\x00", "\x0a", "\x0d", "\x1a"); + $replace = array('\0', '\n', '\r', '\Z'); + $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 (! PMA_exportOutputHandler($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 (! PMA_exportOutputHandler( + 'SET IDENTITY_INSERT ' + . PMA_Util::backquoteCompat( + $table, + $compat + ) + . ' ON ;'.$crlf + )) { + return false; + } + } + $current_row++; + 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') + && $fields_meta[$j]->blob + && isset($GLOBALS['sql_hex_for_blob']) + ) { + // 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'" . PMA_Util::sqlAddSlashes( + PMA_Util::printableBitValue( + $row[$j], $fields_meta[$j]->length + ) + ) + . "'"; + } else { + // something else -> treat as a string + $values[] = '\'' + . str_replace( + $search, $replace, + PMA_Util::sqlAddSlashes($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) + = PMA_Util::getUniqueCondition( + $result, + $fields_cnt, + $fields_meta, + $row + ); + $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) . ')'; + $sql_max_size = $GLOBALS['sql_max_query_size']; + if (isset($sql_max_size) + && $sql_max_size > 0 + && $query_size + strlen($insert_line) > $sql_max_size + ) { + if (! PMA_exportOutputHandler(';' . $crlf)) { + return false; + } + $query_size = 0; + $current_row = 1; + $insert_line = $schema_insert . $insert_line; + } + } + $query_size += strlen($insert_line); + // Other inserts case + } else { + $insert_line = $schema_insert + . '(' + . implode(', ', $values) + . ')'; + } + } + unset($values); + + if (! PMA_exportOutputHandler( + ($current_row == 1 ? '' : $separator . $crlf) + . $insert_line + )) { + return false; + } + + } // end while + + if ($current_row > 0) { + if (! PMA_exportOutputHandler(';' . $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 = PMA_exportOutputHandler( + $crlf . 'SET IDENTITY_INSERT ' + . PMA_Util::backquoteCompat($table, $compat) + . ' OFF;' . $crlf + ); + if (! $outputSucceeded) { + return false; + } + } + } // end if ($result != 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; + } +} diff --git a/sources/libraries/plugins/export/ExportTexytext.class.php b/sources/libraries/plugins/export/ExportTexytext.class.php new file mode 100644 index 0000000..1e6efa0 --- /dev/null +++ b/sources/libraries/plugins/export/ExportTexytext.class.php @@ -0,0 +1,576 @@ +setProperties(); + } + + /** + * Sets the export Texy! text properties + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/RadioPropertyItem.class.php"; + include_once "$props/options/items/BoolPropertyItem.class.php"; + include_once "$props/options/items/TextPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // what to dump (structure/data/both) main group + $dumpWhat = new OptionsPropertyMainGroup(); + $dumpWhat->setName("general_opts"); + $dumpWhat->setText(__('Dump table')); + // create primary items and add them to the group + $leaf = new RadioPropertyItem(); + $leaf->setName("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(); + $dataOptions->setName("data"); + $dataOptions->setText(__('Data dump options')); + $dataOptions->setForce('structure'); + // create primary items and add them to the group + $leaf = new BoolPropertyItem(); + $leaf->setName("columns"); + $leaf->setText(__('Put columns names in the first row')); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName('null'); + $leaf->setText(__('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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * 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 + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + return PMA_exportOutputHandler( + '===' . __('Database') . ' ' . $db . "\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 + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + global $what; + + if (! PMA_exportOutputHandler( + '== ' . __('Dumping data for table') . ' ' . $table . "\n\n" + )) { + return false; + } + + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, null, PMA_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++) { + $text_output .= '|' + . htmlspecialchars( + stripslashes($GLOBALS['dbi']->fieldName($result, $i)) + ); + } // end for + $text_output .= "\n|------\n"; + if (! PMA_exportOutputHandler($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 (! PMA_exportOutputHandler($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 + * + * @return string resulting definition + */ + function getTableDefStandIn($db, $view, $crlf) + { + $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) { + $text_output .= $this->formatOneColumnDefinition($column, $unique_keys); + $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 + * + * @return string resulting schema + */ + function getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $show_dates = false, + $add_semicolon = true, + $view = false + ) { + 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 + if ($do_relation && ! empty($cfgRelation['relation'])) { + // Find which tables are related with the current one and write it in + // an array + $res_rel = PMA_getForeigners($db, $table); + + if ($res_rel && count($res_rel) > 0) { + $have_rel = true; + } else { + $have_rel = false; + } + } else { + $have_rel = false; + } // end if + + /** + * Displays the table structure + */ + + $columns_cnt = 4; + if ($do_relation && $have_rel) { + $columns_cnt++; + } + if ($do_comments && $cfgRelation['commwork']) { + $columns_cnt++; + } + if ($do_mime && $cfgRelation['mimework']) { + $columns_cnt++; + } + + $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 = PMA_getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $text_output .= '|' . htmlspecialchars('MIME'); + $mime_map = PMA_getMIME($db, $table, true); + } + $text_output .= "\n|------\n"; + + $columns = $GLOBALS['dbi']->getColumns($db, $table); + foreach ($columns as $column) { + $text_output .= $this->formatOneColumnDefinition($column, $unique_keys); + $field_name = $column['Field']; + + if ($do_relation && $have_rel) { + $text_output .= '|' + . (isset($res_rel[$field_name]) + ? htmlspecialchars( + $res_rel[$field_name]['foreign_table'] + . ' (' . $res_rel[$field_name]['foreign_field'] . ')' + ) + : ''); + } + 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 + */ + function getTriggers($db, $table) + { + $text_output = "|------\n"; + $text_output .= '|' . __('Column'); + $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 + * + * @return bool Whether it succeeded + */ + function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false + ) { + $dump = ''; + + switch($export_mode) { + case 'create_table': + $dump .= '== ' . __('Table structure for table') . ' ' .$table . "\n\n"; + $dump .= $this->getTableDef( + $db, $table, $crlf, $error_url, $do_relation, $do_comments, + $do_mime, $dates + ); + break; + case 'triggers': + $dump = ''; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + if ($triggers) { + $dump .= '== ' . __('Triggers') . ' ' .$table . "\n\n"; + $dump .= $this->getTriggers($db, $table); + } + break; + case 'create_view': + $dump .= '== ' . __('Structure for view') . ' ' .$table . "\n\n"; + $dump .= $this->getTableDef( + $db, $table, $crlf, $error_url, $do_relation, $do_comments, + $do_mime, $dates, true, true + ); + 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); + } // end switch + + return PMA_exportOutputHandler($dump); + } + + /** + * Formats the definition for one column + * + * @param array $column info about this column + * @param array $unique_keys unique keys for this table + * + * @return string Formatted column definition + */ + function formatOneColumnDefinition( + $column, $unique_keys + ) { + $extracted_columnspec + = PMA_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($column['Field']) . $fmt_post; + $definition .= '|' . htmlspecialchars($type); + $definition .= '|' + . (($column['Null'] == '' || $column['Null'] == 'NO') + ? __('No') : __('Yes')); + $definition .= '|' + . htmlspecialchars( + isset($column['Default']) ? $column['Default'] : '' + ); + return $definition; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/export/ExportXml.class.php b/sources/libraries/plugins/export/ExportXml.class.php new file mode 100644 index 0000000..e36b110 --- /dev/null +++ b/sources/libraries/plugins/export/ExportXml.class.php @@ -0,0 +1,546 @@ +setProperties(); + } + + /** + * Initialize the local variables that are used for export PDF + * + * @return void + */ + protected function initSpecificVariables() + { + global $table, $tables; + $this->_setTable($table); + $this->_setTables($tables); + } + + /** + * Sets the export XML properties + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/HiddenPropertyItem.class.php"; + include_once "$props/options/items/BoolPropertyItem.class.php"; + + // 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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem(); + $leaf->setName("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->setName("structure"); + $structure->setText(__('Object creation options (all are recommended)')); + // create primary items and add them to the group + if (! PMA_DRIZZLE) { + $leaf = new BoolPropertyItem(); + $leaf->setName("export_functions"); + $leaf->setText(__('Functions')); + $structure->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName("export_procedures"); + $leaf->setText(__('Procedures')); + $structure->addProperty($leaf); + } + $leaf = new BoolPropertyItem(); + $leaf->setName("export_tables"); + $leaf->setText(__('Tables')); + $structure->addProperty($leaf); + if (! PMA_DRIZZLE) { + $leaf = new BoolPropertyItem(); + $leaf->setName("export_triggers"); + $leaf->setText(__('Triggers')); + $structure->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName("export_views"); + $leaf->setText(__('Views')); + $structure->addProperty($leaf); + } + $exportSpecificOptions->addProperty($structure); + + // data main group + $data = new OptionsPropertyMainGroup(); + $data->setName("data"); + $data->setText(__('Data dump options')); + // create primary items and add them to the group + $leaf = new BoolPropertyItem(); + $leaf->setName("export_contents"); + $leaf->setText(__('Export contents')); + $data->addProperty($leaf); + $exportSpecificOptions->addProperty($data); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * 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_of_file']; + } else { + $charset = 'utf-8'; + } + + $head = '' . $crlf + . '' . $crlf . $crlf; + + $head .= '' . $crlf; + + if ($export_struct) { + if (PMA_DRIZZLE) { + $result = $GLOBALS['dbi']->fetchResult( + "SELECT + 'utf8' AS DEFAULT_CHARACTER_SET_NAME, + DEFAULT_COLLATION_NAME + FROM data_dictionary.SCHEMAS + WHERE SCHEMA_NAME = '" + . PMA_Util::sqlAddSlashes($db) . "'" + ); + } else { + $result = $GLOBALS['dbi']->fetchResult( + 'SELECT `DEFAULT_CHARACTER_SET_NAME`, `DEFAULT_COLLATION_NAME`' + . ' FROM `information_schema`.`SCHEMATA` WHERE `SCHEMA_NAME`' + . ' = \''.PMA_Util::sqlAddSlashes($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 (count($tables) == 0) { + $tables[] = $table; + } + + foreach ($tables as $table) { + // Export tables and views + $result = $GLOBALS['dbi']->fetchResult( + 'SHOW CREATE TABLE ' . PMA_Util::backquote($db) . '.' + . PMA_Util::backquote($table), + 0 + ); + $tbl = $result[$table][1]; + + $is_view = PMA_Table::isView($db, $table); + + 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 = 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'] + ) { + // Export functions + $functions = $GLOBALS['dbi']->getProceduresOrFunctions( + $db, 'FUNCTION' + ); + if ($functions) { + foreach ($functions as $function) { + $head .= ' ' . $crlf; + + // Do some formatting + $sql = $GLOBALS['dbi']->getDefinition( + $db, 'FUNCTION', $function + ); + $sql = rtrim($sql); + $sql = " " . htmlspecialchars($sql); + $sql = str_replace("\n", "\n ", $sql); + + $head .= $sql . $crlf; + $head .= ' ' . $crlf; + } + + unset($function); + unset($functions); + } + } + + if (isset($GLOBALS['xml_export_procedures']) + && $GLOBALS['xml_export_procedures'] + ) { + // Export procedures + $procedures = $GLOBALS['dbi']->getProceduresOrFunctions( + $db, 'PROCEDURE' + ); + if ($procedures) { + foreach ($procedures as $procedure) { + $head .= ' ' . $crlf; + + // Do some formatting + $sql = $GLOBALS['dbi']->getDefinition( + $db, 'PROCEDURE', $procedure + ); + $sql = rtrim($sql); + $sql = " " . htmlspecialchars($sql); + $sql = str_replace("\n", "\n ", $sql); + + $head .= $sql . $crlf; + $head .= ' ' . $crlf; + } + + unset($procedure); + unset($procedures); + } + } + + unset($result); + + $head .= ' ' . $crlf; + $head .= ' ' . $crlf; + + if ($export_data) { + $head .= $crlf; + } + } + + return PMA_exportOutputHandler($head); + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter () + { + $foot = ''; + + return PMA_exportOutputHandler($foot); + } + + /** + * Outputs database header + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + global $crlf; + + if (isset($GLOBALS['xml_export_contents']) + && $GLOBALS['xml_export_contents'] + ) { + $head = ' ' . $crlf + . ' ' . $crlf; + + return PMA_exportOutputHandler($head); + } else { + 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 PMA_exportOutputHandler(' ' . $crlf); + } else { + return true; + } + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportData ($db, $table, $crlf, $error_url, $sql_query) + { + if (isset($GLOBALS['xml_export_contents']) + && $GLOBALS['xml_export_contents'] + ) { + $result = $GLOBALS['dbi']->query( + $sql_query, null, PMA_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 (! PMA_exportOutputHandler($buffer)) { + return false; + } + + while ($record = $GLOBALS['dbi']->fetchRow($result)) { + $buffer = ' ' . $crlf; + for ($i = 0; $i < $columns_cnt; $i++) { + // 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 (! PMA_exportOutputHandler($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($tables) + { + $this->_tables = $tables; + } +} +?> diff --git a/sources/libraries/plugins/export/ExportYaml.class.php b/sources/libraries/plugins/export/ExportYaml.class.php new file mode 100644 index 0000000..a59fb35 --- /dev/null +++ b/sources/libraries/plugins/export/ExportYaml.class.php @@ -0,0 +1,216 @@ +setProperties(); + } + + /** + * Sets the export YAML properties + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ExportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/HiddenPropertyItem.class.php"; + + $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(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem(); + $leaf->setName("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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader () + { + PMA_exportOutputHandler( + '%YAML 1.1' . $GLOBALS['crlf'] . '---' . $GLOBALS['crlf'] + ); + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter () + { + PMA_exportOutputHandler('...' . $GLOBALS['crlf']); + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + 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 + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + $result = $GLOBALS['dbi']->query( + $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED + ); + + $columns_cnt = $GLOBALS['dbi']->numFields($result); + for ($i = 0; $i < $columns_cnt; $i++) { + $columns[$i] = stripslashes($GLOBALS['dbi']->fieldName($result, $i)); + } + unset($i); + + $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 . '.' . $table . $crlf; + $buffer .= '-' . $crlf; + } else { + $buffer = '-' . $crlf; + } + + for ($i = 0; $i < $columns_cnt; $i++) { + if (! isset($record[$i])) { + continue; + } + + $column = $columns[$i]; + + if (is_null($record[$i])) { + $buffer .= ' ' . $column . ': null' . $crlf; + continue; + } + + if (is_numeric($record[$i])) { + $buffer .= ' ' . $column . ': ' . $record[$i] . $crlf; + continue; + } + + $record[$i] = str_replace( + array('\\', '"', "\n", "\r"), + array('\\\\', '\"', '\n', '\r'), + $record[$i] + ); + $buffer .= ' ' . $column . ': "' . $record[$i] . '"' . $crlf; + } + + if (! PMA_exportOutputHandler($buffer)) { + return false; + } + } + $GLOBALS['dbi']->freeResult($result); + + return true; + } // end getTableYAML +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/export/PMA_ExportPdf.class.php b/sources/libraries/plugins/export/PMA_ExportPdf.class.php new file mode 100644 index 0000000..29c823d --- /dev/null +++ b/sources/libraries/plugins/export/PMA_ExportPdf.class.php @@ -0,0 +1,426 @@ +y; + } + $current_page = $this->page; + if ((($y + $h) > $this->PageBreakTrigger) + AND (! $this->InFooter) + AND ($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; + } + if ($current_page != $this->page) { + // account for columns mode + return true; + } + return false; + } + + /** + * This method is used to render the page header. + * + * @return void + */ + 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( + PMA_PDF_FONT, + '', + ($this->titleFontSize + ? $this->titleFontSize + : $this->FontSizePt) + ); + $this->Cell(0, $this->FontSizePt, $this->titleText, 0, 1, 'C'); + $this->SetFont(PMA_PDF_FONT, '', $this->cellFontSize); + $this->SetY(($this->tMargin) - ($this->FontSizePt / $this->k) * 2.5); + $this->Cell( + 0, + $this->FontSizePt, + __('Database:') . ' ' . $this->currentDb . ', ' + . __('Table:') . ' ' . $this->currentTable, + 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 + */ + 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 + */ + function setAttributes($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 + */ + function setTopMargin($topMargin) + { + $this->tMargin = $topMargin; + } + + /** + * MySQL report + * + * @param string $query Query to execute + * + * @return void + */ + 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, null, PMA_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(); + for ($i = 0; $i < $this->numFields; $i++) { + $stringWidth = $this->getstringwidth($this->fields[$i]->name) + 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] = $this->fields[$i]->name; + $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, null, PMA_DatabaseInterface::QUERY_UNBUFFERED + ); + $this->setY($this->tMargin); + $this->AddPage(); + $this->SetFont(PMA_PDF_FONT, '', 9); + $this->morepagestable($this->FontSizePt); + $GLOBALS['dbi']->freeResult($this->results); + + } // end of mysqlReport function + +} // end of PMA_Export_PDF class +?> diff --git a/sources/libraries/plugins/export/README b/sources/libraries/plugins/export/README new file mode 100644 index 0000000..68d5a26 --- /dev/null +++ b/sources/libraries/plugins/export/README @@ -0,0 +1,276 @@ +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() + { + // set properties + $props = 'libraries/properties/'; + // include the main class for properties for the export plug-ins + include_once "$props/plugins/ExportPluginProperties.class.php"; + // include the group properties classes + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + // include the needed single property items + include_once "$props/options/items/RadioPropertyItem.class.php"; + + $exportPluginProperties = new 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 OptionsPropertyRootGroup(); + $exportSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("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 RadioPropertyItem(); + $leaf->setName("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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * 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 + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db) + { + // 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 + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + // 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 + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + // implementation; + return true; + } + + // optional - implement other methods defined in 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 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; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/export/TableProperty.class.php b/sources/libraries/plugins/export/TableProperty.class.php new file mode 100644 index 0000000..cf7c6fc --- /dev/null +++ b/sources/libraries/plugins/export/TableProperty.class.php @@ -0,0 +1,286 @@ +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 + */ + function getPureType() + { + $pos = strpos($this->type, "("); + if ($pos > 0) { + return 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 + */ + 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 + */ + function isUnique() + { + return $this->key == "PRI" || $this->key == "UNI" ? "true" : "false"; + } + + /** + * Gets the .NET primitive type + * + * @return string type + */ + function getDotNetPrimitiveType() + { + if (strpos($this->type, "int") === 0) { + return "int"; + } + if (strpos($this->type, "long") === 0) { + return "long"; + } + if (strpos($this->type, "char") === 0) { + return "string"; + } + if (strpos($this->type, "varchar") === 0) { + return "string"; + } + if (strpos($this->type, "text") === 0) { + return "string"; + } + if (strpos($this->type, "longtext") === 0) { + return "string"; + } + if (strpos($this->type, "tinyint") === 0) { + return "bool"; + } + if (strpos($this->type, "datetime") === 0) { + return "DateTime"; + } + return "unknown"; + } + + /** + * Gets the .NET object type + * + * @return string type + */ + function getDotNetObjectType() + { + if (strpos($this->type, "int") === 0) { + return "Int32"; + } + if (strpos($this->type, "long") === 0) { + return "Long"; + } + if (strpos($this->type, "char") === 0) { + return "String"; + } + if (strpos($this->type, "varchar") === 0) { + return "String"; + } + if (strpos($this->type, "text") === 0) { + return "String"; + } + if (strpos($this->type, "longtext") === 0) { + return "String"; + } + if (strpos($this->type, "tinyint") === 0) { + return "Boolean"; + } + if (strpos($this->type, "datetime") === 0) { + return "DateTime"; + } + return "Unknown"; + } + + /** + * Gets the index name + * + * @return string containing the name of the index + */ + 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 + */ + function isPK() + { + return $this->key=="PRI"; + } + + /** + * Formats a string for C# + * + * @param string $text string to be formatted + * + * @return string formatted text + */ + 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 + */ + 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 + */ + 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; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/import/AbstractImportCsv.class.php b/sources/libraries/plugins/import/AbstractImportCsv.class.php new file mode 100644 index 0000000..4c72cba --- /dev/null +++ b/sources/libraries/plugins/import/AbstractImportCsv.class.php @@ -0,0 +1,91 @@ +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(); + $importSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + + // create common items and add them to the group + $leaf = new BoolPropertyItem(); + $leaf->setName("replace"); + $leaf->setText(__('Replace table data with file')); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("terminated"); + $leaf->setText(__('Columns separated with:')); + $leaf->setSize(2); + $leaf->setLen(2); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("enclosed"); + $leaf->setText(__('Columns enclosed with:')); + $leaf->setSize(2); + $leaf->setLen(2); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("escaped"); + $leaf->setText(__('Columns escaped with:')); + $leaf->setSize(2); + $leaf->setLen(2); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem(); + $leaf->setName("new_line"); + $leaf->setText(__('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; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/import/ImportCsv.class.php b/sources/libraries/plugins/import/ImportCsv.class.php new file mode 100644 index 0000000..8e067d9 --- /dev/null +++ b/sources/libraries/plugins/import/ImportCsv.class.php @@ -0,0 +1,608 @@ +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(); + $leaf->setName("col_names"); + $leaf->setText( + __( + '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 PMA_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(); + $leaf->setName("columns"); + $leaf->setText( + __('Column names: ') + . PMA_Util::showHint($hint) + ); + $generalOptions->addProperty($leaf); + } + + $leaf = new BoolPropertyItem(); + $leaf->setName("ignore"); + $leaf->setText(__('Do not abort on INSERT error')); + $generalOptions->addProperty($leaf); + + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Handles the whole import logic + * + * @return void + */ + public function doImport() + { + 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) != 1) { + $message = PMA_Message::error( + __('Invalid parameter for CSV import: %s') + ); + $message->addParam(__('Columns terminated with'), false); + $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 (strlen($csv_enclosed) > 1) { + $message = PMA_Message::error( + __('Invalid parameter for CSV import: %s') + ); + $message->addParam(__('Columns enclosed with'), false); + $error = true; + $param_error = true; + } elseif (strlen($csv_escaped) != 1) { + $message = PMA_Message::error( + __('Invalid parameter for CSV import: %s') + ); + $message->addParam(__('Columns escaped with'), false); + $error = true; + $param_error = true; + } elseif (strlen($csv_new_line) != 1 && $csv_new_line != 'auto') { + $message = PMA_Message::error( + __('Invalid parameter for CSV import: %s') + ); + $message->addParam(__('Lines terminated with'), false); + $error = true; + $param_error = true; + } + + // If there is an error in the parameters entered, + // indicate that immediately. + if ($param_error) { + PMA_Util::mysqlDie($message->getMessage(), '', '', $err_url); + } + + $buffer = ''; + $required_fields = 0; + + if (! $this->_getAnalyze()) { + if (isset($_POST['csv_replace'])) { + $sql_template = 'REPLACE'; + } else { + $sql_template = 'INSERT'; + if (isset($_POST['csv_ignore'])) { + $sql_template .= ' IGNORE'; + } + } + $sql_template .= ' INTO ' . PMA_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 = PMA_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 .= PMA_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; + + while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) { + $data = PMA_importGetNextChunk(); + 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 = 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' + && strpos($buffer, "\r") === false + && strpos($buffer, "\n") === false) + || ($csv_new_line != 'auto' + && strpos($buffer, $csv_new_line) === false) + ) { + continue; + } + } + + // Current length of our buffer + $len = strlen($buffer); + // Currently parsed char + $ch = $buffer[$i]; + while ($i < $len) { + // Deadlock protection + if ($lasti == $i && $lastlen == $len) { + $message = PMA_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 = $buffer[$i]; + continue; + } + + // Grab one field + $fallbacki = $i; + if ($ch == $csv_enclosed) { + if ($i == $len - 1) { + break; + } + $need_end = true; + $i++; + $ch = $buffer[$i]; + } 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 = $buffer[$i]; + 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 = $buffer[$i]; + } + + // unquoted NULL string + if (false === $need_end && $value === 'NULL') { + $value = null; + } + + if ($fail) { + $i = $fallbacki; + $ch = $buffer[$i]; + 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 = $buffer[$i]; + break; + } else { + $i++; + $ch = $buffer[$i]; + } + } + // 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 = $buffer[$i]; + break; + } + $i++; + $ch = $buffer[$i]; + } + // 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 ($buffer[$i + 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 = PMA_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 .= '\'' + . PMA_Util::sqlAddSlashes($val) + . '\''; + } + + $first = false; + } + $sql .= ')'; + + /** + * @todo maybe we could add original line to verbose + * SQL in comment + */ + PMA_importRunQuery($sql, $sql); + } + + $line++; + $csv_finish = false; + $values = array(); + $buffer = substr($buffer, $i + 1); + $len = strlen($buffer); + $i = 0; + $lasti = -1; + $ch = $buffer[0]; + } + } // 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]; + } + + 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 (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[] = PMA_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 */ + if (strlen($db)) { + $db_name = $db; + $options = array('create_db' => false); + } else { + $db_name = 'CSV_DB'; + $options = null; + } + + /* Non-applicable parameters */ + $create = null; + + /* Created and execute necessary SQL statements from data */ + PMA_buildSQL($db_name, $tables, $analyses, $create, $options); + + unset($tables); + unset($analyses); + } + + // Commit any possible data in buffers + PMA_importRunQuery(); + + if (count($values) != 0 && ! $error) { + $message = PMA_Message::error( + __('Invalid format of CSV input on line %d.') + ); + $message->addParam($line); + $error = true; + } + } + + + /* ~~~~~~~~~~~~~~~~~~~~ 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/sources/libraries/plugins/import/ImportLdi.class.php b/sources/libraries/plugins/import/ImportLdi.class.php new file mode 100644 index 0000000..1277938 --- /dev/null +++ b/sources/libraries/plugins/import/ImportLdi.class.php @@ -0,0 +1,174 @@ +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( + 'SHOW VARIABLES LIKE \'local\\_infile\';' + ); + if ($result != false && $GLOBALS['dbi']->numRows($result) > 0) { + $tmp = $GLOBALS['dbi']->fetchRow($result); + if ($tmp[1] == '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(); + $leaf->setName("columns"); + $leaf->setText(__('Column names: ')); + $generalOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem(); + $leaf->setName("ignore"); + $leaf->setText(__('Do not abort on INSERT error')); + $generalOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem(); + $leaf->setName("local_option"); + $leaf->setText(__('Use LOCAL keyword')); + $generalOptions->addProperty($leaf); + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Handles the whole import logic + * + * @return void + */ + public function doImport() + { + global $finished, $import_file, $compression, $charset_conversion, $table; + global $ldi_local_option, $ldi_replace, $ldi_ignore, $ldi_terminated, + $ldi_enclosed, $ldi_escaped, $ldi_new_line, $skip_queries, $ldi_columns; + + if ($import_file == 'none' + || $compression != 'none' + || $charset_conversion + ) { + // We handle only some kind of data! + $GLOBALS['message'] = PMA_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 \'' . PMA_Util::sqlAddSlashes($import_file) . '\''; + if (isset($ldi_replace)) { + $sql .= ' REPLACE'; + } elseif (isset($ldi_ignore)) { + $sql .= ' IGNORE'; + } + $sql .= ' INTO TABLE ' . PMA_Util::backquote($table); + + if (strlen($ldi_terminated) > 0) { + $sql .= ' FIELDS TERMINATED BY \'' . $ldi_terminated . '\''; + } + if (strlen($ldi_enclosed) > 0) { + $sql .= ' ENCLOSED BY \'' + . PMA_Util::sqlAddSlashes($ldi_enclosed) . '\''; + } + if (strlen($ldi_escaped) > 0) { + $sql .= ' ESCAPED BY \'' + . PMA_Util::sqlAddSlashes($ldi_escaped) . '\''; + } + if (strlen($ldi_new_line) > 0) { + if ($ldi_new_line == 'auto') { + $ldi_new_line + = (PMA_Util::whichCrlf() == "\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 .= PMA_Util::backquote( + trim($tmp[$i], " \t\r\n\0\x0B`") + ); + } // end for + $sql .= ')'; + } + + PMA_importRunQuery($sql, $sql); + PMA_importRunQuery(); + $finished = true; + } +} diff --git a/sources/libraries/plugins/import/ImportMediawiki.class.php b/sources/libraries/plugins/import/ImportMediawiki.class.php new file mode 100644 index 0000000..b7cd3be --- /dev/null +++ b/sources/libraries/plugins/import/ImportMediawiki.class.php @@ -0,0 +1,573 @@ +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); + } + + $props = 'libraries/properties/'; + include_once "$props/plugins/ImportPluginProperties.class.php"; + + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText(__('MediaWiki Table')); + $importPluginProperties->setExtension('txt'); + $importPluginProperties->setMimeType('text/plain'); + $importPluginProperties->setOptions(array()); + $importPluginProperties->setOptionsText(__('Options')); + + $this->properties = $importPluginProperties; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Handles the whole import logic + * + * @return void + */ + public function doImport() + { + 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 = PMA_importGetNextChunk(); + + 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); + // Don't parse string if we're not at the end + // and don't have a new line inside + if ( 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) { + $full_buffer_lines_count -= 1; + $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 beginnning of comment + if (! strcmp(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; + + // End ignoring structure rows + if ($inside_structure_comment) { + $inside_structure_comment = false; + } + } 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 (substr($cur_buffer_line, 0, 2) === '|-' + || substr($cur_buffer_line, 0, 2) === '|+' + || 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 (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); + + // 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) { + // 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 (strpos($cell_data[0], '[[') === true ) { + if (count($cell_data) == 1) { + $cell = $cell_data[0]; + } else { + $cell = $cell_data[1]; + } + } + + // 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) { + if (strpos($cell, $col_start_char) === 0) { + $cell = trim(substr($cell, 1)); + } + } + + // 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 = PMA_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 + * + * + * @global bool $analyze whether to scan for column types + * + * @return void + */ + private function _importDataOneTable ($table) + { + $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 PMA_buildSQL() + $tables = array(); + $tables [] = array($table[0], $table[1], $table[2]); + + // Obtain the best-fit MySQL types for each column + $analyses = array(); + $analyses [] = PMA_analyzeTable($tables[0]); + + $this->_executeImportTables($tables, $analyses); + } + + // Commit any possible data in buffers + PMA_importRunQuery(); + } + + /** + * 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(&$table_headers, $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 PMA_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) + * ) + * + * @global string $db name of the database to import in + * + * @return void + */ + private function _executeImportTables(&$tables, &$analyses) + { + global $db; + + // $db_name : The currently selected database name, if applicable + // No backquotes + // $options : An associative array of options + if (strlen($db)) { + $db_name = $db; + $options = array('create_db' => false); + } else { + $db_name = 'mediawiki_DB'; + $options = null; + } + + // Array of SQL strings + // Non-applicable parameters + $create = null; + + // Create and execute necessary SQL statements from data + PMA_buildSQL($db_name, $tables, $analyses, $create, $options); + + unset($tables); + unset($analyses); + } + + + /** + * Replaces all instances of the '||' separator between delimiters + * in a given string + * + * @param string $start_delim start delimiter + * @param string $end_delim end delimiter + * @param string $replace the string to be replaced with + * @param string $subject the text to be replaced + * + * @return string with replacements + */ + private function _delimiterReplace($start_delim, $end_delim, $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 rembembers 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; + } +} diff --git a/sources/libraries/plugins/import/ImportOds.class.php b/sources/libraries/plugins/import/ImportOds.class.php new file mode 100644 index 0000000..2b07e88 --- /dev/null +++ b/sources/libraries/plugins/import/ImportOds.class.php @@ -0,0 +1,415 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ImportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/BoolPropertyItem.class.php"; + + $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(); + $importSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + // create primary items and add them to the group + $leaf = new BoolPropertyItem(); + $leaf->setName("col_names"); + $leaf->setText( + __( + '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(); + $leaf->setName("empty_rows"); + $leaf->setText(__('Do not import empty rows')); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName("recognize_percentages"); + $leaf->setText( + __( + 'Import percentages as proper decimals (ex. 12.00% to .12)' + ) + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName("recognize_currency"); + $leaf->setText(__('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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Handles the whole import logic + * + * @return void + */ + public function doImport() + { + global $db, $error, $timeout_passed, $finished; + + $i = 0; + $len = 0; + $buffer = ""; + + /** + * Read in the file via PMA_importGetNextChunk so that + * it can process compressed files + */ + while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) { + $data = PMA_importGetNextChunk(); + 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'] = PMA_Message::error( + __( + 'The XML file specified was either malformed or incomplete.' + . ' Please correct the issue and try again.' + ) + ); + $GLOBALS['error'] = true; + } else { + $root = $xml->children('office', true)->{'body'}->{'spreadsheet'}; + if (empty($root)) { + $sheets = array(); + $GLOBALS['message'] = PMA_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 */ + foreach ($sheets as $sheet) { + $col_names_in_first_row = isset($_REQUEST['ods_col_names']); + + /* Iterate over rows */ + foreach ($sheet as $row) { + $type = $row->getName(); + if (! strcmp('table-row', $type)) { + /* Iterate over columns */ + foreach ($row as $cell) { + $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++) { + if ($_REQUEST['ods_recognize_percentages'] + && ! strcmp( + 'percentage', + $cell_attrs['value-type'] + ) + ) { + $value = (double)$cell_attrs['value']; + } elseif ($_REQUEST['ods_recognize_currency'] + && !strcmp('currency', $cell_attrs['value-type']) + ) { + $value = (double)$cell_attrs['value']; + } else { + /* We need to concatenate all paragraphs */ + $values = array(); + foreach ($text as $paragraph) { + $values[] = (string)$paragraph; + } + $value = implode("\n", $values); + } + if (! $col_names_in_first_row) { + $tempRow[] = $value; + } else { + $col_names[] = $value; + } + + ++$col_count; + } + } else { + /* Number of blank columns repeated */ + if ($col_count < count($row->children('table', true)) - 1 + ) { + $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[] = PMA_getColumnAlphaName( + $col_count + 1 + ); + ++$col_count; + } + } + } else { + if (! $col_names_in_first_row) { + $tempRow[] = 'NULL'; + } else { + $col_names[] = PMA_getColumnAlphaName( + $col_count + 1 + ); + } + + ++$col_count; + } + } + } + } + + /* 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[] = PMA_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_tbls = count($tables); + for ($i = 0; $i < $num_tbls; ++$i) { + for ($j = 0; $j < count($rows); ++$j) { + if (! strcmp($tables[$i][TBL_NAME], $rows[$j][TBL_NAME])) { + if (! isset($tables[$i][COL_NAMES])) { + $tables[$i][] = $rows[$j][COL_NAMES]; + } + + $tables[$i][ROWS] = $rows[$j][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[] = PMA_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 */ + if (strlen($db)) { + $db_name = $db; + $options = array('create_db' => false); + } else { + $db_name = 'ODS_DB'; + $options = null; + } + + /* Non-applicable parameters */ + $create = null; + + /* Created and execute necessary SQL statements from data */ + PMA_buildSQL($db_name, $tables, $analyses, $create, $options); + + unset($tables); + unset($analyses); + + /* Commit any possible data in buffers */ + PMA_importRunQuery(); + } +} diff --git a/sources/libraries/plugins/import/ImportShp.class.php b/sources/libraries/plugins/import/ImportShp.class.php new file mode 100644 index 0000000..7a4c0c8 --- /dev/null +++ b/sources/libraries/plugins/import/ImportShp.class.php @@ -0,0 +1,335 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ImportPluginProperties.class.php"; + + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText(__('ESRI Shape File')); + $importPluginProperties->setExtension('shp'); + $importPluginProperties->setOptions(array()); + $importPluginProperties->setOptionsText(__('Options')); + + $this->properties = $importPluginProperties; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Handles the whole import logic + * + * @return void + */ + public function doImport() + { + global $db, $error, $finished, $compression, + $import_file, $local_import_file, $message; + + $GLOBALS['finished'] = false; + + $shp = new PMA_ShapeFile(1); + // If the zip archive has more than one file, + // get the correct content to the buffer from .shp file. + if ($compression == 'application/zip' + && PMA_getNoOfFilesInZip($import_file) > 1 + ) { + $zip_content = PMA_getZipContents($import_file, '/^.*\.shp$/i'); + $GLOBALS['import_text'] = $zip_content['data']; + } + + $temp_dbf_file = false; + // We need dbase extension to handle .dbf file + if (extension_loaded('dbase')) { + // If we can extract the zip archive to 'TempDir' + // and use the files in it for import + if ($compression == 'application/zip' + && ! empty($GLOBALS['cfg']['TempDir']) + && is_writable($GLOBALS['cfg']['TempDir']) + ) { + $dbf_file_name = PMA_findFileFromZipArchive( + '/^.*\.dbf$/i', $import_file + ); + // If the corresponding .dbf file is in the zip archive + if ($dbf_file_name) { + // Extract the .dbf file and point to it. + $extracted = PMA_zipExtract( + $import_file, + realpath($GLOBALS['cfg']['TempDir']), + array($dbf_file_name) + ); + if ($extracted) { + $dbf_file_path = realpath($GLOBALS['cfg']['TempDir']) + . (PMA_IS_WINDOWS ? '\\' : '/') . $dbf_file_name; + $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 = substr($import_file, 0, strlen($import_file) - 4) + . '.*'; + $shp->FileName = $file_name; + } + } + + // Load data + $shp->loadFromFile(''); + if ($shp->lastError != "") { + $error = true; + $message = PMA_Message::error( + __('There was an error importing the ESRI shape file: "%s".') + ); + $message->addParam($shp->lastError); + return; + } + + // Delete the .dbf file extracted to 'TempDir' + if ($temp_dbf_file + && isset($dbf_file_path) + && file_exists($dbf_file_path) + ) { + unlink($dbf_file_path); + } + + $esri_types = array( + 0 => 'Null Shape', + 1 => 'Point', + 3 => 'PolyLine', + 5 => 'Polygon', + 8 => 'MultiPoint', + 11 => 'PointZ', + 13 => 'PolyLineZ', + 15 => 'PolygonZ', + 18 => 'MultiPointZ', + 21 => 'PointM', + 23 => 'PolyLineM', + 25 => 'PolygonM', + 28 => 'MultiPointM', + 31 => 'MultiPatch', + ); + + 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; + if (! isset($esri_types[$shp->shapeType])) { + $message = PMA_Message::error( + __( + 'You tried to import an invalid file or the imported file' + . ' contains invalid data' + ) + ); + } else { + $message = PMA_Message::error( + __('MySQL Spatial Extension does not support ESRI type "%s".') + ); + $message->addParam($esri_types[$shp->shapeType]); + } + return; + } + + if (isset($gis_type)) { + include_once './libraries/gis/pma_gis_factory.php'; + $gis_obj = PMA_GIS_Factory::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 = PMA_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)) { + $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[] = PMA_analyzeTable($tables[0]); + + $table_no = 0; $spatial_col = 0; + $analyses[$table_no][TYPES][$spatial_col] = GEOMETRY; + $analyses[$table_no][FORMATTEDSQL][$spatial_col] = true; + + // Set database name to the currently selected one, if applicable + if (strlen($db)) { + $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; + PMA_buildSQL($db_name, $tables, $analyses, $null_param, $options); + + unset($tables); + unset($analyses); + + $finished = true; + $error = false; + + // Commit any possible data in buffers + PMA_importRunQuery(); + } + + /** + * 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 .= PMA_importGetNextChunk(); + } + } + $result = substr($buffer, 0, $length); + $buffer = substr($buffer, $length); + return $result; + } +} diff --git a/sources/libraries/plugins/import/ImportSql.class.php b/sources/libraries/plugins/import/ImportSql.class.php new file mode 100644 index 0000000..4ae9024 --- /dev/null +++ b/sources/libraries/plugins/import/ImportSql.class.php @@ -0,0 +1,447 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ImportPluginProperties.class.php"; + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + include_once "$props/options/items/SelectPropertyItem.class.php"; + include_once "$props/options/items/BoolPropertyItem.class.php"; + + $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(); + $importSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("general_opts"); + // create primary items and add them to the group + $leaf = new SelectPropertyItem(); + $leaf->setName("compatibility"); + $leaf->setText(__('SQL compatibility mode:')); + $leaf->setValues($values); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'Server_SQL_mode', + ) + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem(); + $leaf->setName("no_auto_value_on_zero"); + $leaf->setText( + __('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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(&$sql_data = array()) + { + global $error, $timeout_passed; + + $buffer = ''; + // Defaults for parser + $sql = ''; + $start_pos = 0; + $i = 0; + $len= 0; + $big_value = 2147483647; + // include the space because it's mandatory + $delimiter_keyword = 'DELIMITER '; + $length_of_delimiter_keyword = strlen($delimiter_keyword); + + if (isset($_POST['sql_delimiter'])) { + $sql_delimiter = $_POST['sql_delimiter']; + } else { + $sql_delimiter = ';'; + } + + // Handle compatibility options + $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) { + $GLOBALS['dbi']->tryQuery( + 'SET SQL_MODE="' . implode(',', $sql_modes) . '"' + ); + } + unset($sql_modes); + + /** + * will be set in PMA_importGetNextChunk() + * + * @global boolean $GLOBALS['finished'] + */ + $GLOBALS['finished'] = false; + + while (! ($GLOBALS['finished'] && $i >= $len) + && ! $error + && ! $timeout_passed + ) { + $data = PMA_importGetNextChunk(); + 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; + // free memory + unset($data); + // Do not parse string when we're not at the end + // and don't have ; inside + if ((strpos($buffer, $sql_delimiter, $i) === false) + && ! $GLOBALS['finished'] + ) { + continue; + } + } + + // Convert CR (but not CRLF) to LF otherwise all queries + // may not get executed on some platforms + $buffer = preg_replace("/\r($|[^\n])/", "\n$1", $buffer); + + // Current length of our buffer + $len = strlen($buffer); + + // Grab some SQL queries out of it + while ($i < $len) { + $found_delimiter = false; + // Find first interesting character + $old_i = $i; + // this is about 7 times faster that looking for each sequence i + // one by one with strpos() + $match = preg_match( + '/(\'|"|#|-- |\/\*|`|(?i)(? unclosed quote, + // but we handle it as end of query + if ($GLOBALS['finished']) { + $endq = true; + $i = $len - 1; + } + $found_delimiter = false; + break; + } + // Was not the quote escaped? + $j = $pos - 1; + while ($buffer[$j] == '\\') { + $j--; + } + // Even count means it was not escaped + $endq = (((($pos - 1) - $j) % 2) == 0); + // Skip the string + $i = $pos; + + if ($first_sql_delimiter < $pos) { + $found_delimiter = false; + } + } + if (! $endq) { + break; + } + $i++; + // Aren't we at the end? + if ($GLOBALS['finished'] && $i == $len) { + $i--; + } else { + continue; + } + } + + // Not enough data to decide + if ((($i == ($len - 1) && ($ch == '-' || $ch == '/')) + || ($i == ($len - 2) && (($ch == '-' && $buffer[$i + 1] == '-') + || ($ch == '/' && $buffer[$i + 1] == '*')))) + && ! $GLOBALS['finished'] + ) { + break; + } + + // Comments + if ($ch == '#' + || ($i < ($len - 1) && $ch == '-' && $buffer[$i + 1] == '-' + && (($i < ($len - 2) && $buffer[$i + 2] <= ' ') + || ($i == ($len - 1) && $GLOBALS['finished']))) + || ($i < ($len - 1) && $ch == '/' && $buffer[$i + 1] == '*') + ) { + // Copy current string to SQL + if ($start_pos != $i) { + $sql .= substr($buffer, $start_pos, $i - $start_pos); + } + // Skip the rest + $start_of_comment = $i; + // do not use PHP_EOL here instead of "\n", because the export + // file might have been produced on a different system + $i = strpos($buffer, $ch == '/' ? '*/' : "\n", $i); + // didn't we hit end of string? + if ($i === false) { + if ($GLOBALS['finished']) { + $i = $len - 1; + } else { + break; + } + } + // Skip * + if ($ch == '/') { + $i++; + } + // Skip last char + $i++; + // We need to send the comment part in case we are defining + // a procedure or function and comments in it are valuable + $sql .= substr( + $buffer, + $start_of_comment, + $i - $start_of_comment + ); + // Next query part will start here + $start_pos = $i; + // Aren't we at the end? + if ($i == $len) { + $i--; + } else { + continue; + } + } + // Change delimiter, if redefined, and skip it + // (don't send to server!) + if (($i + $length_of_delimiter_keyword < $len) + && strtoupper( + substr($buffer, $i, $length_of_delimiter_keyword) + ) == $delimiter_keyword + ) { + // look for EOL on the character immediately after 'DELIMITER ' + // (see previous comment about PHP_EOL) + $new_line_pos = strpos( + $buffer, + "\n", + $i + $length_of_delimiter_keyword + ); + // it might happen that there is no EOL + if (false === $new_line_pos) { + $new_line_pos = $len; + } + $sql_delimiter = substr( + $buffer, + $i + $length_of_delimiter_keyword, + $new_line_pos - $i - $length_of_delimiter_keyword + ); + $i = $new_line_pos + 1; + // Next query part will start here + $start_pos = $i; + continue; + } + + // End of SQL + if ($found_delimiter + || ($GLOBALS['finished'] + && ($i == $len - 1)) + ) { + $tmp_sql = $sql; + if ($start_pos < $len) { + $length_to_grab = $i - $start_pos; + + if (! $found_delimiter) { + $length_to_grab++; + } + $tmp_sql .= substr($buffer, $start_pos, $length_to_grab); + unset($length_to_grab); + } + // Do not try to execute empty SQL + if (! preg_match('/^([\s]*;)*$/', trim($tmp_sql))) { + $sql = $tmp_sql; + PMA_importRunQuery( + $sql, + substr($buffer, 0, $i + strlen($sql_delimiter)), + false, + $sql_data + ); + $buffer = substr($buffer, $i + strlen($sql_delimiter)); + // Reset parser: + $len = strlen($buffer); + $sql = ''; + $i = 0; + $start_pos = 0; + // Any chance we will get a complete query? + //if ((strpos($buffer, ';') === false) + //&& ! $GLOBALS['finished']) { + if (strpos($buffer, $sql_delimiter) === false + && ! $GLOBALS['finished'] + ) { + break; + } + } else { + $i++; + $start_pos = $i; + } + } + } // End of parser loop + } // End of import loop + // Commit any possible data in buffers + PMA_importRunQuery('', substr($buffer, 0, $len), false, $sql_data); + PMA_importRunQuery('', '', false, $sql_data); + } +} diff --git a/sources/libraries/plugins/import/ImportXml.class.php b/sources/libraries/plugins/import/ImportXml.class.php new file mode 100644 index 0000000..7c7afa0 --- /dev/null +++ b/sources/libraries/plugins/import/ImportXml.class.php @@ -0,0 +1,379 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $props = 'libraries/properties/'; + include_once "$props/plugins/ImportPluginProperties.class.php"; + + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText(__('XML')); + $importPluginProperties->setExtension('xml'); + $importPluginProperties->setMimeType('text/xml'); + $importPluginProperties->setOptions(array()); + $importPluginProperties->setOptionsText(__('Options')); + + $this->properties = $importPluginProperties; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Handles the whole import logic + * + * @return void + */ + public function doImport() + { + global $error, $timeout_passed, $finished, $db; + + $i = 0; + $len = 0; + $buffer = ""; + + /** + * Read in the file via PMA_importGetNextChunk so that + * it can process compressed files + */ + while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) { + $data = PMA_importGetNextChunk(); + 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) { + PMA_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($namespaces['pma']) + ->{'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) { + PMA_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 + */ + $struct = $xml->children($namespaces['pma']); + + $create = array(); + + foreach ($struct as $val1) { + 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 " + . PMA_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; + for ($i = 0; $i < count($tables); ++$i) { + if (! strcmp($tables[$i][TBL_NAME], (string)$tbl_attr['name'])) { + $isInTables = true; + break; + } + } + + if ($isInTables == false) { + $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_tbls = count($tables); + for ($i = 0; $i < $num_tbls; ++$i) { + for ($j = 0; $j < count($rows); ++$j) { + if (! strcmp($tables[$i][TBL_NAME], $rows[$j][TBL_NAME])) { + if (! isset($tables[$i][COL_NAMES])) { + $tables[$i][] = $rows[$j][COL_NAMES]; + } + + $tables[$i][ROWS][] = $rows[$j][ROWS]; + } + } + } + + unset($rows); + + if (! $struct_present) { + $analyses = array(); + + $len = count($tables); + for ($i = 0; $i < $len; ++$i) { + $analyses[] = PMA_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 PMA_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 */ + PMA_buildSQL($db_name, $tables, $analyses, $create, $options); + + unset($analyses); + unset($tables); + unset($create); + + /* Commit any possible data in buffers */ + PMA_importRunQuery(); + } +} diff --git a/sources/libraries/plugins/import/README b/sources/libraries/plugins/import/README new file mode 100644 index 0000000..815cf2b --- /dev/null +++ b/sources/libraries/plugins/import/README @@ -0,0 +1,172 @@ +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 http://wiki.phpmyadmin.net/pma/Gettext_for_developers. + +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + // set properties + $props = 'libraries/properties/'; + // include the main class for properties for the import plug-ins + include_once "$props/plugins/ImportPluginProperties.class.php"; + // include the group properties classes + include_once "$props/options/groups/OptionsPropertyRootGroup.class.php"; + include_once "$props/options/groups/OptionsPropertyMainGroup.class.php"; + // include the needed single property items + include_once "$props/options/items/RadioPropertyItem.class.php"; + + $importPluginProperties = new 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 OptionsPropertyRootGroup(); + $importSpecificOptions->setName("Format Specific Options"); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup(); + $generalOptions->setName("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 RadioPropertyItem(); + $leaf->setName("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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Handles the whole import logic + * + * @return void + */ + public function doImport() + { + // get globals (others are optional) + global $error, $timeout_passed, $finished; + + $buffer = ''; + while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) { + $data = PMA_importGetNextChunk(); + 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: + PMA_importRunQuery($sql, $verbose_sql_with_comments); + } // End of import loop + // Commit any possible data in buffers + PMA_importRunQuery(); + } + + + // 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/sources/libraries/plugins/import/ShapeFile.class.php b/sources/libraries/plugins/import/ShapeFile.class.php new file mode 100644 index 0000000..bad2751 --- /dev/null +++ b/sources/libraries/plugins/import/ShapeFile.class.php @@ -0,0 +1,102 @@ +_loadHeaders(); + $this->_loadRecords(); + if ($this->_isDbaseLoaded()) { + $this->_closeDBFFile(); + } + } + + /** + * Loads metadata from the ESRI shape file header + * + * @return void + * @see ShapeFile::_loadHeaders() + */ + function _loadHeaders() + { + ImportShp::readFromBuffer(24); + $this->fileLength = loadData("N", ImportShp::readFromBuffer(4)); + + ImportShp::readFromBuffer(4); + $this->shapeType = loadData("V", ImportShp::readFromBuffer(4)); + + $this->boundingBox = array(); + $this->boundingBox["xmin"] = loadData("d", ImportShp::readFromBuffer(8)); + $this->boundingBox["ymin"] = loadData("d", ImportShp::readFromBuffer(8)); + $this->boundingBox["xmax"] = loadData("d", ImportShp::readFromBuffer(8)); + $this->boundingBox["ymax"] = loadData("d", ImportShp::readFromBuffer(8)); + + if ($this->_isDbaseLoaded() && $this->_openDBFFile()) { + $this->DBFHeader = $this->_loadDBFHeader(); + } + } + + /** + * Loads geometry data from the ESRI shape file + * + * @return boolean|void + * @see ShapeFile::_loadRecords() + */ + function _loadRecords() + { + global $eof; + ImportShp::readFromBuffer(32); + while (true) { + $record = new PMA_ShapeRecord(-1); + $record->loadFromFile($this->SHPFile, $this->DBFFile); + if ($record->lastError != "") { + return false; + } + if ($eof) { + break; + } + + $this->records[] = $record; + } + } +} +?> diff --git a/sources/libraries/plugins/import/ShapeRecord.class.php b/sources/libraries/plugins/import/ShapeRecord.class.php new file mode 100644 index 0000000..7317c00 --- /dev/null +++ b/sources/libraries/plugins/import/ShapeRecord.class.php @@ -0,0 +1,161 @@ +DBFFile = $DBFFile; + $this->_loadHeaders(); + + switch ($this->shapeType) { + case 0: + $this->_loadNullRecord(); + break; + case 1: + $this->_loadPointRecord(); + break; + case 3: + $this->_loadPolyLineRecord(); + break; + case 5: + $this->_loadPolygonRecord(); + break; + case 8: + $this->_loadMultiPointRecord(); + break; + default: + $this->setError( + sprintf( + __("Geometry type '%s' is not supported by MySQL."), + $this->shapeType + ) + ); + break; + } + if (extension_loaded('dbase') && isset($this->DBFFile)) { + $this->_loadDBFData(); + } + } + + /** + * Loads metadata from the ESRI shape record header + * + * @return void + * @see ShapeRecord::_loadHeaders() + */ + function _loadHeaders() + { + $this->recordNumber = loadData("N", ImportShp::readFromBuffer(4)); + ImportShp::readFromBuffer(4); + $this->shapeType = loadData("V", ImportShp::readFromBuffer(4)); + } + + /** + * Loads data from a point record + * + * @return array + * @see ShapeRecord::_loadPoint() + */ + function _loadPoint() + { + $data = array(); + + $data["x"] = loadData("d", ImportShp::readFromBuffer(8)); + $data["y"] = loadData("d", ImportShp::readFromBuffer(8)); + + return $data; + } + + /** + * Loads data from a multipoint record + * + * @return void + * @see ShapeRecord::_loadMultiPointRecord() + */ + function _loadMultiPointRecord() + { + $this->SHPData = array(); + $this->SHPData["xmin"] = loadData("d", ImportShp::readFromBuffer(8)); + $this->SHPData["ymin"] = loadData("d", ImportShp::readFromBuffer(8)); + $this->SHPData["xmax"] = loadData("d", ImportShp::readFromBuffer(8)); + $this->SHPData["ymax"] = loadData("d", ImportShp::readFromBuffer(8)); + + $this->SHPData["numpoints"] = loadData("V", ImportShp::readFromBuffer(4)); + + for ($i = 0; $i <= $this->SHPData["numpoints"]; $i++) { + $this->SHPData["points"][] = $this->_loadPoint(); + } + } + + /** + * Loads data from a polyline record + * + * @return void + * @see ShapeRecord::_loadPolyLineRecord() + */ + function _loadPolyLineRecord() + { + $this->SHPData = array(); + $this->SHPData["xmin"] = loadData("d", ImportShp::readFromBuffer(8)); + $this->SHPData["ymin"] = loadData("d", ImportShp::readFromBuffer(8)); + $this->SHPData["xmax"] = loadData("d", ImportShp::readFromBuffer(8)); + $this->SHPData["ymax"] = loadData("d", ImportShp::readFromBuffer(8)); + + $this->SHPData["numparts"] = loadData("V", ImportShp::readFromBuffer(4)); + $this->SHPData["numpoints"] = loadData("V", ImportShp::readFromBuffer(4)); + + for ($i = 0; $i < $this->SHPData["numparts"]; $i++) { + $this->SHPData["parts"][$i] = loadData( + "V", ImportShp::readFromBuffer(4) + ); + } + + $readPoints = 0; + reset($this->SHPData["parts"]); + while (list($partIndex, $partData) = each($this->SHPData["parts"])) { + if (! isset($this->SHPData["parts"][$partIndex]["points"]) + || !is_array($this->SHPData["parts"][$partIndex]["points"]) + ) { + $this->SHPData["parts"][$partIndex] = array(); + $this->SHPData["parts"][$partIndex]["points"] = array(); + } + while (! in_array($readPoints, $this->SHPData["parts"]) + && ($readPoints < ($this->SHPData["numpoints"])) + ) { + $this->SHPData["parts"][$partIndex]["points"][] + = $this->_loadPoint(); + $readPoints++; + } + } + } +} +?> diff --git a/sources/libraries/plugins/import/upload/UploadApc.class.php b/sources/libraries/plugins/import/upload/UploadApc.class.php new file mode 100644 index 0000000..3ddb7a7 --- /dev/null +++ b/sources/libraries/plugins/import/upload/UploadApc.class.php @@ -0,0 +1,84 @@ + $id, + 'finished' => false, + 'percent' => 0, + 'total' => 0, + 'complete' => 0, + 'plugin' => UploadApc::getIdKey() + ); + } + $ret = $_SESSION[$SESSION_KEY][$id]; + + if (! PMA_Import_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; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/import/upload/UploadNoplugin.class.php b/sources/libraries/plugins/import/upload/UploadNoplugin.class.php new file mode 100644 index 0000000..90148de --- /dev/null +++ b/sources/libraries/plugins/import/upload/UploadNoplugin.class.php @@ -0,0 +1,64 @@ + $id, + 'finished' => false, + 'percent' => 0, + 'total' => 0, + 'complete' => 0, + 'plugin' => UploadNoplugin::getIdKey() + ); + } + $ret = $_SESSION[$SESSION_KEY][$id]; + + return $ret; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/import/upload/UploadProgress.class.php b/sources/libraries/plugins/import/upload/UploadProgress.class.php new file mode 100644 index 0000000..d7647b3 --- /dev/null +++ b/sources/libraries/plugins/import/upload/UploadProgress.class.php @@ -0,0 +1,94 @@ + $id, + 'finished' => false, + 'percent' => 0, + 'total' => 0, + 'complete' => 0, + 'plugin' => UploadProgress::getIdKey() + ); + } + $ret = $_SESSION[$SESSION_KEY][$id]; + + if (! PMA_Import_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; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/import/upload/UploadSession.class.php b/sources/libraries/plugins/import/upload/UploadSession.class.php new file mode 100644 index 0000000..d9bdd53 --- /dev/null +++ b/sources/libraries/plugins/import/upload/UploadSession.class.php @@ -0,0 +1,96 @@ + $id, + 'finished' => false, + 'percent' => 0, + 'total' => 0, + 'complete' => 0, + 'plugin' => UploadSession::getIdKey() + ); + } + $ret = $_SESSION[$SESSION_KEY][$id]; + + if (! PMA_Import_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; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Application_Octetstream_Download.class.php b/sources/libraries/plugins/transformations/Application_Octetstream_Download.class.php new file mode 100644 index 0000000..069eda7 --- /dev/null +++ b/sources/libraries/plugins/transformations/Application_Octetstream_Download.class.php @@ -0,0 +1,43 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Application_Octetstream_Hex.class.php b/sources/libraries/plugins/transformations/Application_Octetstream_Hex.class.php new file mode 100644 index 0000000..f6f4297 --- /dev/null +++ b/sources/libraries/plugins/transformations/Application_Octetstream_Hex.class.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Image_JPEG_Inline.class.php b/sources/libraries/plugins/transformations/Image_JPEG_Inline.class.php new file mode 100644 index 0000000..501205a --- /dev/null +++ b/sources/libraries/plugins/transformations/Image_JPEG_Inline.class.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Image_JPEG_Link.class.php b/sources/libraries/plugins/transformations/Image_JPEG_Link.class.php new file mode 100644 index 0000000..ca3c1d1 --- /dev/null +++ b/sources/libraries/plugins/transformations/Image_JPEG_Link.class.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Image_PNG_Inline.class.php b/sources/libraries/plugins/transformations/Image_PNG_Inline.class.php new file mode 100644 index 0000000..40b709d --- /dev/null +++ b/sources/libraries/plugins/transformations/Image_PNG_Inline.class.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/README b/sources/libraries/plugins/transformations/README new file mode 100644 index 0000000..7d7a125 --- /dev/null +++ b/sources/libraries/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/sources/libraries/plugins/transformations/TEMPLATE b/sources/libraries/plugins/transformations/TEMPLATE new file mode 100644 index 0000000..6f116ce --- /dev/null +++ b/sources/libraries/plugins/transformations/TEMPLATE @@ -0,0 +1,46 @@ + diff --git a/sources/libraries/plugins/transformations/TEMPLATE_ABSTRACT b/sources/libraries/plugins/transformations/TEMPLATE_ABSTRACT new file mode 100644 index 0000000..5cc1d26 --- /dev/null +++ b/sources/libraries/plugins/transformations/TEMPLATE_ABSTRACT @@ -0,0 +1,89 @@ +mimetype contains the original MimeType of the field (i.e. 'text/plain', 'image/jpeg' etc.) + + return $buffer; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @todo implement + * @return void + */ + public function update (SplSubject $subject) + { + ; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the TransformationName of the specific plugin + * + * @return string + */ + public static function getName() + { + return "[TransformationName]"; + } +} +?> diff --git a/sources/libraries/plugins/transformations/Text_Plain_Append.class.php b/sources/libraries/plugins/transformations/Text_Plain_Append.class.php new file mode 100644 index 0000000..567c2cc --- /dev/null +++ b/sources/libraries/plugins/transformations/Text_Plain_Append.class.php @@ -0,0 +1,45 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Text_Plain_Dateformat.class.php b/sources/libraries/plugins/transformations/Text_Plain_Dateformat.class.php new file mode 100644 index 0000000..70db98a --- /dev/null +++ b/sources/libraries/plugins/transformations/Text_Plain_Dateformat.class.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Text_Plain_External.class.php b/sources/libraries/plugins/transformations/Text_Plain_External.class.php new file mode 100644 index 0000000..18da613 --- /dev/null +++ b/sources/libraries/plugins/transformations/Text_Plain_External.class.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Text_Plain_Formatted.class.php b/sources/libraries/plugins/transformations/Text_Plain_Formatted.class.php new file mode 100644 index 0000000..3942a93 --- /dev/null +++ b/sources/libraries/plugins/transformations/Text_Plain_Formatted.class.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Text_Plain_Imagelink.class.php b/sources/libraries/plugins/transformations/Text_Plain_Imagelink.class.php new file mode 100644 index 0000000..670e52e --- /dev/null +++ b/sources/libraries/plugins/transformations/Text_Plain_Imagelink.class.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Text_Plain_Link.class.php b/sources/libraries/plugins/transformations/Text_Plain_Link.class.php new file mode 100644 index 0000000..d1b17d5 --- /dev/null +++ b/sources/libraries/plugins/transformations/Text_Plain_Link.class.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Text_Plain_Longtoipv4.class.php b/sources/libraries/plugins/transformations/Text_Plain_Longtoipv4.class.php new file mode 100644 index 0000000..9f58a45 --- /dev/null +++ b/sources/libraries/plugins/transformations/Text_Plain_Longtoipv4.class.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Text_Plain_Sql.class.php b/sources/libraries/plugins/transformations/Text_Plain_Sql.class.php new file mode 100644 index 0000000..1987ba9 --- /dev/null +++ b/sources/libraries/plugins/transformations/Text_Plain_Sql.class.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/Text_Plain_Substring.class.php b/sources/libraries/plugins/transformations/Text_Plain_Substring.class.php new file mode 100644 index 0000000..051ae51 --- /dev/null +++ b/sources/libraries/plugins/transformations/Text_Plain_Substring.class.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/abstract/AppendTransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/AppendTransformationsPlugin.class.php new file mode 100644 index 0000000..86bb0be --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/AppendTransformationsPlugin.class.php @@ -0,0 +1,84 @@ + diff --git a/sources/libraries/plugins/transformations/abstract/DateFormatTransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/DateFormatTransformationsPlugin.class.php new file mode 100644 index 0000000..b687934 --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/DateFormatTransformationsPlugin.class.php @@ -0,0 +1,178 @@ +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 (strlen($buffer) == 14 || strlen($buffer) == 8) { + $offset = 4; + } else { + $offset = 2; + } + + $d = array(); + $d['year'] = substr($buffer, 0, $offset); + $d['month'] = substr($buffer, $offset, 2); + $d['day'] = substr($buffer, $offset + 2, 2); + $d['hour'] = substr($buffer, $offset + 4, 2); + $d['minute'] = substr($buffer, $offset + 6, 2); + $d['second'] = substr($buffer, $offset + 8, 2); + + if (checkdate($d['month'], $d['day'], $d['year'])) { + $timestamp = mktime( + $d['hour'], + $d['minute'], + $d['second'], + $d['month'], + $d['day'], + $d['year'] + ); + } + // If all fails, assume one of the dozens of valid strtime() syntaxes + // (http://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 = PMA_Util::localisedDate( + $timestamp, + $options[1] + ); + } elseif ($options[2] == 'utc') { + $text = gmdate($options[1], $timestamp); + } else { + $text = 'INVALID DATE TYPE'; + } + $buffer = '' . $text . ''; + } + + return $buffer; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @todo implement + * @return void + */ + public function update (SplSubject $subject) + { + ; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Date Format"; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/abstract/DownloadTransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/DownloadTransformationsPlugin.class.php new file mode 100644 index 0000000..134fb7f --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/DownloadTransformationsPlugin.class.php @@ -0,0 +1,110 @@ + $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'], + urlencode($cn), + htmlspecialchars($cn), + htmlspecialchars($cn) + ); + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @todo implement + * @return void + */ + public function update (SplSubject $subject) + { + ; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Download"; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/abstract/ExternalTransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/ExternalTransformationsPlugin.class.php new file mode 100644 index 0000000..86ba1ac --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/ExternalTransformationsPlugin.class.php @@ -0,0 +1,182 @@ += 4.3.0 + $newstring = ''; + $descriptorspec = array( + 0 => array("pipe", "r"), + 1 => array("pipe", "w") + ); + $process = proc_open($program . ' ' . $poptions, $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; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @todo implement + * @return void + */ + public function update (SplSubject $subject) + { + ; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "External"; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/abstract/FormattedTransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/FormattedTransformationsPlugin.class.php new file mode 100644 index 0000000..7a39e45 --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/FormattedTransformationsPlugin.class.php @@ -0,0 +1,80 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/abstract/HexTransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/HexTransformationsPlugin.class.php new file mode 100644 index 0000000..2809837 --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/HexTransformationsPlugin.class.php @@ -0,0 +1,91 @@ + \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/abstract/ImageLinkTransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/ImageLinkTransformationsPlugin.class.php new file mode 100644 index 0000000..d5059fd --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/ImageLinkTransformationsPlugin.class.php @@ -0,0 +1,87 @@ + '[BLOB]' + ); + return PMA_Transformation_globalHtmlReplace( + $buffer, + $transform_options + ); + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @todo implement + * @return void + */ + public function update (SplSubject $subject) + { + ; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "ImageLink"; + } +} +?> diff --git a/sources/libraries/plugins/transformations/abstract/InlineTransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/InlineTransformationsPlugin.class.php new file mode 100644 index 0000000..e6c9ca8 --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/InlineTransformationsPlugin.class.php @@ -0,0 +1,101 @@ + '[__BUFFER__]' + ); + } else { + $transform_options = array ( + 'string' => '[__BUFFER__]' + ); + } + return PMA_Transformation_globalHtmlReplace( + $buffer, + $transform_options + ); + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @todo implement + * @return void + */ + public function update (SplSubject $subject) + { + ; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Inline"; + } +} +?> diff --git a/sources/libraries/plugins/transformations/abstract/LongToIPv4TransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/LongToIPv4TransformationsPlugin.class.php new file mode 100644 index 0000000..d1431ce --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/LongToIPv4TransformationsPlugin.class.php @@ -0,0 +1,83 @@ + 4294967295) { + return $buffer; + } + + return long2ip($buffer); + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @todo implement + * @return void + */ + public function update (SplSubject $subject) + { + ; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Long To IPv4"; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/abstract/SQLTransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/SQLTransformationsPlugin.class.php new file mode 100644 index 0000000..991f54d --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/SQLTransformationsPlugin.class.php @@ -0,0 +1,82 @@ + diff --git a/sources/libraries/plugins/transformations/abstract/SubstringTransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/SubstringTransformationsPlugin.class.php new file mode 100644 index 0000000..b6fb75a --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/SubstringTransformationsPlugin.class.php @@ -0,0 +1,118 @@ +substr( + $buffer, $options[0], $options[1] + ); + } else { + $newtext = $GLOBALS['PMA_String']->substr($buffer, $options[0]); + } + + $length = strlen($newtext); + $baselength = strlen($buffer); + if ($length != $baselength) { + if ($options[0] != 0) { + $newtext = $options[2] . $newtext; + } + + if (($length + $options[0]) != $baselength) { + $newtext .= $options[2]; + } + } + + return $newtext; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @todo implement + * @return void + */ + public function update (SplSubject $subject) + { + ; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Substring"; + } +} +?> diff --git a/sources/libraries/plugins/transformations/abstract/TextImageLinkTransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/TextImageLinkTransformationsPlugin.class.php new file mode 100644 index 0000000..a1824c9 --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/TextImageLinkTransformationsPlugin.class.php @@ -0,0 +1,96 @@ + '' + . $buffer . '' + ); + + $buffer = PMA_Transformation_globalHtmlReplace( + $buffer, + $transform_options + ); + + return $buffer; + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @todo implement + * @return void + */ + public function update (SplSubject $subject) + { + ; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Image Link"; + } +} +?> diff --git a/sources/libraries/plugins/transformations/abstract/TextLinkTransformationsPlugin.class.php b/sources/libraries/plugins/transformations/abstract/TextLinkTransformationsPlugin.class.php new file mode 100644 index 0000000..da8fb59 --- /dev/null +++ b/sources/libraries/plugins/transformations/abstract/TextLinkTransformationsPlugin.class.php @@ -0,0 +1,98 @@ + '' + . htmlspecialchars(isset($options[1]) ? $options[1] : $buffer) + . '' + ); + + return PMA_Transformation_globalHtmlReplace( + $buffer, + $transform_options + ); + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @todo implement + * @return void + */ + public function update (SplSubject $subject) + { + ; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "TextLink"; + } +} +?> diff --git a/sources/libraries/plugins/transformations/generator_main_class.sh b/sources/libraries/plugins/transformations/generator_main_class.sh new file mode 100755 index 0000000..0587667 --- /dev/null +++ b/sources/libraries/plugins/transformations/generator_main_class.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# +# Shell script that creates only the main class for a new transformation +# plug-in, using a template +# +# $1: MIMEType +# $2: MIMESubtype +# $3: Transformation Name + +if [ $# != 3 ] +then + echo -e "Usage: ./generator_main_class.sh MIMEType MIMESubtype TransformationName\n" + exit 65 +fi + +./generator_plugin.sh "$1" "$2" "$3" "--generate_only_main_class" \ No newline at end of file diff --git a/sources/libraries/plugins/transformations/generator_plugin.sh b/sources/libraries/plugins/transformations/generator_plugin.sh new file mode 100755 index 0000000..225a2cb --- /dev/null +++ b/sources/libraries/plugins/transformations/generator_plugin.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# +# Shell script that creates a new transformation plug-in (both main and +# abstract class) using a template. +# +# The 'description' parameter will add a new entry in the language file. +# Watch out for special escaping. +# +# $1: MIMEType +# $2: MIMESubtype +# $3: Transformation Name +# $4: (optional) Description + +echo $# +if [ $# -ne 3 -a $# -ne 4 ]; then + echo -e "Usage: ./generator_plugin.sh MIMEType MIMESubtype TransformationName [Description]\n" + exit 65 +fi + +# make sure that the MIME Type, MIME Subtype and Transformation names +# are in the correct format + +# make all names lowercase +MT="`echo $1 | tr [:upper:] [:lower:]`" +MS="`echo $2 | tr [:upper:] [:lower:]`" +TN="`echo $3 | tr [:upper:] [:lower:]`" +# make first letter uppercase +MT="${MT^}" +MS="${MS^}" +TN="${TN^}" +# make the first letter after each underscore uppercase +MT="`echo $MT`" +MT="`echo $MT | sed -e 's/_./\U&\E/g'`" +MS="`echo $MS`" +MS="`echo $MS | sed -e 's/_./\U&\E/g'`" +TN="`echo $TN`" +TN="`echo $TN | sed -e 's/_./\U&\E/g'`" + +# define the name of the main class file and of its template +ClassFile=$MT\_$MS\_$TN.class.php +Template=TEMPLATE +# define the name of the abstract class file and its template +AbstractClassFile=abstract/"$TN"TransformationsPlugin.class.php +AbstractTemplate=TEMPLATE_ABSTRACT +# replace template names with argument names +sed "s/\[MIMEType]/$MT/; s/\[MIMESubtype\]/$MS/; s/\[TransformationName\]/$TN/;" < $Template > $ClassFile +echo "Created $ClassFile" + +GenerateAbstractClass=1 +if [ -n $4 ]; then + if [ "$4" == "--generate_only_main_class" ]; then + if [ -e $AbstractClassFile ]; then + GenerateAbstractClass=0 + fi + fi +fi + +if [ $GenerateAbstractClass -eq 1 ]; then + # replace template names with argument names + sed "s/\[TransformationName\]/$TN/; s/Description of the transformation./$4/;" < $AbstractTemplate > $AbstractClassFile + echo "Created $AbstractClassFile" +fi + +echo "" \ No newline at end of file diff --git a/sources/libraries/pmd_common.php b/sources/libraries/pmd_common.php new file mode 100644 index 0000000..6583991 --- /dev/null +++ b/sources/libraries/pmd_common.php @@ -0,0 +1,284 @@ +getTablesFull($GLOBALS['db']); + // seems to be needed later + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + $i = 0; + foreach ($tables as $one_table) { + $GLOBALS['PMD']['TABLE_NAME'][$i] + = $GLOBALS['db'] . "." . $one_table['TABLE_NAME']; + $GLOBALS['PMD']['OWNER'][$i] = $GLOBALS['db']; + $GLOBALS['PMD']['TABLE_NAME_SMALL'][$i] = $one_table['TABLE_NAME']; + + $GLOBALS['PMD_URL']['TABLE_NAME'][$i] + = urlencode($GLOBALS['db'] . "." . $one_table['TABLE_NAME']); + $GLOBALS['PMD_URL']['OWNER'][$i] = urlencode($GLOBALS['db']); + $GLOBALS['PMD_URL']['TABLE_NAME_SMALL'][$i] + = urlencode($one_table['TABLE_NAME']); + + $GLOBALS['PMD_OUT']['TABLE_NAME'][$i] = htmlspecialchars( + $GLOBALS['db'] . "." . $one_table['TABLE_NAME'], ENT_QUOTES + ); + $GLOBALS['PMD_OUT']['OWNER'][$i] = htmlspecialchars( + $GLOBALS['db'], ENT_QUOTES + ); + $GLOBALS['PMD_OUT']['TABLE_NAME_SMALL'][$i] = htmlspecialchars( + $one_table['TABLE_NAME'], ENT_QUOTES + ); + + $GLOBALS['PMD']['TABLE_TYPE'][$i] = strtoupper($one_table['ENGINE']); + + $DF = PMA_getDisplayField($GLOBALS['db'], $one_table['TABLE_NAME']); + if ($DF != '') { + $retval[$GLOBALS['PMD_URL']["TABLE_NAME_SMALL"][$i]] = urlencode($DF); + } + + $i++; + } + + return $retval; +} + +/** + * retrieves table column info + * + * @return array table column nfo + */ +function get_columns_info() +{ + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + $tab_column = array(); + for ($i = 0, $cnt = count($GLOBALS['PMD']["TABLE_NAME"]); $i < $cnt; $i++) { + $fields_rs = $GLOBALS['dbi']->query( + $GLOBALS['dbi']->getColumnsSql( + $GLOBALS['db'], + $GLOBALS['PMD']["TABLE_NAME_SMALL"][$i], + null, + true + ), + null, + PMA_DatabaseInterface::QUERY_STORE + ); + $tbl_name_i = $GLOBALS['PMD']['TABLE_NAME'][$i]; + $j = 0; + while ($row = $GLOBALS['dbi']->fetchAssoc($fields_rs)) { + $tab_column[$tbl_name_i]['COLUMN_ID'][$j] = $j; + $tab_column[$tbl_name_i]['COLUMN_NAME'][$j] = $row['Field']; + $tab_column[$tbl_name_i]['TYPE'][$j] = $row['Type']; + $tab_column[$tbl_name_i]['NULLABLE'][$j] = $row['Null']; + $j++; + } + } + return $tab_column; +} + +/** + * returns JavaScript code for intializing vars + * + * @return string JavaScript code + */ +function get_script_contr() +{ + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + $con["C_NAME"] = array(); + $i = 0; + $alltab_rs = $GLOBALS['dbi']->query( + 'SHOW TABLES FROM ' . PMA_Util::backquote($GLOBALS['db']), + null, + PMA_DatabaseInterface::QUERY_STORE + ); + while ($val = @$GLOBALS['dbi']->fetchRow($alltab_rs)) { + $row = PMA_getForeigners($GLOBALS['db'], $val[0], '', 'internal'); + //echo "
      internal ".$GLOBALS['db']." - ".$val[0]." - "; + //print_r($row); + if ($row !== false) { + foreach ($row as $field => $value) { + $con['C_NAME'][$i] = ''; + $con['DTN'][$i] = urlencode($GLOBALS['db'] . "." . $val[0]); + $con['DCN'][$i] = urlencode($field); + $con['STN'][$i] = urlencode( + $value['foreign_db'] . "." . $value['foreign_table'] + ); + $con['SCN'][$i] = urlencode($value['foreign_field']); + $i++; + } + } + $row = PMA_getForeigners($GLOBALS['db'], $val[0], '', 'foreign'); + //echo "
      INNO "; + //print_r($row); + if ($row !== false) { + foreach ($row as $field => $value) { + $con['C_NAME'][$i] = ''; + $con['DTN'][$i] = urlencode($GLOBALS['db'].".".$val[0]); + $con['DCN'][$i] = urlencode($field); + $con['STN'][$i] = urlencode( + $value['foreign_db'].".".$value['foreign_table'] + ); + $con['SCN'][$i] = urlencode($value['foreign_field']); + $i++; + } + } + } + + $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, $GLOBALS['PMD_URL']["TABLE_NAME"]) + && in_array($con['STN'][$i], $GLOBALS['PMD_URL']["TABLE_NAME"]) + ) { + $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 + * + * @return array unique or primary indices + */ +function get_pk_or_unique_keys() +{ + return get_all_keys(true); +} + +/** + * returns all indices + * + * @param bool $unique_only whether to include only unique ones + * + * @return array indices + */ +function get_all_keys($unique_only = false) +{ + include_once './libraries/Index.class.php'; + + $keys = array(); + + foreach ($GLOBALS['PMD']['TABLE_NAME_SMALL'] as $I => $table) { + $schema = $GLOBALS['PMD']['OWNER'][$I]; + // for now, take into account only the first index segment + foreach (PMA_Index::getFromTable($table, $schema) as $index) { + if ($unique_only && ! $index->isUnique()) { + continue; + } + $columns = $index->getColumns(); + foreach ($columns as $column_name => $dummy) { + $keys[$schema . '.' .$table . '.' . $column_name] = 1; + } + } + } + return $keys; +} + +/** + * Return script to create j_tab and h_tab arrays + * + * @return string + */ +function get_script_tabs() +{ + $retval = array( + 'j_tabs' => array(), + 'h_tabs' => array() + ); + + for ($i = 0, $cnt = count($GLOBALS['PMD']['TABLE_NAME']); $i < $cnt; $i++) { + $j = 0; + if (PMA_Util::isForeignKeySupported($GLOBALS['PMD']['TABLE_TYPE'][$i])) { + $j = 1; + } + $retval['j_tabs'][$GLOBALS['PMD_URL']['TABLE_NAME'][$i]] = $j; + $retval['h_tabs'][$GLOBALS['PMD_URL']['TABLE_NAME'][$i]] = 1; + } + return $retval; +} + +/** + * Returns table position + * + * @return array table positions and sizes + */ +function get_tab_pos() +{ + $cfgRelation = PMA_getRelationsParam(); + + if (! $cfgRelation['designerwork']) { + return null; + } + + $query = " + SELECT CONCAT_WS('.', `db_name`, `table_name`) AS `name`, + `x` AS `X`, + `y` AS `Y`, + `v` AS `V`, + `h` AS `H` + FROM " . PMA_Util::backquote($cfgRelation['db']) + . "." . PMA_Util::backquote($cfgRelation['designer_coords']); + $tab_pos = $GLOBALS['dbi']->fetchResult( + $query, + 'name', + null, + $GLOBALS['controllink'], + PMA_DatabaseInterface::QUERY_STORE + ); + return count($tab_pos) ? $tab_pos : null; +} + +/** + * Prepares XML output for js/pmd/ajax.js to display a message + * + */ +function PMD_return_upd($b, $ret) +{ + // not sure where this was defined... + global $K; + + header("Content-Type: text/xml; charset=utf-8"); + header("Cache-Control: no-cache"); + die(''); +} +?> diff --git a/sources/libraries/properties/PropertyItem.class.php b/sources/libraries/properties/PropertyItem.class.php new file mode 100644 index 0000000..83a5b26 --- /dev/null +++ b/sources/libraries/properties/PropertyItem.class.php @@ -0,0 +1,49 @@ + \ No newline at end of file diff --git a/sources/libraries/properties/options/OptionsPropertyGroup.class.php b/sources/libraries/properties/options/OptionsPropertyGroup.class.php new file mode 100644 index 0000000..fe2fe40 --- /dev/null +++ b/sources/libraries/properties/options/OptionsPropertyGroup.class.php @@ -0,0 +1,102 @@ +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_udiff( + $this->getProperties(), + array($property), + function ($a, $b) { + return ($a === $b ) ? 0 : 1; + } + ); + } + + + /* ~~~~~~~~~~~~~~~~~~~~ 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() + { + return count($this->_properties); + } +} +?> diff --git a/sources/libraries/properties/options/OptionsPropertyItem.class.php b/sources/libraries/properties/options/OptionsPropertyItem.class.php new file mode 100644 index 0000000..a1718a2 --- /dev/null +++ b/sources/libraries/properties/options/OptionsPropertyItem.class.php @@ -0,0 +1,127 @@ +_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 paramter + * + * @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"; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/properties/options/OptionsPropertyOneItem.class.php b/sources/libraries/properties/options/OptionsPropertyOneItem.class.php new file mode 100644 index 0000000..e19c15c --- /dev/null +++ b/sources/libraries/properties/options/OptionsPropertyOneItem.class.php @@ -0,0 +1,172 @@ +_force; + } + + /** + * Sets the force parameter + * + * @param bool $force force parameter + * + * @return void + */ + public function setForce($force) + { + $this->_force = $force; + } + + /** + * Gets the values + * + * @return string + */ + public function getValues() + { + return $this->_values; + } + + /** + * Sets the values + * + * @param array $values values + * + * @return void + */ + public function setValues($values) + { + $this->_values = $values; + } + + /** + * Gets the type of the newline character + * + * @return string + */ + public function getDoc() + { + return $this->_doc; + } + + /** + * Sets the doc + * + * @param string $doc doc + * + * @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; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/properties/options/groups/OptionsPropertyMainGroup.class.php b/sources/libraries/properties/options/groups/OptionsPropertyMainGroup.class.php new file mode 100644 index 0000000..4e69aa7 --- /dev/null +++ b/sources/libraries/properties/options/groups/OptionsPropertyMainGroup.class.php @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/sources/libraries/properties/options/groups/OptionsPropertyRootGroup.class.php b/sources/libraries/properties/options/groups/OptionsPropertyRootGroup.class.php new file mode 100644 index 0000000..a081744 --- /dev/null +++ b/sources/libraries/properties/options/groups/OptionsPropertyRootGroup.class.php @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/sources/libraries/properties/options/groups/OptionsPropertySubgroup.class.php b/sources/libraries/properties/options/groups/OptionsPropertySubgroup.class.php new file mode 100644 index 0000000..0eeb524 --- /dev/null +++ b/sources/libraries/properties/options/groups/OptionsPropertySubgroup.class.php @@ -0,0 +1,68 @@ +_subgroupHeader; + } + + /** + * Sets the subgroup header + * + * @param string $subgroupHeader subgroup header + * + * @return void + */ + public function setSubgroupHeader($subgroupHeader) + { + $this->_subgroupHeader = $subgroupHeader; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/properties/options/items/BoolPropertyItem.class.php b/sources/libraries/properties/options/items/BoolPropertyItem.class.php new file mode 100644 index 0000000..f33067f --- /dev/null +++ b/sources/libraries/properties/options/items/BoolPropertyItem.class.php @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/sources/libraries/properties/options/items/DocPropertyItem.class.php b/sources/libraries/properties/options/items/DocPropertyItem.class.php new file mode 100644 index 0000000..55aff61 --- /dev/null +++ b/sources/libraries/properties/options/items/DocPropertyItem.class.php @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/sources/libraries/properties/options/items/HiddenPropertyItem.class.php b/sources/libraries/properties/options/items/HiddenPropertyItem.class.php new file mode 100644 index 0000000..53bfe46 --- /dev/null +++ b/sources/libraries/properties/options/items/HiddenPropertyItem.class.php @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/sources/libraries/properties/options/items/MessageOnlyPropertyItem.class.php b/sources/libraries/properties/options/items/MessageOnlyPropertyItem.class.php new file mode 100644 index 0000000..98f2e70 --- /dev/null +++ b/sources/libraries/properties/options/items/MessageOnlyPropertyItem.class.php @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/sources/libraries/properties/options/items/NumberPropertyItem.class.php b/sources/libraries/properties/options/items/NumberPropertyItem.class.php new file mode 100644 index 0000000..7c687c7 --- /dev/null +++ b/sources/libraries/properties/options/items/NumberPropertyItem.class.php @@ -0,0 +1,35 @@ + diff --git a/sources/libraries/properties/options/items/RadioPropertyItem.class.php b/sources/libraries/properties/options/items/RadioPropertyItem.class.php new file mode 100644 index 0000000..4d8ed7a --- /dev/null +++ b/sources/libraries/properties/options/items/RadioPropertyItem.class.php @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/sources/libraries/properties/options/items/SelectPropertyItem.class.php b/sources/libraries/properties/options/items/SelectPropertyItem.class.php new file mode 100644 index 0000000..28460c9 --- /dev/null +++ b/sources/libraries/properties/options/items/SelectPropertyItem.class.php @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/sources/libraries/properties/options/items/TextPropertyItem.class.php b/sources/libraries/properties/options/items/TextPropertyItem.class.php new file mode 100644 index 0000000..9339cdf --- /dev/null +++ b/sources/libraries/properties/options/items/TextPropertyItem.class.php @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/sources/libraries/properties/plugins/ExportPluginProperties.class.php b/sources/libraries/properties/plugins/ExportPluginProperties.class.php new file mode 100644 index 0000000..6170897 --- /dev/null +++ b/sources/libraries/properties/plugins/ExportPluginProperties.class.php @@ -0,0 +1,214 @@ +_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; + } + + /** + * Gets the force file parameter + * + * @return bool + */ + public function getForceFile() + { + return $this->_forceFile; + } + + /** + * Sets the force file parameter + * + * @param bool $forceFile the force file parameter + * + * @return void + */ + public function setForceFile($forceFile) + { + $this->_forceFile = $forceFile; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/properties/plugins/ImportPluginProperties.class.php b/sources/libraries/properties/plugins/ImportPluginProperties.class.php new file mode 100644 index 0000000..65c3092 --- /dev/null +++ b/sources/libraries/properties/plugins/ImportPluginProperties.class.php @@ -0,0 +1,184 @@ +_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 options text + * + * @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; + } +} +?> \ No newline at end of file diff --git a/sources/libraries/properties/plugins/PluginPropertyItem.class.php b/sources/libraries/properties/plugins/PluginPropertyItem.class.php new file mode 100644 index 0000000..af46be2 --- /dev/null +++ b/sources/libraries/properties/plugins/PluginPropertyItem.class.php @@ -0,0 +1,36 @@ + \ No newline at end of file diff --git a/sources/libraries/relation.lib.php b/sources/libraries/relation.lib.php new file mode 100644 index 0000000..d49ec14 --- /dev/null +++ b/sources/libraries/relation.lib.php @@ -0,0 +1,1497 @@ +query( + $sql, + $GLOBALS['controllink'], + $options, + $cache_affected_rows + ); + } else { + $result = @$GLOBALS['dbi']->tryQuery( + $sql, + $GLOBALS['controllink'], + $options, + $cache_affected_rows + ); + } // end if... else... + + if ($result) { + return $result; + } else { + return false; + } +} // end of the "PMA_queryAsControlUser()" function + +/** + * Returns current relation parameters + * + * @return array $cfgRelation + */ +function PMA_getRelationsParam() +{ + // avoid breakage if pmadb got unconfigured after login + if (! defined('TESTSUITE') && empty($GLOBALS['cfg']['Server']['pmadb'])) { + unset($_SESSION['relation'][$GLOBALS['server']]); + } + if (empty($_SESSION['relation'][$GLOBALS['server']])) { + $_SESSION['relation'][$GLOBALS['server']] = PMA_checkRelationsParam(); + } + + // just for BC but needs to be before PMA_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 + */ +function PMA_getRelationsParamDiagnostic($cfgRelation) +{ + $retval = ''; + + $messages['error'] = '' + . __('not OK') + . '' + . ' [ ' + . __('Documentation') + . ' ]'; + + $messages['ok'] = '' + . _pgettext('Correctly working', 'OK') + . ''; + + $messages['enabled'] = '' . __('Enabled') . ''; + $messages['disabled'] = '' . __('Disabled') . ''; + + if (false === $GLOBALS['cfg']['Server']['pmadb']) { + $retval .= 'PMA Database ... ' + . sprintf($messages['error'], 'pmadb') + . '
      ' . "\n" + . __('General relation features') + . ' ' . __('Disabled') + . '' . "\n"; + } else { + $retval .= '' . "\n"; + $retval .= PMA_getDiagMessageForParameter( + 'pmadb', + $GLOBALS['cfg']['Server']['pmadb'], + $messages, + 'pmadb' + ); + $retval .= PMA_getDiagMessageForParameter( + 'relation', + isset($cfgRelation['relation']), + $messages, + 'relation' + ); + $retval .= PMA_getDiagMessageForFeature( + __('General relation features'), + 'relwork', + $messages + ); + $retval .= PMA_getDiagMessageForParameter( + 'table_info', + isset($cfgRelation['table_info']), + $messages, + 'table_info' + ); + $retval .= PMA_getDiagMessageForFeature( + __('Display Features'), + 'displaywork', + $messages + ); + $retval .= PMA_getDiagMessageForParameter( + 'table_coords', + isset($cfgRelation['table_coords']), + $messages, + 'table_coords' + ); + $retval .= PMA_getDiagMessageForParameter( + 'pdf_pages', + isset($cfgRelation['pdf_pages']), + $messages, + 'pdf_pages' + ); + $retval .= PMA_getDiagMessageForFeature( + __('Creation of PDFs'), + 'pdfwork', + $messages + ); + $retval .= PMA_getDiagMessageForParameter( + 'column_info', + isset($cfgRelation['column_info']), + $messages, + 'column_info' + ); + $retval .= PMA_getDiagMessageForFeature( + __('Displaying Column Comments'), + 'commwork', + $messages, + false + ); + $retval .= PMA_getDiagMessageForFeature( + __('Browser transformation'), + 'mimework', + $messages + ); + if ($cfgRelation['commwork'] && ! $cfgRelation['mimework']) { + $retval .= ''; + } + $retval .= PMA_getDiagMessageForParameter( + 'bookmarktable', + isset($cfgRelation['bookmark']), + $messages, + 'bookmark' + ); + $retval .= PMA_getDiagMessageForFeature( + __('Bookmarked SQL query'), + 'bookmarkwork', + $messages + ); + $retval .= PMA_getDiagMessageForParameter( + 'history', + isset($cfgRelation['history']), + $messages, + 'history' + ); + $retval .= PMA_getDiagMessageForFeature( + __('SQL history'), + 'historywork', + $messages + ); + $retval .= PMA_getDiagMessageForParameter( + 'designer_coords', + isset($cfgRelation['designer_coords']), + $messages, + 'designer_coords' + ); + $retval .= PMA_getDiagMessageForFeature( + __('Designer'), + 'designerwork', + $messages + ); + $retval .= PMA_getDiagMessageForParameter( + 'recent', + isset($cfgRelation['recent']), + $messages, + 'recent' + ); + $retval .= PMA_getDiagMessageForFeature( + __('Persistent recently used tables'), + 'recentwork', + $messages + ); + $retval .= PMA_getDiagMessageForParameter( + 'table_uiprefs', + isset($cfgRelation['table_uiprefs']), + $messages, + 'table_uiprefs' + ); + $retval .= PMA_getDiagMessageForFeature( + __('Persistent tables\' UI preferences'), + 'uiprefswork', + $messages + ); + $retval .= PMA_getDiagMessageForParameter( + 'tracking', + isset($cfgRelation['tracking']), + $messages, + 'tracking' + ); + $retval .= PMA_getDiagMessageForFeature( + __('Tracking'), + 'trackingwork', + $messages + ); + $retval .= PMA_getDiagMessageForParameter( + 'userconfig', + isset($cfgRelation['userconfig']), + $messages, + 'userconfig' + ); + $retval .= PMA_getDiagMessageForFeature( + __('User preferences'), + 'userconfigwork', + $messages + ); + $retval .= PMA_getDiagMessageForParameter( + 'users', + isset($cfgRelation['users']), + $messages, + 'users' + ); + $retval .= PMA_getDiagMessageForParameter( + 'usergroups', + isset($cfgRelation['usergroups']), + $messages, + 'usergroups' + ); + $retval .= PMA_getDiagMessageForFeature( + __('Configurable menus'), + 'menuswork', + $messages + ); + $retval .= PMA_getDiagMessageForParameter( + 'navigationhiding', + isset($cfgRelation['navigationhiding']), + $messages, + 'navigationhiding' + ); + $retval .= PMA_getDiagMessageForFeature( + __('Hide/show navigation items'), + 'navwork', + $messages + ); + $retval .= '
      '; + $retval .= __('Please see the documentation on how to update your column_comments table.'); + $retval .= '
      ' . "\n"; + + $retval .= '

      ' . __('Quick steps to setup advanced features:') . '

      '; + $retval .= '
        '; + $retval .= '
      • '; + $retval .= __( + 'Create the needed tables with the ' + . 'examples/create_tables.sql.' + ); + $retval .= ' ' . PMA_Util::showDocu('setup', 'linked-tables'); + $retval .= '
      • '; + $retval .= '
      • '; + $retval .= __('Create a pma user and give access to these tables.'); + $retval .= ' ' . PMA_Util::showDocu('config', 'cfg_Servers_controluser'); + $retval .= '
      • '; + $retval .= '
      • '; + $retval .= __( + 'Enable advanced features in configuration file ' + . '(config.inc.php), for example by ' + . 'starting from config.sample.inc.php.' + ); + $retval .= ' ' . PMA_Util::showDocu('setup', 'quick-install'); + $retval .= '
      • '; + $retval .= '
      • '; + $retval .= __( + 'Re-login to phpMyAdmin to load the updated configuration file.' + ); + $retval .= '
      • '; + $retval .= '
      '; + } + + 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 + */ +function PMA_getDiagMessageForFeature($feature_name, + $relation_parameter, $messages, $skip_line = true +) { + $retval = ' ' . $feature_name . ': '; + if ($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 $relation_parameter_set whether this parameter is set + * @param array $messages utility messages + * @param string $doc_anchor anchor in documentation + * + * @return string + */ +function PMA_getDiagMessageForParameter($parameter, + $relation_parameter_set, $messages, $doc_anchor +) { + $retval = ''; + $retval .= '$cfg[\'Servers\'][$i][\'' . $parameter . '\'] ... '; + $retval .= ''; + if ($relation_parameter_set) { + $retval .= $messages['ok']; + } else { + $retval .= sprintf( + $messages['error'], + PMA_Util::getDocuLink('config', 'cfg_Servers_' . $doc_anchor) + ); + } + $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 + */ +function PMA_checkRelationsParam() +{ + $cfgRelation = array(); + $cfgRelation['relwork'] = false; + $cfgRelation['displaywork'] = false; + $cfgRelation['bookmarkwork'] = false; + $cfgRelation['pdfwork'] = false; + $cfgRelation['commwork'] = false; + $cfgRelation['mimework'] = false; + $cfgRelation['historywork'] = false; + $cfgRelation['recentwork'] = false; + $cfgRelation['uiprefswork'] = false; + $cfgRelation['trackingwork'] = false; + $cfgRelation['designerwork'] = false; + $cfgRelation['userconfigwork'] = false; + $cfgRelation['menuswork'] = false; + $cfgRelation['navwork'] = 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'], $GLOBALS['controllink']) + ) { + // No server selected -> no bookmark table + // we return the array with the falses in it, + // to avoid some 'Unitialized 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 ' + . PMA_Util::backquote( + $GLOBALS['cfg']['Server']['pmadb'] + ); + $tab_rs = PMA_queryAsControlUser( + $tab_query, false, PMA_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']['designer_coords']) { + $cfgRelation['designer_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']['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]; + } + } // end while + $GLOBALS['dbi']->freeResult($tab_rs); + + if (isset($cfgRelation['relation'])) { + $cfgRelation['relwork'] = true; + if (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; + $cfgRelation['mimework'] = true; + } + + if (isset($cfgRelation['history'])) { + $cfgRelation['historywork'] = true; + } + + if (isset($cfgRelation['recent'])) { + $cfgRelation['recentwork'] = true; + } + + if (isset($cfgRelation['table_uiprefs'])) { + $cfgRelation['uiprefswork'] = true; + } + + if (isset($cfgRelation['tracking'])) { + $cfgRelation['trackingwork'] = true; + } + + if (isset($cfgRelation['userconfig'])) { + $cfgRelation['userconfigwork'] = true; + } + + // we do not absolutely need that the internal relations or the PDF + // schema feature be activated + if (isset($cfgRelation['designer_coords'])) { + $cfgRelation['designerwork'] = 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 ($cfgRelation['relwork'] && $cfgRelation['displaywork'] + && $cfgRelation['pdfwork'] && $cfgRelation['commwork'] + && $cfgRelation['mimework'] && $cfgRelation['historywork'] + && $cfgRelation['recentwork'] && $cfgRelation['uiprefswork'] + && $cfgRelation['trackingwork'] && $cfgRelation['userconfigwork'] + && $cfgRelation['bookmarkwork'] && $cfgRelation['designerwork'] + && $cfgRelation['menuswork'] && $cfgRelation['navwork'] + ) { + $cfgRelation['allworks'] = true; + } + + return $cfgRelation; +} // end of the 'PMA_getRelationsParam()' function + +/** + * 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 + */ +function PMA_getForeigners($db, $table, $column = '', $source = 'both') +{ + $cfgRelation = PMA_getRelationsParam(); + $foreign = array(); + + if ($cfgRelation['relwork'] && ($source == 'both' || $source == 'internal')) { + $rel_query = ' + SELECT `master_field`, + `foreign_db`, + `foreign_table`, + `foreign_field` + FROM ' . PMA_Util::backquote($cfgRelation['db']) + . '.' . PMA_Util::backquote($cfgRelation['relation']) . ' + WHERE `master_db` = \'' . PMA_Util::sqlAddSlashes($db) . '\' + AND `master_table` = \'' . PMA_Util::sqlAddSlashes($table) . '\' '; + if (strlen($column)) { + $rel_query .= ' AND `master_field` = ' + . '\'' . PMA_Util::sqlAddSlashes($column) . '\''; + } + $foreign = $GLOBALS['dbi']->fetchResult( + $rel_query, 'master_field', null, $GLOBALS['controllink'] + ); + } + + if (($source == 'both' || $source == 'foreign') && strlen($table)) { + + $show_create_table_query = 'SHOW CREATE TABLE ' + . PMA_Util::backquote($db) . '.' . PMA_Util::backquote($table); + $show_create_table = $GLOBALS['dbi']->fetchValue($show_create_table_query, 0, 1); + $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table)); + + foreach ($analyzed_sql[0]['foreign_keys'] as $one_key) { + // The analyzer may return more than one column name in the + // index list or the ref_index_list; if this happens, + // the current logic just discards the whole index; having + // more than one index field is currently unsupported (see FAQ 3.6) + if (count($one_key['index_list']) == 1) { + foreach ($one_key['index_list'] as $i => $field) { + // If a foreign key is defined in the 'internal' source (pmadb) + // and as a native foreign key, we won't get it twice + // if $source='both' because we use $field as key + + // The parser looks for a CONSTRAINT clause just before + // the FOREIGN KEY clause. It finds it (as output from + // SHOW CREATE TABLE) in MySQL 4.0.13, but not in older + // versions like 3.23.58. + // In those cases, the FOREIGN KEY parsing will put numbers + // like -1, 0, 1... instead of the constraint number. + + if (isset($one_key['constraint'])) { + $foreign[$field]['constraint'] = $one_key['constraint']; + } + + if (isset($one_key['ref_db_name'])) { + $foreign[$field]['foreign_db'] = $one_key['ref_db_name']; + } else { + $foreign[$field]['foreign_db'] = $db; + } + $foreign[$field]['foreign_table'] = $one_key['ref_table_name']; + $foreign[$field]['foreign_field'] = $one_key['ref_index_list'][$i]; + if (isset($one_key['on_delete'])) { + $foreign[$field]['on_delete'] = $one_key['on_delete']; + } + if (isset($one_key['on_update'])) { + $foreign[$field]['on_update'] = $one_key['on_update']; + } + } + } + } + } + + /** + * Emulating relations for some information_schema and data_dictionary tables + */ + $is_information_schema = strtolower($db) == 'information_schema'; + $is_data_dictionary = PMA_DRIZZLE && strtolower($db) == 'data_dictionary'; + if (($is_information_schema || $is_data_dictionary) + && ($source == 'internal' || $source == 'both') + ) { + if ($is_information_schema) { + $relations_key = 'information_schema_relations'; + include_once './libraries/information_schema_relations.lib.php'; + } else { + $relations_key = 'data_dictionary_relations'; + include_once './libraries/data_dictionary_relations.lib.php'; + } + if (isset($GLOBALS[$relations_key][$table])) { + foreach ($GLOBALS[$relations_key][$table] as $field => $relations) { + if ((! strlen($column) || $column == $field) + && (! isset($foreign[$field]) || ! strlen($foreign[$field])) + ) { + $foreign[$field] = $relations; + } + } + } + } + + return $foreign; +} // end of the 'PMA_getForeigners()' function + +/** + * 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 + */ +function PMA_getDisplayField($db, $table) +{ + $cfgRelation = PMA_getRelationsParam(); + + /** + * Try to fetch the display field from DB. + */ + if ($cfgRelation['displaywork']) { + $disp_query = ' + SELECT `display_field` + FROM ' . PMA_Util::backquote($cfgRelation['db']) + . '.' . PMA_Util::backquote($cfgRelation['table_info']) . ' + WHERE `db_name` = \'' . PMA_Util::sqlAddSlashes($db) . '\' + AND `table_name` = \'' . PMA_Util::sqlAddSlashes($table) . '\''; + + $row = $GLOBALS['dbi']->fetchSingleRow( + $disp_query, 'ASSOC', $GLOBALS['controllink'] + ); + 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'; + } + } + + /** + * No Luck... + */ + return false; + +} // end of the 'PMA_getDisplayField()' function + +/** + * 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 + */ +function PMA_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[] = PMA_getDbComment($db); + } + + return $comments; +} // end of the 'PMA_getComments()' function + +/** + * Gets the comment for a db + * + * @param string $db the name of the db to check for + * + * @return string comment + * + * @access public + */ +function PMA_getDbComment($db) +{ + $cfgRelation = PMA_getRelationsParam(); + $comment = ''; + + if ($cfgRelation['commwork']) { + // pmadb internal db comment + $com_qry = " + SELECT `comment` + FROM " . PMA_Util::backquote($cfgRelation['db']) + . "." . PMA_Util::backquote($cfgRelation['column_info']) . " + WHERE db_name = '" . PMA_Util::sqlAddSlashes($db) . "' + AND table_name = '' + AND column_name = '(db_comment)'"; + $com_rs = PMA_queryAsControlUser( + $com_qry, true, PMA_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; +} // end of the 'PMA_getDbComment()' function + +/** + * Gets the comment for a db + * + * @access public + * + * @return string comment + */ +function PMA_getDbComments() +{ + $cfgRelation = PMA_getRelationsParam(); + $comments = array(); + + if ($cfgRelation['commwork']) { + // pmadb internal db comment + $com_qry = " + SELECT `db_name`, `comment` + FROM " . PMA_Util::backquote($cfgRelation['db']) + . "." . PMA_Util::backquote($cfgRelation['column_info']) . " + WHERE `column_name` = '(db_comment)'"; + $com_rs = PMA_queryAsControlUser( + $com_qry, true, PMA_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; +} // end of the 'PMA_getDbComments()' function + +/** + * 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 + */ +function PMA_setDbComment($db, $comment = '') +{ + $cfgRelation = PMA_getRelationsParam(); + + if (! $cfgRelation['commwork']) { + return false; + } + + if (strlen($comment)) { + $upd_query = 'INSERT INTO ' + . PMA_Util::backquote($cfgRelation['db']) . '.' + . PMA_Util::backquote($cfgRelation['column_info']) + . ' (`db_name`, `table_name`, `column_name`, `comment`)' + . ' VALUES (\'' + . PMA_Util::sqlAddSlashes($db) + . "', '', '(db_comment)', '" + . PMA_Util::sqlAddSlashes($comment) + . "') " + . ' ON DUPLICATE KEY UPDATE ' + . "`comment` = '" . PMA_Util::sqlAddSlashes($comment) . "'"; + } else { + $upd_query = 'DELETE FROM ' + . PMA_Util::backquote($cfgRelation['db']) . '.' + . PMA_Util::backquote($cfgRelation['column_info']) + . ' WHERE `db_name` = \'' . PMA_Util::sqlAddSlashes($db) . '\' + AND `table_name` = \'\' + AND `column_name` = \'(db_comment)\''; + } + + if (isset($upd_query)) { + return PMA_queryAsControlUser($upd_query); + } + + return false; +} // end of 'PMA_setDbComment()' function + +/** + * 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 + */ +function PMA_setHistory($db, $table, $username, $sqlquery) +{ + // Prevent to run this automatically on Footer class destroying in testsuite + if (defined('TESTSUITE') + || strlen($sqlquery) > $GLOBALS['cfg']['MaxCharactersInDisplayedSQL'] + ) { + return; + } + + $cfgRelation = PMA_getRelationsParam(); + + if (! isset($_SESSION['sql_history'])) { + $_SESSION['sql_history'] = array(); + } + + $key = md5($sqlquery . $db . $table); + + if (isset($_SESSION['sql_history'][$key])) { + unset($_SESSION['sql_history'][$key]); + } + + $_SESSION['sql_history'][$key] = 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; + } + + PMA_queryAsControlUser( + 'INSERT INTO ' + . PMA_Util::backquote($cfgRelation['db']) . '.' + . PMA_Util::backquote($cfgRelation['history']) . ' + (`username`, + `db`, + `table`, + `timevalue`, + `sqlquery`) + VALUES + (\'' . PMA_Util::sqlAddSlashes($username) . '\', + \'' . PMA_Util::sqlAddSlashes($db) . '\', + \'' . PMA_Util::sqlAddSlashes($table) . '\', + NOW(), + \'' . PMA_Util::sqlAddSlashes($sqlquery) . '\')' + ); +} // end of 'PMA_setHistory()' function + +/** + * Gets a SQL history entry + * + * @param string $username the username + * + * @return array list of history items + * + * @access public + */ +function PMA_getHistory($username) +{ + $cfgRelation = PMA_getRelationsParam(); + + /** + * if db-based history is disabled but there exists a session-based + * history, use it + */ + if (! $GLOBALS['cfg']['QueryHistoryDB'] && isset($_SESSION['sql_history'])) { + return array_reverse($_SESSION['sql_history']); + } + + if (! $cfgRelation['historywork']) { + return false; + } + + $hist_query = ' + SELECT `db`, + `table`, + `sqlquery` + FROM ' . PMA_Util::backquote($cfgRelation['db']) + . '.' . PMA_Util::backquote($cfgRelation['history']) . ' + WHERE `username` = \'' . PMA_Util::sqlAddSlashes($username) . '\' + ORDER BY `id` DESC'; + + return $GLOBALS['dbi']->fetchResult($hist_query, null, null, $GLOBALS['controllink']); +} // end of 'PMA_getHistory()' function + +/** + * purges SQL history + * + * deletes entries that exceeds $cfg['QueryHistoryMax'], oldest first, for the + * given user + * + * @param string $username the username + * + * @return void + * + * @access public + */ +function PMA_purgeHistory($username) +{ + $cfgRelation = PMA_getRelationsParam(); + if (! $GLOBALS['cfg']['QueryHistoryDB'] || ! $cfgRelation['historywork']) { + return; + } + + if (! $cfgRelation['historywork']) { + return; + } + + $search_query = ' + SELECT `timevalue` + FROM ' . PMA_Util::backquote($cfgRelation['db']) + . '.' . PMA_Util::backquote($cfgRelation['history']) . ' + WHERE `username` = \'' . PMA_Util::sqlAddSlashes($username) . '\' + ORDER BY `timevalue` DESC + LIMIT ' . $GLOBALS['cfg']['QueryHistoryMax'] . ', 1'; + + if ($max_time = $GLOBALS['dbi']->fetchValue($search_query, 0, 0, $GLOBALS['controllink'])) { + PMA_queryAsControlUser( + 'DELETE FROM ' + . PMA_Util::backquote($cfgRelation['db']) . '.' + . PMA_Util::backquote($cfgRelation['history']) . ' + WHERE `username` = \'' . PMA_Util::sqlAddSlashes($username) . '\' + AND `timevalue` <= \'' . $max_time . '\'' + ); + } +} // end of 'PMA_purgeHistory()' function + +/** + * 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; +} // end of 'PMA_buildForeignDropdown' function + +/** + * 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; +} // end of 'PMA_foreignDropdown()' function + +/** + * Gets foreign keys in preparation for a drop-down selector + * + * @param array $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 + * + * @return array data about the foreign keys + * + * @access public + */ + +function PMA_getForeignData( + $foreigners, $field, $override_total, $foreign_filter, $foreign_limit +) { + // 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; + if ($foreigners && isset($foreigners[$field])) { + $foreigner = $foreigners[$field]; + $foreign_db = $foreigner['foreign_db']; + $foreign_table = $foreigner['foreign_table']; + $foreign_field = $foreigner['foreign_field']; + + // 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. + + $the_total = PMA_Table::countRecords($foreign_db, $foreign_table, true); + + if ($override_total == true + || $the_total < $GLOBALS['cfg']['ForeignKeyMaxLimit'] + ) { + // foreign_display can be false if no display field defined: + $foreign_display = PMA_getDisplayField($foreign_db, $foreign_table); + + $f_query_main = 'SELECT ' . PMA_Util::backquote($foreign_field) + . (($foreign_display == false) ? '' : ', ' . PMA_Util::backquote($foreign_display)); + $f_query_from = ' FROM ' . PMA_Util::backquote($foreign_db) + . '.' . PMA_Util::backquote($foreign_table); + $f_query_filter = empty($foreign_filter) ? '' : ' WHERE ' + . PMA_Util::backquote($foreign_field) + . ' LIKE "%' . PMA_Util::sqlAddSlashes($foreign_filter, true) . '%"' + . (($foreign_display == false) ? '' : ' OR ' . PMA_Util::backquote($foreign_display) + . ' LIKE "%' . PMA_Util::sqlAddSlashes($foreign_filter, true) . '%"' + ); + $f_query_order = ($foreign_display == false) ? '' :' ORDER BY ' + . PMA_Util::backquote($foreign_table) . '.' + . PMA_Util::backquote($foreign_display); + $f_query_limit = isset($foreign_limit) ? $foreign_limit : ''; + + if (!empty($foreign_filter)) { + $res = $GLOBALS['dbi']->query( + 'SELECT COUNT(*)' . $f_query_from . $f_query_filter + ); + if ($res) { + $the_total = $GLOBALS['dbi']->fetchValue($res); + @$GLOBALS['dbi']->freeResult($res); + } else { + $the_total = 0; + } + } + + $disp = $GLOBALS['dbi']->query( + $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 { + $disp_row = null; + $foreign_link = true; + } + } // end if $foreigners + + $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; +} // end of 'PMA_getForeignData()' function + +/** + * Finds all related tables + * + * @param array $all_tables All the involved tables + * @param string $master The master table to form the LEFT JOIN clause + * + * @return string LEFT JOIN + * @access private + */ +function PMA_getRelatives($all_tables, $master) +{ + $fromclause = ''; + $emerg = ''; + + // The list of tables that we still couldn't connect + $remaining_tables = $all_tables; + unset($remaining_tables[$master]); + // The list of allready connected tables + $known_tables[$master] = $master; + $run = 0; + while (count($remaining_tables) > 0) { + // Whether to go from master to foreign or vice versa + if ($run % 2 == 0) { + $from = 'master'; + $to = 'foreign'; + } else { + $from = 'foreign'; + $to = 'master'; + } + $in_know = '(\'' . implode('\', \'', $known_tables) . '\')'; + $in_left = '(\'' . implode('\', \'', $remaining_tables) . '\')'; + $rel_query = 'SELECT *' + . ' FROM ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . PMA_Util::backquote($GLOBALS['cfgRelation']['relation']) + . ' WHERE ' . $from . '_db = \'' . PMA_Util::sqlAddSlashes($GLOBALS['db']) . '\'' + . ' AND ' . $to . '_db = \'' . PMA_Util::sqlAddSlashes($GLOBALS['db']) . '\'' + . ' AND ' . $from . '_table IN ' . $in_know + . ' AND ' . $to . '_table IN ' . $in_left; + $relations = @$GLOBALS['dbi']->query($rel_query, $GLOBALS['controllink']); + while ($row = $GLOBALS['dbi']->fetchAssoc($relations)) { + $found_table = $row[$to . '_table']; + if (isset($remaining_tables[$found_table])) { + $fromclause + .= "\n" . ' LEFT JOIN ' + . PMA_Util::backquote($GLOBALS['db']) . '.' . PMA_Util::backquote($row[$to . '_table']) . ' ON ' + . PMA_Util::backquote($row[$from . '_table']) . '.' + . PMA_Util::backquote($row[$from . '_field']) . ' = ' + . PMA_Util::backquote($row[$to . '_table']) . '.' + . PMA_Util::backquote($row[$to . '_field']) . ' '; + $known_tables[$found_table] = $found_table; + unset($remaining_tables[$found_table]); + } + } // end while + $run++; + if ($run > 5) { + foreach ($remaining_tables as $table) { + $emerg .= ', ' . PMA_Util::backquote($table); + unset($remaining_tables[$table]); + } + } + } // end while + $fromclause = $emerg . $fromclause; + return $fromclause; +} // end of the "PMA_getRelatives()" function + +/** + * 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 + */ +function PMA_REL_renameField($db, $table, $field, $new_name) +{ + $cfgRelation = PMA_getRelationsParam(); + + if ($cfgRelation['displaywork']) { + $table_query = 'UPDATE ' + . PMA_Util::backquote($cfgRelation['db']) . '.' + . PMA_Util::backquote($cfgRelation['table_info']) + . ' SET display_field = \'' . PMA_Util::sqlAddSlashes($new_name) . '\'' + . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\'' + . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($table) . '\'' + . ' AND display_field = \'' . PMA_Util::sqlAddSlashes($field) . '\''; + PMA_queryAsControlUser($table_query); + } + + if ($cfgRelation['relwork']) { + $table_query = 'UPDATE ' + . PMA_Util::backquote($cfgRelation['db']) . '.' + . PMA_Util::backquote($cfgRelation['relation']) + . ' SET master_field = \'' . PMA_Util::sqlAddSlashes($new_name) . '\'' + . ' WHERE master_db = \'' . PMA_Util::sqlAddSlashes($db) . '\'' + . ' AND master_table = \'' . PMA_Util::sqlAddSlashes($table) . '\'' + . ' AND master_field = \'' . PMA_Util::sqlAddSlashes($field) . '\''; + PMA_queryAsControlUser($table_query); + + $table_query = 'UPDATE ' + . PMA_Util::backquote($cfgRelation['db']) . '.' + . PMA_Util::backquote($cfgRelation['relation']) + . ' SET foreign_field = \'' . PMA_Util::sqlAddSlashes($new_name) . '\'' + . ' WHERE foreign_db = \'' . PMA_Util::sqlAddSlashes($db) . '\'' + . ' AND foreign_table = \'' . PMA_Util::sqlAddSlashes($table) . '\'' + . ' AND foreign_field = \'' . PMA_Util::sqlAddSlashes($field) . '\''; + PMA_queryAsControlUser($table_query); + + } // end if relwork +} + + +/** + * 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 + */ +function PMA_REL_renameSingleTable($table, + $source_db, $target_db, + $source_table, $target_table, + $db_field, $table_field +) { + $query = 'UPDATE ' + . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . PMA_Util::backquote($GLOBALS['cfgRelation'][$table]) + . ' SET ' + . $db_field . ' = \'' . PMA_Util::sqlAddSlashes($target_db) . '\', ' + . $table_field . ' = \'' . PMA_Util::sqlAddSlashes($target_table) . '\'' + . ' WHERE ' + . $db_field . ' = \'' . PMA_Util::sqlAddSlashes($source_db) . '\'' + . ' AND ' + . $table_field . ' = \'' . PMA_Util::sqlAddSlashes($source_table) . '\''; + PMA_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 + */ +function PMA_REL_renameTable($source_db, $target_db, $source_table, $target_table) +{ + // Move old entries from PMA-DBs to new table + if ($GLOBALS['cfgRelation']['commwork']) { + PMA_REL_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']) { + PMA_REL_renameSingleTable( + 'table_info', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + } + + if ($GLOBALS['cfgRelation']['relwork']) { + PMA_REL_renameSingleTable( + 'relation', + $source_db, $target_db, + $source_table, $target_table, + 'foreign_db', 'foreign_table' + ); + + PMA_REL_renameSingleTable( + 'relation', + $source_db, $target_db, + $source_table, $target_table, + 'master_db', 'master_table' + ); + } + + /** + * @todo Can't get moving 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? + */ + + if ($GLOBALS['cfgRelation']['pdfwork']) { + PMA_REL_renameSingleTable( + 'table_coords', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + } + + if ($GLOBALS['cfgRelation']['designerwork']) { + PMA_REL_renameSingleTable( + 'designer_coords', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + } +} + +/** + * Create a PDF page + * + * @param string $newpage name of the new PDF page + * @param array $cfgRelation Relation configuration + * @param string $db database name + * + * @return string $pdf_page_number + */ +function PMA_REL_createPage($newpage, $cfgRelation, $db) +{ + if (! isset($newpage) || $newpage == '') { + $newpage = __('no description'); + } + $ins_query = 'INSERT INTO ' + . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . PMA_Util::backquote($cfgRelation['pdf_pages']) + . ' (db_name, page_descr)' + . ' VALUES (\'' + . PMA_Util::sqlAddSlashes($db) . '\', \'' + . PMA_Util::sqlAddSlashes($newpage) . '\')'; + PMA_queryAsControlUser($ins_query, false); + + return $GLOBALS['dbi']->insertId( + isset($GLOBALS['controllink']) ? $GLOBALS['controllink'] : '' + ); +} +?> diff --git a/sources/libraries/relation_cleanup.lib.php b/sources/libraries/relation_cleanup.lib.php new file mode 100644 index 0000000..52d8812 --- /dev/null +++ b/sources/libraries/relation_cleanup.lib.php @@ -0,0 +1,183 @@ + diff --git a/sources/libraries/replication.inc.php b/sources/libraries/replication.inc.php new file mode 100644 index 0000000..0a35978 --- /dev/null +++ b/sources/libraries/replication.inc.php @@ -0,0 +1,286 @@ +fetchResult('SHOW MASTER STATUS'); + +/** + * get slave replication from server + */ +$server_slave_replication = $GLOBALS['dbi']->fetchResult('SHOW SLAVE STATUS'); + +/** + * replication types + */ +$replication_types = array('master', 'slave'); + + +/** + * define variables for master status + */ +$master_variables = array( + 'File', + 'Position', + 'Binlog_Do_DB', + 'Binlog_Ignore_DB', +); + +/** + * Define variables for slave status + */ +$slave_variables = array( + 'Slave_IO_State', + 'Master_Host', + 'Master_User', + 'Master_Port', + 'Connect_Retry', + 'Master_Log_File', + 'Read_Master_Log_Pos', + 'Relay_Log_File', + 'Relay_Log_Pos', + 'Relay_Master_Log_File', + 'Slave_IO_Running', + 'Slave_SQL_Running', + 'Replicate_Do_DB', + 'Replicate_Ignore_DB', + 'Replicate_Do_Table', + 'Replicate_Ignore_Table', + 'Replicate_Wild_Do_Table', + 'Replicate_Wild_Ignore_Table', + 'Last_Errno', + 'Last_Error', + 'Skip_Counter', + 'Exec_Master_Log_Pos', + 'Relay_Log_Space', + 'Until_Condition', + 'Until_Log_File', + 'Until_Log_Pos', + 'Master_SSL_Allowed', + 'Master_SSL_CA_File', + 'Master_SSL_CA_Path', + 'Master_SSL_Cert', + 'Master_SSL_Cipher', + 'Master_SSL_Key', + 'Seconds_Behind_Master', +); +/** + * define important variables, which need to be watched for + * correct running of replication in slave mode + * + * @usedby PMA_getHtmlForReplicationStatusTable() + */ +// TODO change to regexp or something, to allow for negative match. +// To e.g. highlight 'Last_Error' +// +$slave_variables_alerts = array( + 'Slave_IO_Running' => 'No', + 'Slave_SQL_Running' => 'No', +); +$slave_variables_oks = array( + 'Slave_IO_Running' => 'Yes', + 'Slave_SQL_Running' => 'Yes', +); + +// check which replication is available and +// set $server_{master/slave}_status and assign values + +// replication info is more easily passed to functions +/* + * @todo use $replication_info everywhere instead of the generated variable names + */ +$replication_info = array(); + +foreach ($replication_types as $type) { + if (count(${"server_{$type}_replication"}) > 0) { + ${"server_{$type}_status"} = true; + $replication_info[$type]['status'] = true; + } else { + ${"server_{$type}_status"} = false; + $replication_info[$type]['status'] = false; + } + if (${"server_{$type}_status"}) { + if ($type == "master") { + ${"server_{$type}_Do_DB"} = explode( + ",", $server_master_replication[0]["Binlog_Do_DB"] + ); + $replication_info[$type]['Do_DB'] = ${"server_{$type}_Do_DB"}; + + ${"server_{$type}_Ignore_DB"} = explode( + ",", $server_master_replication[0]["Binlog_Ignore_DB"] + ); + $replication_info[$type]['Ignore_DB'] = ${"server_{$type}_Ignore_DB"}; + } elseif ($type == "slave") { + ${"server_{$type}_Do_DB"} = explode( + ",", $server_slave_replication[0]["Replicate_Do_DB"] + ); + $replication_info[$type]['Do_DB'] = ${"server_{$type}_Do_DB"}; + + ${"server_{$type}_Ignore_DB"} = explode( + ",", $server_slave_replication[0]["Replicate_Ignore_DB"] + ); + $replication_info[$type]['Ignore_DB'] = ${"server_{$type}_Ignore_DB"}; + + ${"server_{$type}_Do_Table"} = explode( + ",", $server_slave_replication[0]["Replicate_Do_Table"] + ); + $replication_info[$type]['Do_Table'] = ${"server_{$type}_Do_Table"}; + + ${"server_{$type}_Ignore_Table"} = explode( + ",", $server_slave_replication[0]["Replicate_Ignore_Table"] + ); + $replication_info[$type]['Ignore_Table'] + = ${"server_{$type}_Ignore_Table"}; + + ${"server_{$type}_Wild_Do_Table"} = explode( + ",", $server_slave_replication[0]["Replicate_Wild_Do_Table"] + ); + $replication_info[$type]['Wild_Do_Table'] + = ${"server_{$type}_Wild_Do_Table"}; + + ${"server_{$type}_Wild_Ignore_Table"} = explode( + ",", $server_slave_replication[0]["Replicate_Wild_Ignore_Table"] + ); + $replication_info[$type]['Wild_Ignore_Table'] + = ${"server_{$type}_Wild_Ignore_Table"}; + } + } +} + +/** + * Extracts database or table name from string + * + * @param string $string contains "dbname.tablename" + * @param string $what what to extract (db|table) + * + * @return $string the extracted part + */ +function PMA_extractDbOrTable($string, $what = 'db') +{ + $list = explode(".", $string); + if ('db' == $what) { + return $list[0]; + } else { + return $list[1]; + } +} + +/** + * Configures replication slave + * + * @param string $action possible values: START or STOP + * @param string $control default: null, + * possible values: SQL_THREAD or IO_THREAD or null. + * If it is set to null, it controls both + * SQL_THREAD and IO_THREAD + * @param mixed $link mysql link + * + * @return mixed output of DatabaseInterface::tryQuery + */ +function PMA_Replication_Slave_control($action, $control = null, $link = null) +{ + $action = strtoupper($action); + $control = strtoupper($control); + + if ($action != "START" && $action != "STOP") { + return -1; + } + if ($control != "SQL_THREAD" && $control != "IO_THREAD" && $control != null) { + return -1; + } + + return $GLOBALS['dbi']->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 + */ +function PMA_Replication_Slave_changeMaster($user, $password, $host, $port, + $pos, $stop = true, $start = true, $link = null +) { + if ($stop) { + PMA_Replication_Slave_control("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) { + PMA_Replication_Slave_control("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 + */ +function PMA_Replication_connectToMaster( + $user, $password, $host = null, $port = null, $socket = null +) { + $server = array(); + $server["host"] = $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($user, $password, false, $server, true); +} +/** + * 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 PMA_Replication_Slave_changeMaster + */ +function PMA_Replication_Slave_binLogMaster($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/sources/libraries/replication_gui.lib.php b/sources/libraries/replication_gui.lib.php new file mode 100644 index 0000000..7c5af7b --- /dev/null +++ b/sources/libraries/replication_gui.lib.php @@ -0,0 +1,1071 @@ +getDisplay(); + $_SESSION['replication']['sr_action_status'] = 'unknown'; + } elseif ($_SESSION['replication']['sr_action_status'] == 'success') { + $success_message = $_SESSION['replication']['sr_action_info']; + $html .= PMA_Message::success($success_message)->getDisplay(); + $_SESSION['replication']['sr_action_status'] = 'unknown'; + } + } + return $html; +} + +/** + * returns HTML for master replication + * + * @return String HTML code + */ +function PMA_getHtmlForMasterReplication() +{ + $html = ''; + if (! isset($_REQUEST['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 .= PMA_getHtmlForReplicationStatusTable('master', true, false); + $html .= '
      • '; + + $html .= '
      • '; + $html .= __('Show connected slaves') . ''; + $html .= PMA_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($_REQUEST['mr_adduser'])) { + $html .= PMA_getHtmlForReplicationMasterAddSlaveuser(); + } elseif (! isset($_REQUEST['repl_clear_scr'])) { + $html .= "
      "; + $html .= "
      "; + } + + return $html; +} + +/** + * returns HTML for master replication configuration + * + * @return String HTML code + */ +function PMA_getHtmlForMasterConfiguration() +{ + $html = '
      '; + $html .= '' . __('Master configuration') . ''; + $html .= __( + 'This server is not configured as master server in a ' + . 'replication process. You can choose from either replicating ' + . 'all databases and ignoring certain (useful if you want to replicate ' + . 'majority of 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 .= PMA_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 .= PMA_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 + */ +function PMA_getHtmlForSlaveConfiguration( + $server_slave_status, $server_slave_replication +) { + $html = '
      '; + $html .= '' . __('Slave replication') . ''; + 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 = 'server_replication.php' + . PMA_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 = 'server_replication.php' + . PMA_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 = 'server_replication.php' + . PMA_URL_getCommon($_url_params); + + $_url_params['sr_slave_action'] = 'reset'; + $slave_control_reset_link = 'server_replication.php' + . PMA_URL_getCommon($_url_params); + + $_url_params = $GLOBALS['url_params']; + $_url_params['sr_slave_skip_error'] = true; + $slave_skip_error_link = 'server_replication.php' + . PMA_URL_getCommon($_url_params); + + if ($server_slave_replication[0]['Slave_SQL_Running'] == 'No') { + $html .= PMA_Message::error( + __('Slave SQL Thread not running!') + )->getDisplay(); + } + if ($server_slave_replication[0]['Slave_IO_Running'] == 'No') { + $html .= PMA_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 = 'server_replication.php' + . PMA_URL_getCommon($_url_params); + + $html .= __('Server is configured as slave in a replication process. Would you like to:'); + $html .= '
      '; + $html .= ''; + $html .= '
      '; + + } elseif (! isset($_REQUEST['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 configure it?' + ), + 'server_replication.php' . PMA_URL_getCommon($_url_params) + ); + } + $html .= '
      '; + + return $html; +} + +/** + * returns HTML for Slave Error Management + * + * @param String $slave_skip_error_link error link + * + * @return String HTML code + */ +function PMA_getHtmlForSlaveErrorManagement($slave_skip_error_link) +{ + $html = ''; + $html .= __('Error management:') . ''; + $html .= ' '; + return $html; +} + +/** + * returns HTML for not configure for a server replication + * + * @return String HTML code + */ +function PMA_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 configure it?' + ), + 'server_replication.php' . PMA_URL_getCommon($_url_params) + ); + $html .= '
      '; + return $html; +} + +/** + * returns HTML code for selecting databases + * + * @return String HTML code + */ +function PMA_getHtmlForReplicationDbMultibox() +{ + $multi_values = ''; + $multi_values .= ''; + $multi_values .= '
      '; + $multi_values .= __('Uncheck All') . ''; + + return $multi_values; +} + +/** + * returns HTML for changing master + * + * @param String $submitname - submit button name + * + * @return String HTML code + */ + +function PMA_getHtmlForReplicationChangeMaster($submitname) +{ + $html = ''; + list($username_length, $hostname_length) + = PMA_replicationGetUsernameHostnameLength(); + + $html .= '
      '; + $html .= PMA_URL_getHiddenInputs('', ''); + $html .= '
      '; + $html .= ' ' . __('Slave configuration'); + $html .= ' - ' . __('Change or reconfigure master server') . ''; + $html .= __( + 'Make sure, you have 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 .= PMA_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 .= PMA_getHtmlForAddUserInputDiv( + array('text'=>__('Password:'), 'for'=>"text_pma_pw"), + array( + 'type'=>'password', + 'name'=>'pma_pw', + 'id'=>'text_pma_pw', + 'title'=>__('Password'), + 'required'=>'required' + ) + ); + + $html .= PMA_getHtmlForAddUserInputDiv( + array('text'=>__('Host:'), 'for'=>"text_hostname"), + array( + 'type'=>'text', + 'name'=>'hostname', + 'id'=>'text_hostname', + 'maxlength'=>$hostname_length, + 'value'=>'', + 'required'=>'required' + ) + ); + + $html .= PMA_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 + */ +function PMA_getHtmlForAddUserInputDiv($label_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 + */ +function PMA_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 .= ' '; + + $odd_row = true; + foreach (${"{$type}_variables"} as $variable) { + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + + $odd_row = ! $odd_row; + } + + $html .= ' '; + $html .= '
      ' . __('Variable') . '' . __('Value') . '
      '; + $html .= $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 .= str_replace( + ',', + ', ', + ${"server_{$type}_replication"}[0][$variable] + ); + } else { + $html .= ${"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 + */ +function PMA_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 .= ' '; + + $odd_row = true; + foreach ($data as $slave) { + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + + $odd_row = ! $odd_row; + } + + $html .= ' '; + $html .= '
      ' . __('Server ID') . '' . __('Host') . '
      ' . $slave['Server_id'] . '' . $slave['Host'] . '
      '; + $html .= '
      '; + $html .= PMA_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 + */ + +function PMA_replicationGetUsernameHostnameLength() +{ + $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 + */ +function PMA_getHtmlForReplicationMasterAddSlaveuser() +{ + $html = ''; + list($username_length, $hostname_length) + = PMA_replicationGetUsernameHostnameLength(); + + if (isset($_REQUEST['username']) && strlen($_REQUEST['username']) === 0) { + $GLOBALS['pred_username'] = 'any'; + } + $html .= '
      '; + $html .= '
      ' . __('Add slave replication user') . '' + . PMA_getHtmlForAddUserLoginForm($username_length) + . '
      ' + . '' + . '' + . ' ' + . '' + . '' + . ' ' + . '' + . '
      ' + . '' + . '' + . ' ' + . '' + . '' + . '
      '; + + return $html; +} + +/** + * returns HTML for TableInfoForm + * + * @param int $hostname_length Selected hostname length + * + * @return String HTML code + */ +function PMA_getHtmlForTableInfoForm($hostname_length) +{ + $html = ' ' + . ' ' + . ' ' + . '
      ' + . '' + . PMA_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 + */ +function PMA_handleControlRequest() +{ + if (isset($_REQUEST['sr_take_action'])) { + $refresh = false; + $result = null; + $messageSuccess = null; + $messageError = null; + + if (isset($_REQUEST['slave_changemaster'])) { + $result = PMA_handleRequestForSlaveChangeMaster(); + } elseif (isset($_REQUEST['sr_slave_server_control'])) { + $result = PMA_handleRequestForSlaveServerControl(); + $refresh = true; + + switch ($_REQUEST['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($_REQUEST['sr_slave_skip_error'])) { + $result = PMA_handleRequestForSlaveSkipError(); + } + + if ($refresh) { + $response = PMA_Response::getInstance(); + if ($response->isAjax()) { + $response->isSuccess($result); + $response->addJSON( + 'message', + $result ? PMA_Message::success($messageSuccess) : PMA_Message::error($messageError) + ); + } else { + PMA_sendHeaderLocation( + $GLOBALS['cfg']['PmaAbsoluteUri'] . 'server_replication.php' + . PMA_URL_getCommon($GLOBALS['url_params'], '&') + ); + } + } + unset($refresh); + } +} +/** + * handle control requests for Slave Change Master + * + * @return boolean + */ +function PMA_handleRequestForSlaveChangeMaster() +{ + $_SESSION['replication']['m_username'] = $sr['username'] + = PMA_Util::sqlAddSlashes($_REQUEST['username']); + $_SESSION['replication']['m_password'] = $sr['pma_pw'] + = PMA_Util::sqlAddSlashes($_REQUEST['pma_pw']); + $_SESSION['replication']['m_hostname'] = $sr['hostname'] + = PMA_Util::sqlAddSlashes($_REQUEST['hostname']); + $_SESSION['replication']['m_port'] = $sr['port'] + = PMA_Util::sqlAddSlashes($_REQUEST['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 = PMA_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 = PMA_Replication_Slave_binLogMaster($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 (! PMA_Replication_Slave_changeMaster( + $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 + */ +function PMA_handleRequestForSlaveServerControl() +{ + if (empty($_REQUEST['sr_slave_control_parm'])) { + $_REQUEST['sr_slave_control_parm'] = null; + } + if ($_REQUEST['sr_slave_action'] == 'reset') { + $qStop = PMA_Replication_Slave_control("STOP"); + $qReset = $GLOBALS['dbi']->tryQuery("RESET SLAVE;"); + $qStart = PMA_Replication_Slave_control("START"); + + $result = ($qStop !== false && $qStop !== -1 && + $qReset !== false && $qReset !== -1 && + $qStart !== false && $qStart !== -1); + } else { + $qControl = PMA_Replication_Slave_control( + $_REQUEST['sr_slave_action'], + $_REQUEST['sr_slave_control_parm'] + ); + + $result = ($qControl !== false && $qControl !== -1); + } + + return $result; +} + +/** + * handle control requests for Slave Skip Error + * + * @return boolean + */ +function PMA_handleRequestForSlaveSkipError() +{ + $count = 1; + if (isset($_REQUEST['sr_skip_errors_count'])) { + $count = $_REQUEST['sr_skip_errors_count'] * 1; + } + + $qStop = PMA_Replication_Slave_control("STOP"); + $qSkip = $GLOBALS['dbi']->tryQuery("SET GLOBAL SQL_SLAVE_SKIP_COUNTER = ".$count.";"); + $qStart = PMA_Replication_Slave_control("START"); + + $result = ($qStop !== false && $qStop !== -1 && + $qSkip !== false && $qSkip !== -1 && + $qStart !== false && $qStart !== -1); + + return $result; +} +?> diff --git a/sources/libraries/rte/rte_events.lib.php b/sources/libraries/rte/rte_events.lib.php new file mode 100644 index 0000000..7706597 --- /dev/null +++ b/sources/libraries/rte/rte_events.lib.php @@ -0,0 +1,637 @@ + 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 + */ +function PMA_EVN_main() +{ + global $db; + + PMA_EVN_setGlobals(); + /** + * Process all requests + */ + PMA_EVN_handleEditor(); + PMA_EVN_handleExport(); + /** + * Display a list of available events + */ + $columns = "`EVENT_NAME`, `EVENT_TYPE`, `STATUS`"; + $where = "EVENT_SCHEMA='" . PMA_Util::sqlAddSlashes($db) . "'"; + $query = "SELECT $columns FROM `INFORMATION_SCHEMA`.`EVENTS` " + . "WHERE $where ORDER BY `EVENT_NAME` ASC;"; + $items = $GLOBALS['dbi']->fetchResult($query); + echo PMA_RTE_getList('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 PMA_EVN_getFooterLinks(); +} // end PMA_EVN_main() + +/** + * Handles editor requests for adding or editing an item + * + * @return void + */ +function PMA_EVN_handleEditor() +{ + global $_REQUEST, $_POST, $errors, $db; + + if (! empty($_REQUEST['editor_process_add']) + || ! empty($_REQUEST['editor_process_edit']) + ) { + $sql_query = ''; + + $item_query = PMA_EVN_getQueryFromRequest(); + + if (! count($errors)) { // set by PMA_RTN_getQueryFromRequest() + // Execute the created query + if (! empty($_REQUEST['editor_process_edit'])) { + // Backup the old trigger, in case something goes wrong + $create_item = $GLOBALS['dbi']->getDefinition( + $db, + 'EVENT', + $_REQUEST['item_original_name'] + ); + $drop_item = "DROP EVENT " + . PMA_Util::backquote($_REQUEST['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(null); + } 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(null); + // 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); + if (! $result) { + // OMG, this is really bad! We dropped the query, + // failed to create a new one + // and now even the backup query does not execute! + // This should not happen, but we better handle + // this just in case. + $errors[] = __( + 'Sorry, we failed to restore the dropped event.' + ) + . '
      ' + . __('The backed up query was:') + . "\"" . htmlspecialchars($create_item) . "\"" + . '
      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(null); + } + } else { + $message = PMA_Message::success( + __('Event %1$s has been modified.') + ); + $message->addParam( + PMA_Util::backquote($_REQUEST['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(null); + } else { + $message = PMA_Message::success( + __('Event %1$s has been created.') + ); + $message->addParam( + PMA_Util::backquote($_REQUEST['item_name']) + ); + $sql_query = $item_query; + } + } + } + + if (count($errors)) { + $message = PMA_Message::error(__('One or more errors have occurred while processing your request:')); + $message->addString('
        '); + foreach ($errors as $string) { + $message->addString('
      • ' . $string . '
      • '); + } + $message->addString('
      '); + } + + $output = PMA_Util::getMessage($message, $sql_query); + if ($GLOBALS['is_ajax_request']) { + $response = PMA_Response::getInstance(); + if ($message->isSuccess()) { + $columns = "`EVENT_NAME`, `EVENT_TYPE`, `STATUS`"; + $where = "EVENT_SCHEMA='" . PMA_Util::sqlAddSlashes($db) . "' " + . "AND EVENT_NAME='" + . PMA_Util::sqlAddSlashes($_REQUEST['item_name']) . "'"; + $query = "SELECT " . $columns + . " FROM `INFORMATION_SCHEMA`.`EVENTS` WHERE " . $where. ";"; + $event = $GLOBALS['dbi']->fetchSingleRow($query); + $response->addJSON( + 'name', + htmlspecialchars(strtoupper($_REQUEST['item_name'])) + ); + $response->addJSON('new_row', PMA_EVN_getRowForList($event)); + $response->addJSON('insert', ! empty($event)); + $response->addJSON('message', $output); + } else { + $response->isSuccess(false); + $response->addJSON('message', $message); + } + exit; + } + } + /** + * Display a form used to add/edit a trigger, if necessary + */ + if (count($errors) + || (empty($_REQUEST['editor_process_add']) + && empty($_REQUEST['editor_process_edit']) + && (! empty($_REQUEST['add_item']) + || ! empty($_REQUEST['edit_item']) + || ! empty($_REQUEST['item_changetype']))) + ) { // FIXME: this must be simpler than that + $operation = ''; + if (! empty($_REQUEST['item_changetype'])) { + $operation = 'change'; + } + // Get the data for the form (if any) + if (! empty($_REQUEST['add_item'])) { + $title = PMA_RTE_getWord('add'); + $item = PMA_EVN_getDataFromRequest(); + $mode = 'add'; + } else if (! empty($_REQUEST['edit_item'])) { + $title = __("Edit event"); + if (! empty($_REQUEST['item_name']) + && empty($_REQUEST['editor_process_edit']) + && empty($_REQUEST['item_changetype']) + ) { + $item = PMA_EVN_getDataFromName($_REQUEST['item_name']); + if ($item !== false) { + $item['item_original_name'] = $item['item_name']; + } + } else { + $item = PMA_EVN_getDataFromRequest(); + } + $mode = 'edit'; + } + if ($item !== false) { + // Show form + $editor = PMA_EVN_getEditorForm($mode, $operation, $item); + if ($GLOBALS['is_ajax_request']) { + $response = PMA_Response::getInstance(); + $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( + PMA_RTE_getWord('not_found'), + htmlspecialchars(PMA_Util::backquote($_REQUEST['item_name'])), + htmlspecialchars(PMA_Util::backquote($db)) + ); + $message = PMA_message::error($message); + if ($GLOBALS['is_ajax_request']) { + $response = PMA_Response::getInstance(); + $response->isSuccess(false); + $response->addJSON('message', $message); + exit; + } else { + $message->display(); + } + } + } +} // end PMA_EVN_handleEditor() + +/** + * This function will generate the values that are required to for the editor + * + * @return array Data necessary to create the editor. + */ +function PMA_EVN_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($_REQUEST[$index]) ? $_REQUEST[$index] : ''; + } + $retval['item_type'] = 'ONE TIME'; + $retval['item_type_toggle'] = 'RECURRING'; + if (isset($_REQUEST['item_type']) && $_REQUEST['item_type'] == 'RECURRING') { + $retval['item_type'] = 'RECURRING'; + $retval['item_type_toggle'] = 'ONE TIME'; + } + return $retval; +} // end PMA_EVN_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. + */ +function PMA_EVN_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='" . PMA_Util::sqlAddSlashes($db) . "' " + . "AND EVENT_NAME='" . PMA_Util::sqlAddSlashes($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 PMA_EVN_getDataFromName() + +/** + * Displays a form used to add/edit an event + * + * @param string $mode If the editor will be used 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 + * PMA_EVN_getDataFromRequest() or + * PMA_EVN_getDataFromName() + * + * @return string HTML code for the editor. + */ +function PMA_EVN_getEditorForm($mode, $operation, $item) +{ + global $db, $table, $event_status, $event_type, $event_interval; + + // 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 .= PMA_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"; + + return $retval; +} // end PMA_RTN_getRowForList() + +/** + * Creates the contents for a row in the list of triggers + * + * @param array $trigger An array of routine data + * @param string $rowclass Empty or one of ['even'|'odd'] + * + * @return string HTML code of a cell for the list of triggers + */ +function PMA_TRI_getRowForList($trigger, $rowclass = '') +{ + global $ajax_class, $url_query, $db, $table, $titles; + + $retval = " \n"; + $retval .= " \n"; + if (empty($table)) { + $retval .= " \n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + + return $retval; +} // end PMA_TRI_getRowForList() + +/** + * Creates the contents for a row in the list of events + * + * @param array $event An array of routine data + * @param string $rowclass Empty or one of ['even'|'odd'] + * + * @return string HTML code of a cell for the list of events + */ +function PMA_EVN_getRowForList($event, $rowclass = '') +{ + global $ajax_class, $url_query, $db, $titles; + + $sql_drop = sprintf( + 'DROP EVENT IF EXISTS %s', + PMA_Util::backquote($event['EVENT_NAME']) + ); + + $retval = " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + + return $retval; +} // end PMA_EVN_getRowForList() + +?> diff --git a/sources/libraries/rte/rte_main.inc.php b/sources/libraries/rte/rte_main.inc.php new file mode 100644 index 0000000..9abb9a3 --- /dev/null +++ b/sources/libraries/rte/rte_main.inc.php @@ -0,0 +1,96 @@ +getTables($db))) { + include_once './libraries/tbl_common.inc.php'; + } else { + $table = ''; + include_once './libraries/db_common.inc.php'; + include_once './libraries/db_info.inc.php'; + } +} else { + /** + * Since we did not include some libraries, we need + * to manually select the required database and + * create the missing $url_query variable + */ + if (strlen($db)) { + $GLOBALS['dbi']->selectDb($db); + if (! isset($url_query)) { + $url_query = PMA_URL_getCommon($db, $table); + } + } +} + +/** + * Generate the conditional classes that will + * be used to attach jQuery events to links + */ +$ajax_class = array( + 'add' => '', + 'edit' => '', + 'exec' => '', + 'drop' => '', + 'export' => '' +); +$ajax_class = array( + 'add' => 'class="ajax add_anchor"', + 'edit' => 'class="ajax edit_anchor"', + 'exec' => 'class="ajax exec_anchor"', + 'drop' => 'class="ajax drop_anchor"', + 'export' => 'class="ajax export_anchor"' +); + +/** + * Create labels for the list + */ +$titles = PMA_Util::buildActionTitles(); + +/** + * Keep a list of errors that occurred while + * processing an 'Add' or 'Edit' operation. + */ +$errors = array(); + + +/** + * Call the appropriate main function + */ +switch ($_PMA_RTE) { +case 'RTN': + $type = null; + if (isset($_REQUEST['type'])) { + $type = $_REQUEST['type']; + } + PMA_RTN_main($type); + break; +case 'TRI': + PMA_TRI_main(); + break; +case 'EVN': + PMA_EVN_main(); + break; +} + +?> diff --git a/sources/libraries/rte/rte_routines.lib.php b/sources/libraries/rte/rte_routines.lib.php new file mode 100644 index 0000000..0f8f75a --- /dev/null +++ b/sources/libraries/rte/rte_routines.lib.php @@ -0,0 +1,1702 @@ +fetchResult( + "SELECT $columns FROM `INFORMATION_SCHEMA`.`ROUTINES` WHERE $where;" + ); + echo PMA_RTE_getList('routine', $items); + /** + * Display the form for adding a new routine, if the user has the privileges. + */ + echo PMA_RTN_getFooterLinks(); + /** + * Display a warning for users with PHP's old "mysql" extension. + */ + if ($GLOBALS['cfg']['Server']['extension'] === 'mysql') { + 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 PMA_RTN_main() + +/** + * This function parses a string containing one parameter of a routine, + * as returned by PMA_RTN_parseAllParameters() and returns an array containing + * the information about this parameter. + * + * @param string $value A string containing one parameter of a routine + * + * @return array Parsed information about the input parameter + */ +function PMA_RTN_parseOneParameter($value) +{ + global $param_directions; + + $retval = array(0 => '', + 1 => '', + 2 => '', + 3 => '', + 4 => ''); + $parsed_param = PMA_SQP_parse($value); + $pos = 0; + if (in_array(strtoupper($parsed_param[$pos]['data']), $param_directions)) { + $retval[0] = strtoupper($parsed_param[0]['data']); + $pos++; + } + if ($parsed_param[$pos]['type'] == 'alpha_identifier' + || $parsed_param[$pos]['type'] == 'quote_backtick' + ) { + $retval[1] = PMA_Util::unQuote( + $parsed_param[$pos]['data'] + ); + $pos++; + } + $depth = 0; + $param_length = ''; + $param_opts = array(); + for ($i=$pos; $i<$parsed_param['len']; $i++) { + if (($parsed_param[$i]['type'] == 'alpha_columnType' + || $parsed_param[$i]['type'] == 'alpha_functionName') && $depth == 0 + ) { + $retval[2] = strtoupper($parsed_param[$i]['data']); + } else if ($parsed_param[$i]['type'] == 'punct_bracket_open_round' + && $depth == 0 + ) { + $depth = 1; + } else if ($parsed_param[$i]['type'] == 'punct_bracket_close_round' + && $depth == 1 + ) { + $depth = 0; + } else if ($depth == 1) { + $param_length .= $parsed_param[$i]['data']; + } else if ($parsed_param[$i]['type'] == 'alpha_reservedWord' + && strtoupper($parsed_param[$i]['data']) == 'CHARSET' && $depth == 0 + ) { + if ($parsed_param[$i+1]['type'] == 'alpha_charset' + || $parsed_param[$i+1]['type'] == 'alpha_identifier' + ) { + $param_opts[] = strtolower($parsed_param[$i+1]['data']); + } + } else if ($parsed_param[$i]['type'] == 'alpha_columnAttrib' + && $depth == 0 + ) { + $param_opts[] = strtoupper($parsed_param[$i]['data']); + } + } + $retval[3] = $param_length; + sort($param_opts); + $retval[4] = implode(' ', $param_opts); + + return $retval; +} // end PMA_RTN_parseOneParameter() + +/** + * This function looks through the contents of a parsed + * SHOW CREATE [PROCEDURE | FUNCTION] query and extracts + * information about the routine's parameters. + * + * @param array $parsed_query Parsed query, returned by by PMA_SQP_parse() + * @param string $routine_type Routine type: 'PROCEDURE' or 'FUNCTION' + * + * @return array Information about the parameteres of a routine. + */ +function PMA_RTN_parseAllParameters($parsed_query, $routine_type) +{ + $retval = array(); + $retval['num'] = 0; + + // First get the list of parameters from the query + $buffer = ''; + $params = array(); + $fetching = false; + $depth = 0; + for ($i=0; $i<$parsed_query['len']; $i++) { + if ($parsed_query[$i]['type'] == 'alpha_reservedWord' + && $parsed_query[$i]['data'] == $routine_type + ) { + $fetching = true; + } else if ($fetching == true + && $parsed_query[$i]['type'] == 'punct_bracket_open_round' + ) { + $depth++; + if ($depth > 1) { + $buffer .= $parsed_query[$i]['data'] . ' '; + } + } else if ($fetching == true + && $parsed_query[$i]['type'] == 'punct_bracket_close_round' + ) { + $depth--; + if ($depth > 0) { + $buffer .= $parsed_query[$i]['data'] . ' '; + } else { + break; + } + } else if ($parsed_query[$i]['type'] == 'punct_listsep' && $depth == 1) { + $params[] = $buffer; + $retval['num']++; + $buffer = ''; + } else if ($fetching == true && $depth > 0) { + $buffer .= $parsed_query[$i]['data'] . ' '; + } + } + if (! empty($buffer)) { + $params[] = $buffer; + $retval['num']++; + } + // Now parse each parameter individually + foreach ($params as $key => $value) { + list($retval['dir'][], + $retval['name'][], + $retval['type'][], + $retval['length'][], + $retval['opts'][]) = PMA_RTN_parseOneParameter($value); + } + // Since some indices of $retval may be still undefined, we fill + // them each with an empty array to avoid E_ALL errors in PHP. + foreach (array('dir', 'name', 'type', 'length', 'opts') as $key => $index) { + if (! isset($retval[$index])) { + $retval[$index] = array(); + } + } + + return $retval; +} // end PMA_RTN_parseAllParameters() + +/** + * This function looks through the contents of a parsed + * SHOW CREATE [PROCEDURE | FUNCTION] query and extracts + * information about the routine's definer. + * + * @param array $parsed_query Parsed query, returned by PMA_SQP_parse() + * + * @return string The definer of a routine. + */ +function PMA_RTN_parseRoutineDefiner($parsed_query) +{ + $retval = ''; + $fetching = false; + for ($i=0; $i<$parsed_query['len']; $i++) { + if ($parsed_query[$i]['type'] == 'alpha_reservedWord' + && $parsed_query[$i]['data'] == 'DEFINER' + ) { + $fetching = true; + } else if ($fetching == true + && $parsed_query[$i]['type'] != 'quote_backtick' + && substr($parsed_query[$i]['type'], 0, 5) != 'punct' + ) { + break; + } else if ($fetching == true + && $parsed_query[$i]['type'] == 'quote_backtick' + ) { + $retval .= PMA_Util::unQuote( + $parsed_query[$i]['data'] + ); + } else if ($fetching == true && $parsed_query[$i]['type'] == 'punct_user') { + $retval .= $parsed_query[$i]['data']; + } + } + return $retval; +} // end PMA_RTN_parseRoutineDefiner() + +/** + * Handles editor requests for adding or editing an item + * + * @return void + */ +function PMA_RTN_handleEditor() +{ + global $_GET, $_POST, $_REQUEST, $GLOBALS, $db, $errors; + + if (! empty($_REQUEST['editor_process_add']) + || ! empty($_REQUEST['editor_process_edit']) + ) { + /** + * Handle a request to create/edit a routine + */ + $sql_query = ''; + $routine_query = PMA_RTN_getQueryFromRequest(); + if (! count($errors)) { // set by PMA_RTN_getQueryFromRequest() + // Execute the created query + if (! empty($_REQUEST['editor_process_edit'])) { + $isProcOrFunc = in_array( + $_REQUEST['item_original_type'], + array('PROCEDURE', 'FUNCTION') + ); + if (!$isProcOrFunc) { + $errors[] = sprintf( + __('Invalid routine type: "%s"'), + htmlspecialchars($_REQUEST['item_original_type']) + ); + } else { + // Backup the old routine, in case something goes wrong + $create_routine = $GLOBALS['dbi']->getDefinition( + $db, $_REQUEST['item_original_type'], + $_REQUEST['item_original_name'] + ); + $drop_routine = "DROP {$_REQUEST['item_original_type']} " + . PMA_Util::backquote($_REQUEST['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(null); + } else { + $result = $GLOBALS['dbi']->tryQuery($routine_query); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($routine_query) + ) + . '
      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(null); + // 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); + if (! $result) { + // OMG, this is really bad! We dropped the query, + // failed to create a new one + // and now even the backup query does not execute! + // This should not happen, but we better handle + // this just in case. + $errors[] = __( + 'Sorry, we failed to restore' + . ' the dropped routine.' + ) + . '
      ' + . __('The backed up query was:') + . "\"" . htmlspecialchars($create_routine) . "\"" + . '
      ' + . __('MySQL said: ') + . $GLOBALS['dbi']->getError(null); + } + } else { + $message = PMA_Message::success( + __('Routine %1$s has been modified.') + ); + $message->addParam( + PMA_Util::backquote($_REQUEST['item_name']) + ); + $sql_query = $drop_routine . $routine_query; + } + } + } + } 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(null); + } else { + $message = PMA_Message::success( + __('Routine %1$s has been created.') + ); + $message->addParam( + PMA_Util::backquote($_REQUEST['item_name']) + ); + $sql_query = $routine_query; + } + } + } + + if (count($errors)) { + $message = PMA_Message::error( + __( + 'One or more errors have occurred while' + . ' processing your request:' + ) + ); + $message->addString('
        '); + foreach ($errors as $string) { + $message->addString('
      • ' . $string . '
      • '); + } + $message->addString('
      '); + } + + $output = PMA_Util::getMessage($message, $sql_query); + if ($GLOBALS['is_ajax_request']) { + $response = PMA_Response::getInstance(); + if ($message->isSuccess()) { + $columns = "`SPECIFIC_NAME`, `ROUTINE_NAME`, `ROUTINE_TYPE`," + . " `DTD_IDENTIFIER`, `ROUTINE_DEFINITION`"; + $where = "ROUTINE_SCHEMA='" . PMA_Util::sqlAddSlashes($db) . "' " + . "AND ROUTINE_NAME='" + . PMA_Util::sqlAddSlashes($_REQUEST['item_name']) . "'" + . "AND ROUTINE_TYPE='" + . PMA_Util::sqlAddSlashes($_REQUEST['item_type']) . "'"; + $routine = $GLOBALS['dbi']->fetchSingleRow( + "SELECT $columns FROM `INFORMATION_SCHEMA`.`ROUTINES`" + . " WHERE $where;" + ); + $response->addJSON( + 'name', htmlspecialchars(strtoupper($_REQUEST['item_name'])) + ); + $response->addJSON('new_row', PMA_RTN_getRowForList($routine)); + $response->addJSON('insert', ! empty($routine)); + $response->addJSON('message', $output); + } else { + $response->isSuccess(false); + $response->addJSON('message', $output); + } + exit; + } + } + + /** + * Display a form used to add/edit a routine, if necessary + */ + // FIXME: this must be simpler than that + if (count($errors) + || ( empty($_REQUEST['editor_process_add']) + && empty($_REQUEST['editor_process_edit']) + && (! empty($_REQUEST['add_item']) || ! empty($_REQUEST['edit_item']) + || ! empty($_REQUEST['routine_addparameter']) + || ! empty($_REQUEST['routine_removeparameter']) + || ! empty($_REQUEST['routine_changetype']))) + ) { + // Handle requests to add/remove parameters and changing routine type + // This is necessary when JS is disabled + $operation = ''; + if (! empty($_REQUEST['routine_addparameter'])) { + $operation = 'add'; + } else if (! empty($_REQUEST['routine_removeparameter'])) { + $operation = 'remove'; + } else if (! empty($_REQUEST['routine_changetype'])) { + $operation = 'change'; + } + // Get the data for the form (if any) + if (! empty($_REQUEST['add_item'])) { + $title = PMA_RTE_getWord('add'); + $routine = PMA_RTN_getDataFromRequest(); + $mode = 'add'; + } else if (! empty($_REQUEST['edit_item'])) { + $title = __("Edit routine"); + if (! $operation && ! empty($_REQUEST['item_name']) + && empty($_REQUEST['editor_process_edit']) + ) { + $routine = PMA_RTN_getDataFromName( + $_REQUEST['item_name'], $_REQUEST['item_type'] + ); + if ($routine !== false) { + $routine['item_original_name'] = $routine['item_name']; + $routine['item_original_type'] = $routine['item_type']; + } + } else { + $routine = PMA_RTN_getDataFromRequest(); + } + $mode = 'edit'; + } + if ($routine !== false) { + // Show form + $editor = PMA_RTN_getEditorForm($mode, $operation, $routine); + if ($GLOBALS['is_ajax_request']) { + $response = PMA_Response::getInstance(); + $response->addJSON('message', $editor); + $response->addJSON('title', $title); + $response->addJSON('param_template', PMA_RTN_getParameterRow()); + $response->addJSON('type', $routine['item_type']); + } else { + echo "\n\n

      $title

      \n\n$editor"; + } + exit; + } else { + $message = __('Error in processing request:') . ' '; + $message .= sprintf( + PMA_RTE_getWord('not_found'), + htmlspecialchars(PMA_Util::backquote($_REQUEST['item_name'])), + htmlspecialchars(PMA_Util::backquote($db)) + ); + $message = PMA_message::error($message); + if ($GLOBALS['is_ajax_request']) { + $response->isSuccess(false); + $response->addJSON('message', $message); + exit; + } else { + $message->display(); + } + } + } +} // end PMA_RTN_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. + */ +function PMA_RTN_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 $key => $index) { + $retval[$index] = isset($_REQUEST[$index]) ? $_REQUEST[$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($_REQUEST['item_original_type']) + && $_REQUEST['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($_REQUEST['item_param_name']) + && isset($_REQUEST['item_param_type']) + && isset($_REQUEST['item_param_length']) + && isset($_REQUEST['item_param_opts_num']) + && isset($_REQUEST['item_param_opts_text']) + && is_array($_REQUEST['item_param_name']) + && is_array($_REQUEST['item_param_type']) + && is_array($_REQUEST['item_param_length']) + && is_array($_REQUEST['item_param_opts_num']) + && is_array($_REQUEST['item_param_opts_text']) + ) { + if ($_REQUEST['item_type'] == 'PROCEDURE') { + $retval['item_param_dir'] = $_REQUEST['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'] = $_REQUEST['item_param_name']; + $retval['item_param_type'] = $_REQUEST['item_param_type']; + foreach ($retval['item_param_type'] as $key => $value) { + if (! in_array($value, PMA_Util::getSupportedDatatypes(), true)) { + $retval['item_param_type'][$key] = ''; + } + } + $retval['item_param_length'] = $_REQUEST['item_param_length']; + $retval['item_param_opts_num'] = $_REQUEST['item_param_opts_num']; + $retval['item_param_opts_text'] = $_REQUEST['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($_REQUEST['item_returntype']) + && in_array($_REQUEST['item_returntype'], PMA_Util::getSupportedDatatypes()) + ) { + $retval['item_returntype'] = $_REQUEST['item_returntype']; + } + + $retval['item_isdeterministic'] = ''; + if (isset($_REQUEST['item_isdeterministic']) + && strtolower($_REQUEST['item_isdeterministic']) == 'on' + ) { + $retval['item_isdeterministic'] = " checked='checked'"; + } + $retval['item_securitytype_definer'] = ''; + $retval['item_securitytype_invoker'] = ''; + if (isset($_REQUEST['item_securitytype'])) { + if ($_REQUEST['item_securitytype'] === 'DEFINER') { + $retval['item_securitytype_definer'] = " selected='selected'"; + } else if ($_REQUEST['item_securitytype'] === 'INVOKER') { + $retval['item_securitytype_invoker'] = " selected='selected'"; + } + } + $retval['item_sqldataaccess'] = ''; + if (isset($_REQUEST['item_sqldataaccess']) + && in_array($_REQUEST['item_sqldataaccess'], $param_sqldataaccess, true) + ) { + $retval['item_sqldataaccess'] = $_REQUEST['item_sqldataaccess']; + } + + return $retval; +} // end function PMA_RTN_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. + */ +function PMA_RTN_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='" . PMA_Util::sqlAddSlashes($db) . "' " + . "AND SPECIFIC_NAME='" . PMA_Util::sqlAddSlashes($name) . "'" + . "AND ROUTINE_TYPE='" . PMA_Util::sqlAddSlashes($type) . "'"; + $query = "SELECT $fields FROM INFORMATION_SCHEMA.ROUTINES WHERE $where;"; + + $routine = $GLOBALS['dbi']->fetchSingleRow($query); + + if (! $routine) { + return false; + } + + // Get required data + $retval['item_name'] = $routine['SPECIFIC_NAME']; + $retval['item_type'] = $routine['ROUTINE_TYPE']; + $parsed_query = PMA_SQP_parse( + $GLOBALS['dbi']->getDefinition( + $db, + $routine['ROUTINE_TYPE'], + $routine['SPECIFIC_NAME'] + ) + ); + $params = PMA_RTN_parseAllParameters($parsed_query, $routine['ROUTINE_TYPE']); + $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_opts_num'] = $params['opts']; + $retval['item_param_opts_text'] = $params['opts']; + + // Get extra data + if ($all) { + 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'])) { + if (strlen($routine['DTD_IDENTIFIER']) > 63) { + // If the DTD_IDENTIFIER string from INFORMATION_SCHEMA is + // at least 64 characters, then it may actually have been + // chopped because that column is a varchar(64), so we will + // parse the output of SHOW CREATE query to get accurate + // information about the return variable. + $dtd = ''; + $fetching = false; + for ($i=0; $i<$parsed_query['len']; $i++) { + if ($parsed_query[$i]['type'] == 'alpha_reservedWord' + && strtoupper($parsed_query[$i]['data']) == 'RETURNS' + ) { + $fetching = true; + } else if ($fetching == true + && $parsed_query[$i]['type'] == 'alpha_reservedWord' + ) { + // We will not be looking for options such as UNSIGNED + // or ZEROFILL because there is no way that a numeric + // field's DTD_IDENTIFIER can be longer than 64 + // characters. We can safely assume that the return + // datatype is either ENUM or SET, so we only look + // for CHARSET. + $word = strtoupper($parsed_query[$i]['data']); + if ($word == 'CHARSET' + && ($parsed_query[$i+1]['type'] == 'alpha_charset' + || $parsed_query[$i+1]['type'] == 'alpha_identifier') + ) { + $dtd .= $word . ' ' . $parsed_query[$i+1]['data']; + } + break; + } else if ($fetching == true) { + $dtd .= $parsed_query[$i]['data'] . ' '; + } + } + $routine['DTD_IDENTIFIER'] = $dtd; + } + $returnparam = PMA_RTN_parseOneParameter($routine['DTD_IDENTIFIER']); + $retval['item_returntype'] = $returnparam[2]; + $retval['item_returnlength'] = $returnparam[3]; + $retval['item_returnopts_num'] = $returnparam[4]; + $retval['item_returnopts_text'] = $returnparam[4]; + } + $retval['item_definer'] = PMA_RTN_parseRoutineDefiner($parsed_query); + $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'"; + } else if ($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; +} // PMA_RTN_getDataFromName() + +/** + * Creates one row for the parameter table used in the routine editor. + * + * @param array $routine Data for the routine returned by + * PMA_RTN_getDataFromRequest() or + * PMA_RTN_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. + */ +function PMA_RTN_getParameterRow($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 => '') + ); + } else if (! 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 .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + + return $retval; +} // end PMA_RTN_getParameterRow() + +/** + * Displays a form used to add/edit a routine + * + * @param string $mode If the editor will be used 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 + * PMA_RTN_getDataFromRequest() or + * PMA_RTN_getDataFromName() + * + * @return string HTML code for the editor. + */ +function PMA_RTN_getEditorForm($mode, $operation, $routine) +{ + global $db, $errors, $param_sqldataaccess, $param_opts_num; + + // 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'; + } + } else if ($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']++; + } else if ($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']--; + } + $disable_remove_parameter = ''; + if (! $routine['item_num_params']) { + $disable_remove_parameter = " 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 .= PMA_URL_getHiddenInputs($db) . "\n"; + $retval .= "
      \n"; + $retval .= "" . __('Details') . "\n"; + $retval .= "
      " . __('Event name') . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "
      " . __('Event type') . "\n"; + if ($GLOBALS['is_ajax_request']) { + $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 .= " \n"; + $retval .= "\n"; + } + $retval .= "\n\n"; + $retval .= "\n\n"; + + return $retval; +} // end PMA_EVN_getEditorForm() + +/** + * Composes the query necessary to create an event from an HTTP request. + * + * @return string The CREATE EVENT query. + */ +function PMA_EVN_getQueryFromRequest() +{ + global $_REQUEST, $errors, $event_status, $event_type, $event_interval; + + $query = 'CREATE '; + if (! empty($_REQUEST['item_definer'])) { + if (strpos($_REQUEST['item_definer'], '@') !== false) { + $arr = explode('@', $_REQUEST['item_definer']); + $query .= 'DEFINER=' . PMA_Util::backquote($arr[0]); + $query .= '@' . PMA_Util::backquote($arr[1]) . ' '; + } else { + $errors[] = __('The definer must be in the "username@hostname" format'); + } + } + $query .= 'EVENT '; + if (! empty($_REQUEST['item_name'])) { + $query .= PMA_Util::backquote($_REQUEST['item_name']) . ' '; + } else { + $errors[] = __('You must provide an event name'); + } + $query .= 'ON SCHEDULE '; + if (! empty($_REQUEST['item_type']) + && in_array($_REQUEST['item_type'], $event_type) + ) { + if ($_REQUEST['item_type'] == 'RECURRING') { + if (! empty($_REQUEST['item_interval_value']) + && !empty($_REQUEST['item_interval_field']) + && in_array($_REQUEST['item_interval_field'], $event_interval) + ) { + $query .= 'EVERY ' . intval($_REQUEST['item_interval_value']) . ' '; + $query .= $_REQUEST['item_interval_field'] . ' '; + } else { + $errors[] = __('You must provide a valid interval value for the event.'); + } + if (! empty($_REQUEST['item_starts'])) { + $query .= "STARTS '" + . PMA_Util::sqlAddSlashes($_REQUEST['item_starts']) . "' "; + } + if (! empty($_REQUEST['item_ends'])) { + $query .= "ENDS '" + . PMA_Util::sqlAddSlashes($_REQUEST['item_ends']) . "' "; + } + } else { + if (! empty($_REQUEST['item_execute_at'])) { + $query .= "AT '" + . PMA_Util::sqlAddSlashes($_REQUEST['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($_REQUEST['item_preserve'])) { + $query .= 'NOT '; + } + $query .= 'PRESERVE '; + if (! empty($_REQUEST['item_status'])) { + foreach ($event_status['display'] as $key => $value) { + if ($value == $_REQUEST['item_status']) { + $query .= $event_status['query'][$key] . ' '; + break; + } + } + } + if (! empty($_REQUEST['item_comment'])) { + $query .= "COMMENT '" . PMA_Util::sqlAddslashes( + $_REQUEST['item_comment'] + ) . "' "; + } + $query .= 'DO '; + if (! empty($_REQUEST['item_definition'])) { + $query .= $_REQUEST['item_definition']; + } else { + $errors[] = __('You must provide an event definition.'); + } + + return $query; +} // end PMA_EVN_getQueryFromRequest() + +?> diff --git a/sources/libraries/rte/rte_export.lib.php b/sources/libraries/rte/rte_export.lib.php new file mode 100644 index 0000000..8fd9a7c --- /dev/null +++ b/sources/libraries/rte/rte_export.lib.php @@ -0,0 +1,122 @@ +' + . htmlspecialchars(trim($export_data)) . ''; + $title = sprintf(PMA_RTE_getWord('export'), $item_name); + if ($GLOBALS['is_ajax_request'] == true) { + $response = PMA_Response::getInstance(); + $response->addJSON('message', $export_data); + $response->addJSON('title', $title); + exit; + } else { + echo "
      \n" + . "$title\n" + . $export_data + . "
      \n"; + } + } else { + $_db = htmlspecialchars(PMA_Util::backquote($db)); + $message = __('Error in processing request:') . ' ' + . sprintf(PMA_RTE_getWord('not_found'), $item_name, $_db); + $response = PMA_message::error($message); + if ($GLOBALS['is_ajax_request'] == true) { + $response = PMA_Response::getInstance(); + $response->isSuccess(false); + $response->addJSON('message', $message); + exit; + } else { + $response->display(); + } + } +} // end PMA_RTE_handleExport() + +/** + * If necessary, prepares event information and passes + * it to PMA_RTE_handleExport() for the actual export. + * + * @return void + */ +function PMA_EVN_handleExport() +{ + 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); + PMA_RTE_handleExport($item_name, $export_data); + } +} // end PMA_EVN_handleExport() + +/** + * If necessary, prepares routine information and passes + * it to PMA_RTE_handleExport() for the actual export. + * + * @return void + */ +function PMA_RTN_handleExport() +{ + 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') { + $export_data = $GLOBALS['dbi']->getDefinition( + $db, + $_GET['item_type'], + $_GET['item_name'] + ); + PMA_RTE_handleExport($_GET['item_name'], $export_data); + } + } +} // end PMA_RTN_handleExport() + +/** + * If necessary, prepares trigger information and passes + * it to PMA_RTE_handleExport() for the actual export. + * + * @return void + */ +function PMA_TRI_handleExport() +{ + 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; + } + } + PMA_RTE_handleExport($item_name, $export_data); + } +} // end PMA_TRI_handleExport() +?> diff --git a/sources/libraries/rte/rte_footer.lib.php b/sources/libraries/rte/rte_footer.lib.php new file mode 100644 index 0000000..752f2f3 --- /dev/null +++ b/sources/libraries/rte/rte_footer.lib.php @@ -0,0 +1,127 @@ +\n"; + $retval .= "
      \n"; + $retval .= "" . _pgettext('Create new procedure', 'New') . "\n"; + $retval .= " \n"; + $retval .= "
      \n"; + $retval .= "\n\n"; + + return $retval; +} // end PMA_RTE_getFooterLinks() + +/** + * Creates a fieldset for adding a new routine, if the user has the privileges. + * + * @return string HTML code with containing the fotter fieldset + */ +function PMA_RTN_getFooterLinks() +{ + return PMA_RTE_getFooterLinks('CREATE_PROCEDURE', 'CREATE ROUTINE', 'ROUTINE'); +}// end PMA_RTN_getFooterLinks() + +/** + * Creates a fieldset for adding a new trigger, if the user has the privileges. + * + * @return string HTML code with containing the fotter fieldset + */ +function PMA_TRI_getFooterLinks() +{ + return PMA_RTE_getFooterLinks('CREATE_TRIGGER', 'TRIGGER', 'TRIGGER'); +} // end PMA_TRI_getFooterLinks() + +/** + * Creates a fieldset for adding a new event, if the user has the privileges. + * + * @return string HTML code with containing the fotter fieldset + */ +function PMA_EVN_getFooterLinks() +{ + 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 = 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 .= PMA_RTE_getFooterLinks('CREATE_EVENT', 'EVENT', 'EVENT'); + $retval .= "
      \n"; + $retval .= " \n"; + $retval .= " " . __('Event scheduler status') . "\n"; + $retval .= " \n"; + $retval .= "
      \n"; + // show the toggle button + $retval .= PMA_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 PMA_EVN_getFooterLinks() + +?> diff --git a/sources/libraries/rte/rte_list.lib.php b/sources/libraries/rte/rte_list.lib.php new file mode 100644 index 0000000..7cc0c1c --- /dev/null +++ b/sources/libraries/rte/rte_list.lib.php @@ -0,0 +1,386 @@ +\n"; + $retval .= "
      \n"; + $retval .= " \n"; + $retval .= " " . PMA_RTE_getWord('title') . "\n"; + $retval .= " " . PMA_Util::showMySQLDocu(PMA_RTE_getWord('docu')) . "\n"; + $retval .= " \n"; + $retval .= "
      \n"; + $retval .= " " . PMA_RTE_getWord('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"; // see comment above + for ($i = 0; $i < 7; $i++) { + $retval .= " \n"; + } + break; + case 'trigger': + $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"; // see comment above + for ($i = 0; $i < 6; $i++) { + $retval .= " \n"; + } + break; + default: + break; + } + $retval .= " \n"; + $retval .= " \n"; + $ct = 0; + foreach ($items as $item) { + $rowclass = ($ct % 2 == 0) ? 'odd' : 'even'; + if ($GLOBALS['is_ajax_request'] && empty($_REQUEST['ajax_page_request'])) { + $rowclass .= ' ajaxInsert hide'; + } + // Get each row from the correct function + switch ($type) { + case 'routine': + $retval .= PMA_RTN_getRowForList($item, $rowclass); + break; + case 'trigger': + $retval .= PMA_TRI_getRowForList($item, $rowclass); + break; + case 'event': + $retval .= PMA_EVN_getRowForList($item, $rowclass); + break; + default: + break; + } + $ct++; + } + $retval .= "
      " . __('Name') . "" . __('Action') . "" . __('Type') . "" . __('Returns') . "
      " . __('Name') . "" . __('Table') . "" . __('Action') . "" . __('Time') . "" . __('Event') . "
      " . __('Name') . "" . __('Status') . "" . __('Action') . "" . __('Type') . "
      \n"; + $retval .= "
      \n"; + $retval .= "\n"; + + return $retval; +} // end PMA_RTE_getList() + +/** + * Creates the contents for a row in the list of routines + * + * @param array $routine An array of routine data + * @param string $rowclass Empty or one of ['even'|'odd'] + * + * @return string HTML code of a row for the list of routines + */ +function PMA_RTN_getRowForList($routine, $rowclass = '') +{ + global $ajax_class, $url_query, $db, $titles; + + $sql_drop = sprintf( + 'DROP %s IF EXISTS %s', + $routine['ROUTINE_TYPE'], + PMA_Util::backquote($routine['SPECIFIC_NAME']) + ); + $type_link = "item_type={$routine['ROUTINE_TYPE']}"; + + $retval = "
      \n"; + $retval .= " " + . htmlspecialchars($sql_drop) . "\n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($routine['SPECIFIC_NAME']) . "\n"; + $retval .= " \n"; + $retval .= " \n"; + if ($routine['ROUTINE_DEFINITION'] !== null + && PMA_Util::currentUserHasPrivilege('ALTER ROUTINE', $db) + && PMA_Util::currentUserHasPrivilege('CREATE ROUTINE', $db) + ) { + $retval .= ' ' . $titles['Edit'] . "\n"; + } else { + $retval .= " {$titles['NoEdit']}\n"; + } + $retval .= " \n"; + + // There is a problem with PMA_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 he 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. + $routine_details = PMA_RTN_getDataFromName( + $routine['SPECIFIC_NAME'], + $routine['ROUTINE_TYPE'], + false + ); + if ($routine !== false) { + $execute_action = 'execute_routine'; + for ($i=0; $i<$routine_details['item_num_params']; $i++) { + if ($routine_details['item_type'] == 'PROCEDURE' + && $routine_details['item_param_dir'][$i] == 'OUT' + ) { + continue; + } + $execute_action = 'execute_dialog'; + break; + } + $retval .= ' ' . $titles['Execute'] . "\n"; + } + + $retval .= " \n"; + $retval .= ' ' . $titles['Export'] . "\n"; + $retval .= " \n"; + if (PMA_Util::currentUserHasPrivilege('ALTER ROUTINE', $db)) { + $retval .= ' ' . $titles['Drop'] . "\n"; + } else { + $retval .= " {$titles['NoDrop']}\n"; + } + $retval .= " \n"; + $retval .= " {$routine['ROUTINE_TYPE']}\n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($routine['DTD_IDENTIFIER']) . "\n"; + $retval .= "
      \n"; + $retval .= " " + . htmlspecialchars($trigger['drop']) . "\n"; + $retval .= " \n"; + $retval .= " " . htmlspecialchars($trigger['name']) . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " " + . $trigger['table'] . "\n"; + $retval .= " \n"; + if (PMA_Util::currentUserHasPrivilege('TRIGGER', $db, $table)) { + $retval .= ' ' . $titles['Edit'] . "\n"; + } else { + $retval .= " {$titles['NoEdit']}\n"; + } + $retval .= " \n"; + $retval .= ' ' . $titles['Export'] . "\n"; + $retval .= " \n"; + if (PMA_Util::currentUserHasPrivilege('TRIGGER', $db)) { + $retval .= ' ' . $titles['Drop'] . "\n"; + } else { + $retval .= " {$titles['NoDrop']}\n"; + } + $retval .= " \n"; + $retval .= " {$trigger['action_timing']}\n"; + $retval .= " \n"; + $retval .= " {$trigger['event_manipulation']}\n"; + $retval .= "
      \n"; + $retval .= " " + . htmlspecialchars($sql_drop) . "\n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($event['EVENT_NAME']) . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$event['STATUS']}\n"; + $retval .= " \n"; + if (PMA_Util::currentUserHasPrivilege('EVENT', $db)) { + $retval .= ' ' . $titles['Edit'] . "\n"; + } else { + $retval .= " {$titles['NoEdit']}\n"; + } + $retval .= " \n"; + $retval .= ' ' . $titles['Export'] . "\n"; + $retval .= " \n"; + if (PMA_Util::currentUserHasPrivilege('EVENT', $db)) { + $retval .= ' ' . $titles['Drop'] . "\n"; + } else { + $retval .= " {$titles['NoDrop']}\n"; + } + $retval .= " \n"; + $retval .= " {$event['EVENT_TYPE']}\n"; + $retval .= "
      \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " ---\n"; + $retval .= PMA_generateCharsetDropdownBox( + PMA_CSDROPDOWN_CHARSET, + "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 .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + // parameter handling end + $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 .= "
      " . __('Routine name') . "\n"; + $retval .= " \n"; + if ($GLOBALS['is_ajax_request']) { + $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 .= " "; + for ($i=0; $i<$routine['item_num_params']; $i++) { // each parameter + $retval .= PMA_RTN_getParameterRow($routine, $i, $isprocedure_class); + } + $retval .= "
      " + . __('Direction') . "" . __('Name') . "" . __('Type') . "" . __('Length/Values') . "" . __('Options') . " 
      \n"; + $retval .= "
       \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "
      " . __('Return type') . "
      " . __('Return length/values') . "\n"; + $retval .= "
      \n"; + $retval .= PMA_generateCharsetDropdownBox( + PMA_CSDROPDOWN_CHARSET, + "item_returnopts_text", + null, + $routine['item_returnopts_text'] + ); + $retval .= "\n
      \n"; + $retval .= "
      \n"; + $retval .= "
      ---
      \n"; + $retval .= "
      " . __('Definition') . "
      " . __('Is deterministic') . "
      " . __('Definer') . "\n"; + $retval .= "
      " . __('SQL data access') . "
      " . __('Comment') . "\n"; + $retval .= "\n"; + } + $retval .= "\n\n"; + $retval .= "\n\n"; + + return $retval; +} // end PMA_RTN_getEditorForm() + +/** + * Composes the query necessary to create a routine from an HTTP request. + * + * @return string The CREATE [ROUTINE | PROCEDURE] query. + */ +function PMA_RTN_getQueryFromRequest() +{ + global $_REQUEST, $errors, $param_sqldataaccess, $param_directions, $PMA_Types; + + $_REQUEST['item_type'] = isset($_REQUEST['item_type']) + ? $_REQUEST['item_type'] : ''; + + $query = 'CREATE '; + if (! empty($_REQUEST['item_definer'])) { + if (strpos($_REQUEST['item_definer'], '@') !== false) { + $arr = explode('@', $_REQUEST['item_definer']); + $query .= 'DEFINER=' . PMA_Util::backquote($arr[0]); + $query .= '@' . PMA_Util::backquote($arr[1]) . ' '; + } else { + $errors[] = __('The definer must be in the "username@hostname" format'); + } + } + if ($_REQUEST['item_type'] == 'FUNCTION' + || $_REQUEST['item_type'] == 'PROCEDURE' + ) { + $query .= $_REQUEST['item_type'] . ' '; + } else { + $errors[] = sprintf( + __('Invalid routine type: "%s"'), + htmlspecialchars($_REQUEST['item_type']) + ); + } + if (! empty($_REQUEST['item_name'])) { + $query .= PMA_Util::backquote($_REQUEST['item_name']); + } else { + $errors[] = __('You must provide a routine name'); + } + $params = ''; + $warned_about_dir = false; + $warned_about_name = false; + $warned_about_length = false; + + if ( ! empty($_REQUEST['item_param_name']) + && ! empty($_REQUEST['item_param_type']) + && ! empty($_REQUEST['item_param_length']) + && is_array($_REQUEST['item_param_name']) + && is_array($_REQUEST['item_param_type']) + && is_array($_REQUEST['item_param_length']) + ) { + $item_param_name = $_REQUEST['item_param_name']; + $item_param_type = $_REQUEST['item_param_type']; + $item_param_length = $_REQUEST['item_param_length']; + + for ($i=0; $i < count($item_param_name); $i++) { + if (! empty($item_param_name[$i]) + && ! empty($item_param_type[$i]) + ) { + if ($_REQUEST['item_type'] == 'PROCEDURE' + && ! empty($_REQUEST['item_param_dir'][$i]) + && in_array($_REQUEST['item_param_dir'][$i], $param_directions) + ) { + $params .= $_REQUEST['item_param_dir'][$i] . " " + . PMA_Util::backquote($item_param_name[$i]) + . " " . $item_param_type[$i]; + } else if ($_REQUEST['item_type'] == 'FUNCTION') { + $params .= PMA_Util::backquote($item_param_name[$i]) + . " " . $item_param_type[$i]; + } else if (! $warned_about_dir) { + $warned_about_dir = true; + $errors[] = sprintf( + __('Invalid direction "%s" given for parameter.'), + htmlspecialchars($_REQUEST['item_param_dir'][$i]) + ); + } + if ($item_param_length[$i] != '' + && !preg_match( + '@^(DATE|DATETIME|TIME|TINYBLOB|TINYTEXT|BLOB|TEXT|' + . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|' + . 'SERIAL|BOOLEAN)$@i', + $item_param_type[$i] + ) + ) { + $params .= "(" . $item_param_length[$i] . ")"; + } else if ($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($_REQUEST['item_param_opts_text'][$i])) { + if ($PMA_Types->getTypeClass($item_param_type[$i]) == 'CHAR') { + $params .= ' CHARSET ' + . strtolower($_REQUEST['item_param_opts_text'][$i]); + } + } + if (! empty($_REQUEST['item_param_opts_num'][$i])) { + if ($PMA_Types->getTypeClass($item_param_type[$i]) == 'NUMBER') { + $params .= ' ' + . strtoupper($_REQUEST['item_param_opts_num'][$i]); + } + } + if ($i != (count($item_param_name) - 1)) { + $params .= ", "; + } + } else if (! $warned_about_name) { + $warned_about_name = true; + $errors[] = __( + 'You must provide a name and a type for each routine parameter.' + ); + break; + } + } + } + $query .= "(" . $params . ") "; + if ($_REQUEST['item_type'] == 'FUNCTION') { + $item_returntype = isset($_REQUEST['item_returntype']) + ? $_REQUEST['item_returntype'] + : null; + + if (! empty($item_returntype) + && in_array( + $item_returntype, PMA_Util::getSupportedDatatypes() + ) + ) { + $query .= "RETURNS " . $item_returntype; + } else { + $errors[] = __('You must provide a valid return type for the routine.'); + } + if (! empty($_REQUEST['item_returnlength']) + && !preg_match( + '@^(DATE|DATETIME|TIME|TINYBLOB|TINYTEXT|BLOB|TEXT|' + . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|SERIAL|BOOLEAN)$@i', + $item_returntype + ) + ) { + $query .= "(" . $_REQUEST['item_returnlength'] . ")"; + } else if (empty($_REQUEST['item_returnlength']) + && preg_match( + '@^(ENUM|SET|VARCHAR|VARBINARY)$@i', $item_returntype + ) + ) { + 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($_REQUEST['item_returnopts_text'])) { + if ($PMA_Types->getTypeClass($item_returntype) == 'CHAR') { + $query .= ' CHARSET ' + . strtolower($_REQUEST['item_returnopts_text']); + } + } + if (! empty($_REQUEST['item_returnopts_num'])) { + if ($PMA_Types->getTypeClass($item_returntype) == 'NUMBER') { + $query .= ' ' . strtoupper($_REQUEST['item_returnopts_num']); + } + } + $query .= ' '; + } + if (! empty($_REQUEST['item_comment'])) { + $query .= "COMMENT '" . PMA_Util::sqlAddslashes($_REQUEST['item_comment']) + . "' "; + } + if (isset($_REQUEST['item_isdeterministic'])) { + $query .= 'DETERMINISTIC '; + } else { + $query .= 'NOT DETERMINISTIC '; + } + if (! empty($_REQUEST['item_sqldataaccess']) + && in_array($_REQUEST['item_sqldataaccess'], $param_sqldataaccess) + ) { + $query .= $_REQUEST['item_sqldataaccess'] . ' '; + } + if (! empty($_REQUEST['item_securitytype'])) { + if ($_REQUEST['item_securitytype'] == 'DEFINER' + || $_REQUEST['item_securitytype'] == 'INVOKER' + ) { + $query .= 'SQL SECURITY ' . $_REQUEST['item_securitytype'] . ' '; + } + } + if (! empty($_REQUEST['item_definition'])) { + $query .= $_REQUEST['item_definition']; + } else { + $errors[] = __('You must provide a routine definition.'); + } + + return $query; +} // end PMA_RTN_getQueryFromRequest() + +/** + * Handles requests for executing a routine + * + * @return void + */ +function PMA_RTN_handleExecute() +{ + global $_GET, $_POST, $_REQUEST, $GLOBALS, $db; + + /** + * Handle all user requests other than the default of listing routines + */ + if (! empty($_REQUEST['execute_routine']) && ! empty($_REQUEST['item_name'])) { + // Build the queries + $routine = PMA_RTN_getDataFromName( + $_REQUEST['item_name'], $_REQUEST['item_type'], false + ); + if ($routine !== false) { + $queries = array(); + $end_query = array(); + $args = array(); + $all_functions = $GLOBALS['PMA_Types']->getAllFunctions(); + for ($i=0; $i<$routine['item_num_params']; $i++) { + if (isset($_REQUEST['params'][$routine['item_param_name'][$i]])) { + $value = $_REQUEST['params'][$routine['item_param_name'][$i]]; + if (is_array($value)) { // is SET type + $value = implode(',', $value); + } + $value = PMA_Util::sqlAddSlashes($value); + if (! empty($_REQUEST['funcs'][$routine['item_param_name'][$i]]) + && in_array( + $_REQUEST['funcs'][$routine['item_param_name'][$i]], + $all_functions + ) + ) { + $queries[] = "SET @p$i=" + . $_REQUEST['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 " + . PMA_Util::backquote($routine['item_param_name'][$i]); + } + } + } + if ($routine['item_type'] == 'PROCEDURE') { + $queries[] = "CALL " . PMA_Util::backquote($routine['item_name']) + . "(" . implode(', ', $args) . ");\n"; + if (count($end_query)) { + $queries[] = "SELECT " . implode(', ', $end_query) . ";\n"; + } + } else { + $queries[] = "SELECT " . PMA_Util::backquote($routine['item_name']) + . "(" . implode(', ', $args) . ") " + . "AS " . PMA_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 = PMA_Util::formatSql(implode($queries, "\n")); + + // Display results + $output .= "
      "; + $output .= sprintf( + __('Execution results of routine %s'), + PMA_Util::backquote(htmlspecialchars($routine['item_name'])) + ); + $output .= ""; + + $num_of_rusults_set_to_display = 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 $key => $field) { + $output .= ""; + } + $output .= ""; + + $color_class = 'odd'; + + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $output .= ""; + foreach ($row as $key => $value) { + if ($value === null) { + $value = 'NULL'; + } else { + $value = htmlspecialchars($value); + } + $output .= ""; + } + $output .= ""; + $color_class = ($color_class == 'odd') ? 'even' : 'odd'; + } + + $output .= "
      "; + $output .= htmlspecialchars($field->name); + $output .= "
      " + . $value . "
      "; + $num_of_rusults_set_to_display++; + $affected = $num_rows; + + } + + if (! $GLOBALS['dbi']->moreResults()) { + break; + } + + $output .= "
      "; + + $GLOBALS['dbi']->freeResult($result); + + } while ($GLOBALS['dbi']->nextResult()); + + $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 = PMA_message::success($message); + + if ($num_of_rusults_set_to_display == 0) { + $notice = __( + 'MySQL returned an empty result set (i.e. zero rows).' + ); + $output .= PMA_message::notice($notice)->getDisplay(); + } + + } else { + $output = ''; + $message = PMA_message::error( + sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($multiple_query) + ) + . '

      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(null) + ); + } + + // Print/send output + if ($GLOBALS['is_ajax_request']) { + $response = PMA_Response::getInstance(); + $response->isSuccess($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 + } + } else { + $message = __('Error in processing request:') . ' '; + $message .= sprintf( + PMA_RTE_getWord('not_found'), + htmlspecialchars(PMA_Util::backquote($_REQUEST['item_name'])), + htmlspecialchars(PMA_Util::backquote($db)) + ); + $message = PMA_message::error($message); + if ($GLOBALS['is_ajax_request']) { + $response = PMA_Response::getInstance(); + $response->isSuccess(false); + $response->addJSON('message', $message); + exit; + } else { + echo $message->getDisplay(); + unset($_POST); + } + } + } else if (! empty($_GET['execute_dialog']) && ! empty($_GET['item_name'])) { + /** + * Display the execute form for a routine. + */ + $routine = PMA_RTN_getDataFromName( + $_GET['item_name'], $_GET['item_type'], true + ); + if ($routine !== false) { + $form = PMA_RTN_getExecuteForm($routine); + if ($GLOBALS['is_ajax_request'] == true) { + $title = __("Execute routine") . " " . PMA_Util::backquote( + htmlentities($_GET['item_name'], ENT_QUOTES) + ); + $response = PMA_Response::getInstance(); + $response->addJSON('message', $form); + $response->addJSON('title', $title); + $response->addJSON('dialog', true); + } else { + echo "\n\n

      " . __("Execute routine") . "

      \n\n"; + echo $form; + } + exit; + } else if (($GLOBALS['is_ajax_request'] == true)) { + $message = __('Error in processing request:') . ' '; + $message .= sprintf( + PMA_RTE_getWord('not_found'), + htmlspecialchars(PMA_Util::backquote($_REQUEST['item_name'])), + htmlspecialchars(PMA_Util::backquote($db)) + ); + $message = PMA_message::error($message); + + $response = PMA_Response::getInstance(); + $response->isSuccess(false); + $response->addJSON('message', $message); + exit; + } + } +} + +/** + * Creates the HTML code that shows the routine execution dialog. + * + * @param array $routine Data for the routine returned by + * PMA_RTN_getDataFromName() + * + * @return string HTML code for the routine execution dialog. + */ +function PMA_RTN_getExecuteForm($routine) +{ + global $db, $cfg; + + // 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 .= "
      \n"; + $retval .= "\n"; + $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 = PMA_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; + } + $rowclass = ($i % 2 == 0) ? 'even' : 'odd'; + $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'; + } else if ($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( + strtolower($routine['item_param_type'][$i]), $no_support_types + ) + ) { + $retval .= "--\n"; + } else { + $field = array( + 'True_Type' => 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'))) { + $tokens = PMA_SQP_parse($routine['item_param_length'][$i]); + if ($routine['item_param_type'][$i] == 'ENUM') { + $input_type = 'radio'; + } else { + $input_type = 'checkbox'; + } + for ($j=0; $j<$tokens['len']; $j++) { + if ($tokens[$j]['type'] != 'punct_listsep') { + $tokens[$j]['data'] = htmlentities( + PMA_Util::unquote($tokens[$j]['data']), + ENT_QUOTES + ); + $retval .= "" + . $tokens[$j]['data'] . "
      \n"; + } + } + } else if (in_array( + strtolower($routine['item_param_type'][$i]), $no_support_types + )) { + $retval .= "\n"; + } else { + $retval .= "\n"; + } + $retval .= "
      \n"; + if ($GLOBALS['is_ajax_request'] != true) { + $retval .= "\n\n"; + $retval .= "
      \n"; + $retval .= " \n"; + $retval .= "
      \n"; + } else { + $retval .= ""; + $retval .= ""; + } + $retval .= "
      \n\n"; + $retval .= "\n\n"; + + return $retval; +} // end PMA_RTN_getExecuteForm() + +?> diff --git a/sources/libraries/rte/rte_triggers.lib.php b/sources/libraries/rte/rte_triggers.lib.php new file mode 100644 index 0000000..e8157a4 --- /dev/null +++ b/sources/libraries/rte/rte_triggers.lib.php @@ -0,0 +1,486 @@ +getTriggers($db, $table); + echo PMA_RTE_getList('trigger', $items); + /** + * Display a link for adding a new trigger, + * if the user has the necessary privileges + */ + echo PMA_TRI_getFooterLinks(); +} // end PMA_TRI_main() + +/** + * Handles editor requests for adding or editing an item + * + * @return void + */ +function PMA_TRI_handleEditor() +{ + global $_REQUEST, $_POST, $errors, $db, $table; + + if (! empty($_REQUEST['editor_process_add']) + || ! empty($_REQUEST['editor_process_edit']) + ) { + $sql_query = ''; + + $item_query = PMA_TRI_getQueryFromRequest(); + + if (! count($errors)) { // set by PMA_RTN_getQueryFromRequest() + // Execute the created query + if (! empty($_REQUEST['editor_process_edit'])) { + // Backup the old trigger, in case something goes wrong + $trigger = PMA_TRI_getDataFromName($_REQUEST['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(null); + } 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(null); + // 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); + if (! $result) { + // OMG, this is really bad! We dropped the query, + // failed to create a new one + // and now even the backup query does not execute! + // This should not happen, but we better handle + // this just in case. + $errors[] = __( + 'Sorry, we failed to restore the dropped trigger.' + ) + . '
      ' + . __('The backed up query was:') + . "\"" . htmlspecialchars($create_item) . "\"" + . '
      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(null); + } + } else { + $message = PMA_Message::success( + __('Trigger %1$s has been modified.') + ); + $message->addParam( + PMA_Util::backquote($_REQUEST['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(null); + } else { + $message = PMA_Message::success( + __('Trigger %1$s has been created.') + ); + $message->addParam( + PMA_Util::backquote($_REQUEST['item_name']) + ); + $sql_query = $item_query; + } + } + } + + if (count($errors)) { + $message = PMA_Message::error(__('One or more errors have occurred while processing your request:')); + $message->addString('
        '); + foreach ($errors as $string) { + $message->addString('
      • ' . $string . '
      • '); + } + $message->addString('
      '); + } + + $output = PMA_Util::getMessage($message, $sql_query); + if ($GLOBALS['is_ajax_request']) { + $response = PMA_Response::getInstance(); + if ($message->isSuccess()) { + $items = $GLOBALS['dbi']->getTriggers($db, $table, ''); + $trigger = false; + foreach ($items as $value) { + if ($value['name'] == $_REQUEST['item_name']) { + $trigger = $value; + } + } + $insert = false; + if (empty($table) + || ($trigger !== false && $table == $trigger['table']) + ) { + $insert = true; + $response->addJSON('new_row', PMA_TRI_getRowForList($trigger)); + $response->addJSON( + 'name', + htmlspecialchars( + strtoupper($_REQUEST['item_name']) + ) + ); + } + $response->addJSON('insert', $insert); + $response->addJSON('message', $output); + } else { + $response->addJSON('message', $message); + $response->isSuccess(false); + } + exit; + } + } + + /** + * Display a form used to add/edit a trigger, if necessary + */ + if (count($errors) + || (empty($_REQUEST['editor_process_add']) + && empty($_REQUEST['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 = PMA_RTE_getWord('add'); + $item = PMA_TRI_getDataFromRequest(); + $mode = 'add'; + } else if (! empty($_REQUEST['edit_item'])) { + $title = __("Edit trigger"); + if (! empty($_REQUEST['item_name']) + && empty($_REQUEST['editor_process_edit']) + ) { + $item = PMA_TRI_getDataFromName($_REQUEST['item_name']); + if ($item !== false) { + $item['item_original_name'] = $item['item_name']; + } + } else { + $item = PMA_TRI_getDataFromRequest(); + } + $mode = 'edit'; + } + if ($item !== false) { + // Show form + $editor = PMA_TRI_getEditorForm($mode, $item); + if ($GLOBALS['is_ajax_request']) { + $response = PMA_Response::getInstance(); + $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( + PMA_RTE_getWord('not_found'), + htmlspecialchars(PMA_Util::backquote($_REQUEST['item_name'])), + htmlspecialchars(PMA_Util::backquote($db)) + ); + $message = PMA_message::error($message); + if ($GLOBALS['is_ajax_request']) { + $response = PMA_Response::getInstance(); + $response->isSuccess(false); + $response->addJSON('message', $message); + exit; + } else { + $message->display(); + } + } + } +} // end PMA_TRI_handleEditor() + +/** + * This function will generate the values that are required to for the editor + * + * @return array Data necessary to create the editor. + */ +function PMA_TRI_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($_REQUEST[$index]) ? $_REQUEST[$index] : ''; + } + return $retval; +} // end PMA_TRI_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. + */ +function PMA_TRI_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 PMA_TRI_getDataFromName() + +/** + * Displays a form used to add/edit a trigger + * + * @param string $mode If the editor will be used edit a trigger + * or add a new one: 'edit' or 'add'. + * @param array $item Data for the trigger returned by PMA_TRI_getDataFromRequest() + * or PMA_TRI_getDataFromName() + * + * @return string HTML code for the editor. + */ +function PMA_TRI_getEditorForm($mode, $item) +{ + global $db, $table, $event_manipulations, $action_timings; + + // 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`='" . PMA_Util::sqlAddSlashes($db) . "' "; + $query .= "AND `TABLE_TYPE`='BASE TABLE'"; + $tables = $GLOBALS['dbi']->fetchResult($query); + + // Create the output + $retval = ""; + $retval .= "\n\n"; + $retval .= "
      \n"; + $retval .= "\n"; + $retval .= $original_data; + $retval .= PMA_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 .= "
      " . __('Trigger name') . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "
      " . _pgettext('Trigger action time', 'Time') . "
      " . __('Event') . "
      " . __('Definition') . "
      " . __('Definer') . "\n"; + $retval .= "\n"; + } + $retval .= "\n\n"; + $retval .= "\n\n"; + + return $retval; +} // end PMA_TRI_getEditorForm() + +/** + * Composes the query necessary to create a trigger from an HTTP request. + * + * @return string The CREATE TRIGGER query. + */ +function PMA_TRI_getQueryFromRequest() +{ + global $_REQUEST, $db, $errors, $action_timings, $event_manipulations; + + $query = 'CREATE '; + if (! empty($_REQUEST['item_definer'])) { + if (strpos($_REQUEST['item_definer'], '@') !== false) { + $arr = explode('@', $_REQUEST['item_definer']); + $query .= 'DEFINER=' . PMA_Util::backquote($arr[0]); + $query .= '@' . PMA_Util::backquote($arr[1]) . ' '; + } else { + $errors[] = __('The definer must be in the "username@hostname" format'); + } + } + $query .= 'TRIGGER '; + if (! empty($_REQUEST['item_name'])) { + $query .= PMA_Util::backquote($_REQUEST['item_name']) . ' '; + } else { + $errors[] = __('You must provide a trigger name'); + } + if (! empty($_REQUEST['item_timing']) + && in_array($_REQUEST['item_timing'], $action_timings) + ) { + $query .= $_REQUEST['item_timing'] . ' '; + } else { + $errors[] = __('You must provide a valid timing for the trigger'); + } + if (! empty($_REQUEST['item_event']) + && in_array($_REQUEST['item_event'], $event_manipulations) + ) { + $query .= $_REQUEST['item_event'] . ' '; + } else { + $errors[] = __('You must provide a valid event for the trigger'); + } + $query .= 'ON '; + if (! empty($_REQUEST['item_table']) + && in_array($_REQUEST['item_table'], $GLOBALS['dbi']->getTables($db)) + ) { + $query .= PMA_Util::backquote($_REQUEST['item_table']); + } else { + $errors[] = __('You must provide a valid table name'); + } + $query .= ' FOR EACH ROW '; + if (! empty($_REQUEST['item_definition'])) { + $query .= $_REQUEST['item_definition']; + } else { + $errors[] = __('You must provide a trigger definition.'); + } + + return $query; +} // end PMA_TRI_getQueryFromRequest() + +?> diff --git a/sources/libraries/rte/rte_words.lib.php b/sources/libraries/rte/rte_words.lib.php new file mode 100644 index 0000000..85210bf --- /dev/null +++ b/sources/libraries/rte/rte_words.lib.php @@ -0,0 +1,69 @@ + __('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'), + '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 PMA_RTE_getWord() + +?> diff --git a/sources/libraries/sanitizing.lib.php b/sources/libraries/sanitizing.lib.php new file mode 100644 index 0000000..7e6e84d --- /dev/null +++ b/sources/libraries/sanitizing.lib.php @@ -0,0 +1,191 @@ +'; +} + +/** + * Callback function for replacing [doc@anchor] links in bb code. + * + * @param array $found Array of preg matches + * + * @return string Replaced string + */ +function PMA_replaceDocLink($found) +{ + $anchor = $found[1]; + if (strncmp('faq', $anchor, 3) == 0) { + $page = 'faq'; + } else if (strncmp('cfg', $anchor, 3) == 0) { + $page = 'cfg'; + } else { + /* Guess */ + $page = 'setup'; + } + $link = PMA_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 + */ +function PMA_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]' => '