├── public ├── css │ └── default │ │ ├── license.txt │ │ ├── db.png │ │ ├── cache.png │ │ ├── clock.png │ │ ├── close.png │ │ ├── top.png │ │ ├── explain.png │ │ ├── records.png │ │ ├── affected.png │ │ ├── backtrace.png │ │ ├── db-manager.png │ │ ├── unbuffered.png │ │ ├── zebra_database.min.css │ │ ├── zebra_database.css │ │ └── zebra_database.scss └── javascript │ ├── zebra_database.min.js │ └── zebra_database.src.js ├── examples ├── credentials.php ├── install.txt ├── ajax.php └── example.php ├── update-debug-info.php ├── eslint.config.js ├── languages ├── english.php ├── russian.php └── german.php ├── LICENSE.md ├── README.md ├── CHANGELOG.md └── includes └── SqlFormatter.php /public/css/default/license.txt: -------------------------------------------------------------------------------- 1 | All icons are from https://www.flaticon.com/ from various authors -------------------------------------------------------------------------------- /public/css/default/db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefangabos/Zebra_Database/HEAD/public/css/default/db.png -------------------------------------------------------------------------------- /public/css/default/cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefangabos/Zebra_Database/HEAD/public/css/default/cache.png -------------------------------------------------------------------------------- /public/css/default/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefangabos/Zebra_Database/HEAD/public/css/default/clock.png -------------------------------------------------------------------------------- /public/css/default/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefangabos/Zebra_Database/HEAD/public/css/default/close.png -------------------------------------------------------------------------------- /public/css/default/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefangabos/Zebra_Database/HEAD/public/css/default/top.png -------------------------------------------------------------------------------- /public/css/default/explain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefangabos/Zebra_Database/HEAD/public/css/default/explain.png -------------------------------------------------------------------------------- /public/css/default/records.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefangabos/Zebra_Database/HEAD/public/css/default/records.png -------------------------------------------------------------------------------- /public/css/default/affected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefangabos/Zebra_Database/HEAD/public/css/default/affected.png -------------------------------------------------------------------------------- /public/css/default/backtrace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefangabos/Zebra_Database/HEAD/public/css/default/backtrace.png -------------------------------------------------------------------------------- /public/css/default/db-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefangabos/Zebra_Database/HEAD/public/css/default/db-manager.png -------------------------------------------------------------------------------- /public/css/default/unbuffered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefangabos/Zebra_Database/HEAD/public/css/default/unbuffered.png -------------------------------------------------------------------------------- /examples/credentials.php: -------------------------------------------------------------------------------- 1 | '', 5 | 'username' => '', 6 | 'password' => '', 7 | 'database' => '', 8 | 'port' => '', 9 | 'socket' => '', 10 | 'flags' => 0, 11 | ]; 12 | -------------------------------------------------------------------------------- /examples/install.txt: -------------------------------------------------------------------------------- 1 | Prior to running the example, you must first download and install the the "world" database from MySQL's website at https://dev.mysql.com/doc/index-other.html. 2 | 3 | Then, you must edit example.php and change the settings of host, user name, password and database to match your configuration. -------------------------------------------------------------------------------- /examples/ajax.php: -------------------------------------------------------------------------------- 1 | connect($credentials['host'], $credentials['username'], $credentials['password'], $credentials['database']); 17 | 18 | // let's work with a country 19 | $country = 'United States'; 20 | 21 | // get the country's code 22 | $country_code = $db->dlookup('Code', 'country', 'Name = ?', array($country)); 23 | -------------------------------------------------------------------------------- /update-debug-info.php: -------------------------------------------------------------------------------- 1 | You need to move the `update-debug-info.php` file from the library's root folder to location accessible by an 7 | * AJAX GET request. Additionally, you can rename the file to whatever suits your needs - or use whatever technique 8 | * you want, as long as the file is publicly accessible and its content is unchanged. 9 | * 10 | * Read more {@link Zebra_Database::$debug_ajax here}. 11 | * 12 | * @license https://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE 13 | * @package Zebra_Database 14 | */ 15 | 16 | // no POST data, only a single GET parameter, is the right GET parameter, it contains a valid ID (numeric only), a file with the right name exists (zdb-log-[numeric ID]) 17 | if (empty($_POST) && isset($_GET) && is_array($_GET) && count($_GET) == 1 && isset($_GET['id']) && preg_match('/^[0-9]{12}$/', $_GET['id']) && file_exists(($path = sys_get_temp_dir() . '/zdb-log-' . $_GET['id'])) && is_file($path)) { 18 | echo file_get_contents($path); 19 | unlink($path); 20 | die(); 21 | } 22 | -------------------------------------------------------------------------------- /examples/example.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
10 | Prior to running this example you must first download and install the
11 | world test database from MySQL's website.
12 | Then, you must edit this file (example.php) and change the settings of host, user name, password
13 | and database to match your configuration.
14 |
In 5 seconds an AJAX requests will be made and the results will be addded to the debugging console.
17 | 18 | debug_ajax = '../update-debug-info.php'; 34 | 35 | // connect to the MySQL server and select the database 36 | $db->connect($credentials['host'], $credentials['username'], $credentials['password'], $credentials['database'], $credentials['port'], $credentials['socket'], false, $credentials['flags']); 37 | 38 | $db->set_charset(); 39 | 40 | // let's work with a country 41 | $country = 'United States'; 42 | 43 | // get the country's code 44 | $country_code = $db->dlookup('Code', 'country', 'Name = ?', array($country)); 45 | 46 | // get all the cities for the country code 47 | $db->query(' 48 | SELECT 49 | Name 50 | FROM 51 | city 52 | WHERE 53 | CountryCode = ? 54 | ORDER BY 55 | Name 56 | ', array($country_code)); 57 | 58 | // get all the languages spoken for the country code 59 | $db->query(' 60 | SELECT 61 | Language, 62 | IsOfficial, 63 | Percentage 64 | FROM 65 | countrylanguage 66 | WHERE 67 | CountryCode = ? 68 | ORDER BY 69 | Percentage DESC 70 | ', array($country_code)); 71 | 72 | ?> 73 | 74 | 75 | 76 | 77 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | "languageOptions": { 3 | "globals": { 4 | "$": true 5 | } 6 | }, 7 | "rules": { 8 | "array-bracket-spacing": ["warn", "never"], 9 | "block-scoped-var": 1, 10 | "block-spacing": ["warn", "always"], 11 | "brace-style": ["warn", "1tbs", { "allowSingleLine": true }], 12 | "comma-dangle": 1, 13 | "comma-spacing": ["warn", { 14 | "before": false, 15 | "after": true 16 | }], 17 | "comma-style": ["warn", "last"], 18 | "computed-property-spacing": ["warn", "never"], 19 | "curly": ["warn", "multi"], 20 | "dot-location": ["warn", "property"], 21 | "eol-last": ["warn", "always"], 22 | "indent": ["warn", 4, { 23 | "SwitchCase": 1, 24 | "ignoreComments": true 25 | }], 26 | "keyword-spacing": ["warn", { 27 | "before": true, 28 | "after": true 29 | }], 30 | "func-call-spacing": ["warn", "never"], 31 | "key-spacing": ["warn", { 32 | "beforeColon": false, 33 | "mode": "minimum" 34 | }], 35 | "lines-around-comment": ["warn", { 36 | "beforeBlockComment": true, 37 | "afterBlockComment": false 38 | }], 39 | "quotes": ["warn", "single"], 40 | "newline-after-var": ["warn", "always"], 41 | "newline-before-return": 1, 42 | "no-console": 1, 43 | "no-dupe-args": 1, 44 | "no-dupe-keys": 1, 45 | "no-duplicate-case": 1, 46 | "no-else-return": 1, 47 | "no-empty": 1, 48 | "no-extra-semi": 1, 49 | "no-fallthrough": 1, 50 | "no-func-assign": 1, 51 | "no-inner-declarations": ["warn", "functions"], 52 | "no-irregular-whitespace": ["warn", { 53 | "skipStrings": false, 54 | "skipComments": false, 55 | "skipRegExps": false, 56 | "skipTemplates": false 57 | }], 58 | "no-lonely-if": 1, 59 | "no-mixed-spaces-and-tabs": 1, 60 | "no-multi-spaces": ["warn", { 61 | "ignoreEOLComments": true 62 | }], 63 | "no-multiple-empty-lines": ["warn", { 64 | "max": 1, 65 | "maxEOF": 1, 66 | "maxBOF": 0 67 | }], 68 | "no-redeclare": 1, 69 | "no-tabs": 1, 70 | "no-trailing-spaces": ["warn", { 71 | "skipBlankLines": false 72 | }], 73 | "no-unexpected-multiline": 1, 74 | "no-unneeded-ternary": ["warn", { 75 | "defaultAssignment": false 76 | }], 77 | "no-unused-vars": [1, { 78 | "vars": "all", 79 | "args": "after-used", 80 | "varsIgnorePattern": "[$]this" 81 | }], 82 | "no-useless-concat": 1, 83 | "no-whitespace-before-property": 1, 84 | "object-curly-spacing": ["warn", "never"], 85 | "object-property-newline": ["warn", { 86 | "allowMultiplePropertiesPerLine": false 87 | }], 88 | "one-var": ["warn", "always"], 89 | "radix": ["warn", "always"], 90 | "semi-spacing": ["warn", { 91 | "before": false, 92 | "after": true 93 | }], 94 | "space-before-blocks": ["warn", "always"], 95 | "space-before-function-paren": ["warn", "never"], 96 | "space-in-parens": ["warn", "never"], 97 | "space-infix-ops": 1, 98 | "space-unary-ops": ["warn", { 99 | "words": true, 100 | "nonwords": false 101 | }], 102 | "spaced-comment": ["warn", "always", { 103 | "exceptions": ["-", "+", "*", "=", "_"] 104 | }], 105 | "use-isnan": 1, 106 | "valid-typeof": 1, 107 | "wrap-iife": ["warn", "inside"] 108 | } 109 | }]; 110 | -------------------------------------------------------------------------------- /public/css/default/zebra_database.min.css: -------------------------------------------------------------------------------- 1 | #zdc{position:absolute;top:0;left:0;width:100%;font-family:Tahoma,Arial,Helvetica,sans-serif;font-size:13px;color:#222;text-align:left;padding-top:32px;z-index:9998}#zdc *{margin:0;padding:0;font-family:inherit;font-size:inherit;line-height:1.3}#zdc .zdc-visible{display:block!important}#zdc a{color:#000;text-decoration:none}#zdc ul{list-style-type:none;margin:0;padding:0;display:block;width:100%}#zdc ul:after{content:"";display:table;clear:both}#zdc ul li{float:left}#zdc ul li a{display:block;float:left}#zdc table{border-collapse:collapse}#zdc table td,#zdc table th{padding:4px;border:none;vertical-align:top}#zdc .zdc-entry{background:#9ca68f;border-top:1px solid #fff}#zdc .zdc-entry .zdc-counter{background:#69735b;color:#fff}#zdc .zdc-entry .zdc-counter.zdc-counter-ajax{background:#69735b;color:#cddc39}#zdc .zdc-entry.zdc-transaction .zdc-counter{background:#888;color:#fff}#zdc .zdc-entry.zdc-highlight .zdc-counter{background:#926492;color:#fff}#zdc .zdc-entry .zdc-data{width:100%;padding:5px}#zdc .zdc-entry .zdc-data .zdc-box{background:#eee;padding:8px}#zdc .zdc-entry .zdc-data .zdc-box.zdc-error{background:#c33;color:#fff;padding:5px}#zdc .zdc-entry .zdc-data .zdc-box.zdc-error a{text-decoration:underline;color:inherit}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions{background:#ccd1c6;padding:2px}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li{padding:5px 5px 5px 28px;background-repeat:no-repeat;background-position:5px center}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-time{background-image:url(clock.png)}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-ajax{background-color:#e63570;color:#fff;padding:5px;border-radius:5px;margin-right:2px}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-cache{background-image:url(cache.png);background-color:#cf9;color:#222;border-radius:5px;margin-right:2px}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-unbuffered{background-image:url(unbuffered.png);background-color:#ffc;color:#222;border-radius:5px;margin-right:2px}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-top{background-image:url(top.png)}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-close{background-image:url(close.png);background-position:5px 6px!important}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-records{background-image:url(records.png)}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-affected{background-image:url(affected.png)}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-explain{background-image:url(explain.png)}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-backtrace{background-image:url(backtrace.png)}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-database-manager{background-image:url(db-manager.png)}#zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li a{text-decoration:underline!important}#zdc .zdc-entry .zdc-data .zdc-box.zdc-data-table tr:nth-child(2n){background:#e0e0e0}#zdc .zdc-entry .zdc-data .zdc-box.zdc-data-table td,#zdc .zdc-entry .zdc-data .zdc-box.zdc-data-table th{border:1px solid #888}#zdc .zdc-entry .zdc-data .zdc-box.zdc-data-table th{background:#9ca68f;color:#f0f0f0}#zdc .zdc-entry .zdc-data .zdc-box pre{padding:8px}#zdc-main{position:fixed;top:0;left:0;background:#151c24;font-size:13px;display:none!important}#zdc-main a{border-right:1px solid #fff;color:#fff;padding:8px}#zdc-main a:hover{background:#3a4d63;color:#fff}#zdc-main span{background:orange;padding:2px 4px;font-weight:700;color:#000;margin:0 0 0 2px;border-radius:2px}#zdc-globals-submenu{background:#403d32}#zdc-globals-submenu a{padding:7px 8px 9px;color:#fff}#zdc-globals-submenu a:hover{background:#79735e;color:#fff}.zdc-syntax-highlight pre{line-height:1.3!important;background:#eee!important;border:transparent!important}.zdc-syntax-highlight .keyword{font-weight:700;color:#909;text-transform:uppercase}.zdc-syntax-highlight .string{font-weight:700;color:green}.zdc-syntax-highlight .symbol{font-weight:700;color:#f0f}.zdc-syntax-highlight .comment{color:teal}#zdc-mini{position:fixed;top:0;right:0;z-index:9999;font-family:Tahoma,Arial,Helvetica,sans-serif;font-size:13px;text-align:left;display:block}#zdc-mini a{background:url(db.png) 10px center no-repeat #151c24;display:block;padding:8px 8px 8px 35px;color:#fff;text-decoration:none;line-height:1.3}#zdc-mini a span{font-weight:700;background-color:#c33;margin:0 0 0 6px;padding:2px 7px;border-radius:2px}#zdc-globals-submenu,.zdc-backtrace-table,.zdc-entry,.zdc-explain-table,.zdc-records-table{display:none;overflow:auto} -------------------------------------------------------------------------------- /public/css/default/zebra_database.css: -------------------------------------------------------------------------------- 1 | #zdc { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | font-family: Tahoma,Arial,Helvetica,sans-serif; 7 | font-size: 13px; 8 | color: #222; 9 | text-align: left; 10 | padding-top: 32px; 11 | z-index: 9998 12 | } 13 | #zdc * { 14 | margin: 0; 15 | padding: 0; 16 | font-family: inherit; 17 | font-size: inherit; 18 | line-height: 1.3 19 | } 20 | #zdc .zdc-visible { 21 | display: block!important 22 | } 23 | #zdc a { 24 | color: #000; 25 | text-decoration: none 26 | } 27 | #zdc ul { 28 | list-style-type: none; 29 | margin: 0; 30 | padding: 0; 31 | display: block; 32 | width: 100% 33 | } 34 | #zdc ul:after { 35 | content: ""; 36 | display: table; 37 | clear: both 38 | } 39 | #zdc ul li { 40 | float: left 41 | } 42 | #zdc ul li a { 43 | display: block; 44 | float: left 45 | } 46 | #zdc table { 47 | border-collapse: collapse 48 | } 49 | #zdc table td, 50 | #zdc table th { 51 | padding: 4px; 52 | border: none; 53 | vertical-align: top 54 | } 55 | #zdc .zdc-entry { 56 | background: #9ca68f; 57 | border-top: 1px solid #fff 58 | } 59 | #zdc .zdc-entry .zdc-counter { 60 | background: #69735b; 61 | color: #fff 62 | } 63 | #zdc .zdc-entry .zdc-counter.zdc-counter-ajax { 64 | background: #69735b; 65 | color: #cddc39 66 | } 67 | #zdc .zdc-entry.zdc-transaction .zdc-counter { 68 | background: #888; 69 | color: #fff 70 | } 71 | #zdc .zdc-entry.zdc-highlight .zdc-counter { 72 | background: #926492; 73 | color: #fff 74 | } 75 | #zdc .zdc-entry .zdc-data { 76 | width: 100%; 77 | padding: 5px 78 | } 79 | #zdc .zdc-entry .zdc-data .zdc-box { 80 | background: #eee; 81 | padding: 8px 82 | } 83 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-error { 84 | background: #c33; 85 | color: #fff; 86 | padding: 5px 87 | } 88 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-error a { 89 | text-decoration: underline; 90 | color: inherit 91 | } 92 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions { 93 | background: #ced3c7; 94 | padding: 2px 95 | } 96 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li { 97 | padding: 5px 5px 5px 28px; 98 | background-repeat: no-repeat; 99 | background-position: 5px center 100 | } 101 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-time { 102 | background-image: url(clock.png) 103 | } 104 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-ajax { 105 | background-color: #e63570; 106 | color: #fff; 107 | padding: 5px; 108 | border-radius: 5px; 109 | margin-right: 2px 110 | } 111 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-cache { 112 | background-image: url(cache.png); 113 | background-color: #cf9; 114 | color: #222; 115 | border-radius: 5px; 116 | margin-right: 2px 117 | } 118 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-unbuffered { 119 | background-image: url(unbuffered.png); 120 | background-color: #ffc; 121 | color: #222; 122 | border-radius: 5px; 123 | margin-right: 2px 124 | } 125 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-top { 126 | background-image: url(top.png) 127 | } 128 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-close { 129 | background-image: url(close.png); 130 | background-position: 5px 6px!important 131 | } 132 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-records { 133 | background-image: url(records.png) 134 | } 135 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-affected { 136 | background-image: url(affected.png) 137 | } 138 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-explain { 139 | background-image: url(explain.png) 140 | } 141 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-backtrace { 142 | background-image: url(backtrace.png) 143 | } 144 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li.zdc-database-manager { 145 | background-image: url(db-manager.png) 146 | } 147 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-actions ul li a { 148 | text-decoration: underline!important 149 | } 150 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-data-table tr:nth-child(2n) { 151 | background: #e1e1e1 152 | } 153 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-data-table td, 154 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-data-table th { 155 | border: 1px solid #888 156 | } 157 | #zdc .zdc-entry .zdc-data .zdc-box.zdc-data-table th { 158 | background: #9ca68f; 159 | color: #f0f0f0 160 | } 161 | #zdc .zdc-entry .zdc-data .zdc-box pre { 162 | padding: 8px 163 | } 164 | #zdc-main { 165 | position: fixed; 166 | top: 0; 167 | left: 0; 168 | background: #151c24; 169 | font-size: 13px; 170 | display: none!important 171 | } 172 | #zdc-main a { 173 | border-right: 1px solid #fff; 174 | color: #fff; 175 | padding: 8px 176 | } 177 | #zdc-main a:hover { 178 | background: #3a4e64; 179 | color: #fff 180 | } 181 | #zdc-main span { 182 | background: orange; 183 | padding: 2px 4px; 184 | font-weight: 700; 185 | color: #000; 186 | margin: 0 0 0 2px; 187 | border-radius: 2px 188 | } 189 | #zdc-globals-submenu { 190 | background: #403d32 191 | } 192 | #zdc-globals-submenu a { 193 | padding: 7px 8px 9px; 194 | color: #fff 195 | } 196 | #zdc-globals-submenu a:hover { 197 | background: #79735e; 198 | color: #fff 199 | } 200 | .zdc-syntax-highlight pre { 201 | line-height: 1.3!important; 202 | background: #eee!important; 203 | border: transparent!important 204 | } 205 | .zdc-syntax-highlight .keyword { 206 | font-weight: 700; 207 | color: #909; 208 | text-transform: uppercase 209 | } 210 | .zdc-syntax-highlight .string { 211 | font-weight: 700; 212 | color: green 213 | } 214 | .zdc-syntax-highlight .symbol { 215 | font-weight: 700; 216 | color: #f0f 217 | } 218 | .zdc-syntax-highlight .comment { 219 | color: teal 220 | } 221 | #zdc-mini { 222 | position: fixed; 223 | top: 0; 224 | right: 0; 225 | z-index: 9999; 226 | font-family: Tahoma,Arial,Helvetica,sans-serif; 227 | font-size: 13px; 228 | text-align: left; 229 | display: block 230 | } 231 | #zdc-mini a { 232 | background: url(db.png) 10px center no-repeat #151c24; 233 | display: block; 234 | padding: 8px 8px 8px 35px; 235 | color: #fff; 236 | text-decoration: none; 237 | line-height: 1.3 238 | } 239 | #zdc-mini a span { 240 | font-weight: 700; 241 | background-color: #c33; 242 | margin: 0 0 0 6px; 243 | padding: 2px 7px; 244 | border-radius: 2px 245 | } 246 | #zdc-globals-submenu, 247 | .zdc-backtrace-table, 248 | .zdc-entry, 249 | .zdc-explain-table, 250 | .zdc-records-table { 251 | display: none; 252 | overflow: auto 253 | } -------------------------------------------------------------------------------- /languages/english.php: -------------------------------------------------------------------------------- 1 | . 5 | * 6 | * @version 1.0.1 7 | * @author Stefan Gabos%s
%s
%sverwendet werden.
2 |
3 | # Zebra Database [](https://twitter.com/intent/tweet?text=A+compact,+lightweight+and+feature-rich+PHP+MySQLi+database+wrapper&url=https://github.com/stefangabos/Zebra_Database&via=stefangabos&hashtags=php,mysqli,mysql)
4 |
5 | *A PHP MySQLi database wrapper that's lightweight, secure and feature-rich, providing methods for interacting with MySQL databases that are more secure, powerful and intuitive than PHP's default ones.*
6 |
7 | [](https://packagist.org/packages/stefangabos/zebra_database) [](https://packagist.org/packages/stefangabos/zebra_database) [](https://packagist.org/packages/stefangabos/zebra_database) [](https://packagist.org/packages/stefangabos/zebra_database) [](https://packagist.org/packages/stefangabos/zebra_database)
8 |
9 | Zebra_Database supports [transactions](https://dev.mysql.com/doc/refman/8.0/en/glossary.html#glos_transaction) and provides ways for caching query results either by saving cached data to the disk, in the session, by using [memcache](https://memcached.org/) or [redis](https://redis.io/).
10 |
11 | The library provides a comprehensive debugging interface with detailed information about the executed queries: execution time, returned/affected rows, excerpts of the found rows, error messages, backtrace information, etc. It can also automatically [EXPLAIN](https://dev.mysql.com/doc/refman/8.0/en/explain.html) SELECT queries *(so you don't miss those keys again!)*.
12 |
13 | Can provides debugging information when called from **CLI** (command-line interface) and supports logging queries done through **AJAX** requests.
14 |
15 | It encourages developers to write maintainable code and provides a better default security layer by encouraging the use of *prepared statements*, where parameters are automatically [escaped](https://www.php.net/manual/en/mysqli.real-escape-string.php).
16 |
17 | The code is heavily commented and generates no warnings/errors/notices when PHP's error reporting level is set to E_ALL.
18 |
19 | > tags
141 | public static $use_pre = true;
142 |
143 | // This flag tells us if SqlFormatted has been initialized
144 | protected static $init;
145 |
146 | // Regular expressions for tokenizing
147 | protected static $regex_boundaries;
148 | protected static $regex_reserved;
149 | protected static $regex_reserved_newline;
150 | protected static $regex_reserved_toplevel;
151 | protected static $regex_function;
152 |
153 | // Cache variables
154 | // Only tokens shorter than this size will be cached. Somewhere between 10 and 20 seems to work well for most cases.
155 | public static $max_cachekey_size = 15;
156 | protected static $token_cache = array();
157 | protected static $cache_hits = 0;
158 | protected static $cache_misses = 0;
159 |
160 | /**
161 | * Get stats about the token cache
162 | * @return Array An array containing the keys 'hits', 'misses', 'entries', and 'size' in bytes
163 | */
164 | public static function getCacheStats()
165 | {
166 | return array(
167 | 'hits'=>self::$cache_hits,
168 | 'misses'=>self::$cache_misses,
169 | 'entries'=>count(self::$token_cache),
170 | 'size'=>strlen(serialize(self::$token_cache))
171 | );
172 | }
173 |
174 | /**
175 | * Stuff that only needs to be done once. Builds regular expressions and sorts the reserved words.
176 | */
177 | protected static function init()
178 | {
179 | if (self::$init) return;
180 |
181 | // Sort reserved word list from longest word to shortest, 3x faster than usort
182 | $reservedMap = array_combine(self::$reserved, array_map('strlen', self::$reserved));
183 | arsort($reservedMap);
184 | self::$reserved = array_keys($reservedMap);
185 |
186 | // Set up regular expressions
187 | self::$regex_boundaries = '('.implode('|',array_map(array(__CLASS__, 'quote_regex'),self::$boundaries)).')';
188 | self::$regex_reserved = '('.implode('|',array_map(array(__CLASS__, 'quote_regex'),self::$reserved)).')';
189 | self::$regex_reserved_toplevel = str_replace(' ','\\s+','('.implode('|',array_map(array(__CLASS__, 'quote_regex'),self::$reserved_toplevel)).')');
190 | self::$regex_reserved_newline = str_replace(' ','\\s+','('.implode('|',array_map(array(__CLASS__, 'quote_regex'),self::$reserved_newline)).')');
191 |
192 | self::$regex_function = '('.implode('|',array_map(array(__CLASS__, 'quote_regex'),self::$functions)).')';
193 |
194 | self::$init = true;
195 | }
196 |
197 | /**
198 | * Return the next token and token type in a SQL string.
199 | * Quoted strings, comments, reserved words, whitespace, and punctuation are all their own tokens.
200 | *
201 | * @param String $string The SQL string
202 | * @param array $previous The result of the previous getNextToken() call
203 | *
204 | * @return Array An associative array containing the type and value of the token.
205 | */
206 | protected static function getNextToken($string, $previous = null)
207 | {
208 | // Whitespace
209 | if (preg_match('/^\s+/',$string,$matches)) {
210 | return array(
211 | self::TOKEN_VALUE => $matches[0],
212 | self::TOKEN_TYPE=>self::TOKEN_TYPE_WHITESPACE
213 | );
214 | }
215 |
216 | // Comment
217 | if ($string[0] === '#' || (isset($string[1])&&($string[0]==='-'&&$string[1]==='-') || ($string[0]==='/'&&$string[1]==='*'))) {
218 | // Comment until end of line
219 | if ($string[0] === '-' || $string[0] === '#') {
220 | $last = strpos($string, "\n");
221 | $type = self::TOKEN_TYPE_COMMENT;
222 | } else { // Comment until closing comment tag
223 | $last = strpos($string, "*/", 2) + 2;
224 | $type = self::TOKEN_TYPE_BLOCK_COMMENT;
225 | }
226 |
227 | if ($last === false) {
228 | $last = strlen($string);
229 | }
230 |
231 | return array(
232 | self::TOKEN_VALUE => substr($string, 0, $last),
233 | self::TOKEN_TYPE => $type
234 | );
235 | }
236 |
237 | // Quoted String
238 | if ($string[0]==='"' || $string[0]==='\'' || $string[0]==='`' || $string[0]==='[') {
239 | $return = array(
240 | self::TOKEN_TYPE => (($string[0]==='`' || $string[0]==='[')? self::TOKEN_TYPE_BACKTICK_QUOTE : self::TOKEN_TYPE_QUOTE),
241 | self::TOKEN_VALUE => self::getQuotedString($string)
242 | );
243 |
244 | return $return;
245 | }
246 |
247 | // User-defined Variable
248 | if (($string[0] === '@' || $string[0] === ':') && isset($string[1])) {
249 | $ret = array(
250 | self::TOKEN_VALUE => null,
251 | self::TOKEN_TYPE => self::TOKEN_TYPE_VARIABLE
252 | );
253 |
254 | // If the variable name is quoted
255 | if ($string[1]==='"' || $string[1]==='\'' || $string[1]==='`') {
256 | $ret[self::TOKEN_VALUE] = $string[0].self::getQuotedString(substr($string,1));
257 | }
258 | // Non-quoted variable name
259 | else {
260 | preg_match('/^('.$string[0].'[a-zA-Z0-9\._\$]+)/',$string,$matches);
261 | if ($matches) {
262 | $ret[self::TOKEN_VALUE] = $matches[1];
263 | }
264 | }
265 |
266 | if($ret[self::TOKEN_VALUE] !== null) return $ret;
267 | }
268 |
269 | // Number (decimal, binary, or hex)
270 | if (preg_match('/^([0-9]+(\.[0-9]+)?|0x[0-9a-fA-F]+|0b[01]+)($|\s|"\'`|'.self::$regex_boundaries.')/',$string,$matches)) {
271 | return array(
272 | self::TOKEN_VALUE => $matches[1],
273 | self::TOKEN_TYPE=>self::TOKEN_TYPE_NUMBER
274 | );
275 | }
276 |
277 | // Boundary Character (punctuation and symbols)
278 | if (preg_match('/^('.self::$regex_boundaries.')/',$string,$matches)) {
279 | return array(
280 | self::TOKEN_VALUE => $matches[1],
281 | self::TOKEN_TYPE => self::TOKEN_TYPE_BOUNDARY
282 | );
283 | }
284 |
285 | // A reserved word cannot be preceded by a '.'
286 | // this makes it so in "mytable.from", "from" is not considered a reserved word
287 | if (!$previous || !isset($previous[self::TOKEN_VALUE]) || $previous[self::TOKEN_VALUE] !== '.') {
288 | $upper = strtoupper($string);
289 | // Top Level Reserved Word
290 | if (preg_match('/^('.self::$regex_reserved_toplevel.')($|\s|'.self::$regex_boundaries.')/', $upper,$matches)) {
291 | return array(
292 | self::TOKEN_TYPE=>self::TOKEN_TYPE_RESERVED_TOPLEVEL,
293 | self::TOKEN_VALUE=>substr($string,0,strlen($matches[1]))
294 | );
295 | }
296 | // Newline Reserved Word
297 | if (preg_match('/^('.self::$regex_reserved_newline.')($|\s|'.self::$regex_boundaries.')/', $upper,$matches)) {
298 | return array(
299 | self::TOKEN_TYPE=>self::TOKEN_TYPE_RESERVED_NEWLINE,
300 | self::TOKEN_VALUE=>substr($string,0,strlen($matches[1]))
301 | );
302 | }
303 | // Other Reserved Word
304 | if (preg_match('/^('.self::$regex_reserved.')($|\s|'.self::$regex_boundaries.')/', $upper,$matches)) {
305 | return array(
306 | self::TOKEN_TYPE=>self::TOKEN_TYPE_RESERVED,
307 | self::TOKEN_VALUE=>substr($string,0,strlen($matches[1]))
308 | );
309 | }
310 | }
311 |
312 | // A function must be suceeded by '('
313 | // this makes it so "count(" is considered a function, but "count" alone is not
314 | $upper = strtoupper($string);
315 | // function
316 | if (preg_match('/^('.self::$regex_function.'[(]|\s|[)])/', $upper,$matches)) {
317 | return array(
318 | self::TOKEN_TYPE=>self::TOKEN_TYPE_RESERVED,
319 | self::TOKEN_VALUE=>substr($string,0,strlen($matches[1])-1)
320 | );
321 | }
322 |
323 | // Non reserved word
324 | preg_match('/^(.*?)($|\s|["\'`]|'.self::$regex_boundaries.')/',$string,$matches);
325 |
326 | return array(
327 | self::TOKEN_VALUE => $matches[1],
328 | self::TOKEN_TYPE => self::TOKEN_TYPE_WORD
329 | );
330 | }
331 |
332 | protected static function getQuotedString($string)
333 | {
334 | $ret = null;
335 |
336 | // This checks for the following patterns:
337 | // 1. backtick quoted string using `` to escape
338 | // 2. square bracket quoted string (SQL Server) using ]] to escape
339 | // 3. double quoted string using "" or \" to escape
340 | // 4. single quoted string using '' or \' to escape
341 | if ( preg_match('/^(((`[^`]*($|`))+)|((\[[^\]]*($|\]))(\][^\]]*($|\]))*)|(("[^"\\\\]*(?:\\\\.[^"\\\\]*)*("|$))+)|((\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*(\'|$))+))/s', $string, $matches)) {
342 | $ret = $matches[1];
343 | }
344 |
345 | return $ret;
346 | }
347 |
348 | /**
349 | * Takes a SQL string and breaks it into tokens.
350 | * Each token is an associative array with type and value.
351 | *
352 | * @param String $string The SQL string
353 | *
354 | * @return Array An array of tokens.
355 | */
356 | protected static function tokenize($string)
357 | {
358 | self::init();
359 |
360 | $tokens = array();
361 |
362 | // Used for debugging if there is an error while tokenizing the string
363 | $original_length = strlen($string);
364 |
365 | // Used to make sure the string keeps shrinking on each iteration
366 | $old_string_len = strlen($string) + 1;
367 |
368 | $token = null;
369 |
370 | $current_length = strlen($string);
371 |
372 | // Keep processing the string until it is empty
373 | while ($current_length) {
374 | // If the string stopped shrinking, there was a problem
375 | if ($old_string_len <= $current_length) {
376 | $tokens[] = array(
377 | self::TOKEN_VALUE=>$string,
378 | self::TOKEN_TYPE=>self::TOKEN_TYPE_ERROR
379 | );
380 |
381 | return $tokens;
382 | }
383 | $old_string_len = $current_length;
384 |
385 | // Determine if we can use caching
386 | if ($current_length >= self::$max_cachekey_size) {
387 | $cacheKey = substr($string,0,self::$max_cachekey_size);
388 | } else {
389 | $cacheKey = false;
390 | }
391 |
392 | // See if the token is already cached
393 | if ($cacheKey && isset(self::$token_cache[$cacheKey])) {
394 | // Retrieve from cache
395 | $token = self::$token_cache[$cacheKey];
396 | $token_length = strlen($token[self::TOKEN_VALUE]);
397 | self::$cache_hits++;
398 | } else {
399 | // Get the next token and the token type
400 | $token = self::getNextToken($string, $token);
401 | $token_length = strlen($token[self::TOKEN_VALUE]);
402 | self::$cache_misses++;
403 |
404 | // If the token is shorter than the max length, store it in cache
405 | if ($cacheKey && $token_length < self::$max_cachekey_size) {
406 | self::$token_cache[$cacheKey] = $token;
407 | }
408 | }
409 |
410 | $tokens[] = $token;
411 |
412 | // Advance the string
413 | $string = substr($string, $token_length);
414 |
415 | $current_length -= $token_length;
416 | }
417 |
418 | return $tokens;
419 | }
420 |
421 | /**
422 | * Format the whitespace in a SQL string to make it easier to read.
423 | *
424 | * @param String $string The SQL string
425 | * @param boolean $highlight If true, syntax highlighting will also be performed
426 | *
427 | * @return String The SQL string with HTML styles and formatting wrapped in a tag
428 | */
429 | public static function format($string, $highlight=true)
430 | {
431 | // This variable will be populated with formatted html
432 | $return = '';
433 |
434 | // Use an actual tab while formatting and then switch out with self::$tab at the end
435 | $tab = "\t";
436 |
437 | $indent_level = 0;
438 | $newline = false;
439 | $inline_parentheses = false;
440 | $increase_special_indent = false;
441 | $increase_block_indent = false;
442 | $indent_types = array();
443 | $added_newline = false;
444 | $inline_count = 0;
445 | $inline_indented = false;
446 | $clause_limit = false;
447 |
448 | // Tokenize String
449 | $original_tokens = self::tokenize($string);
450 |
451 | // Remove existing whitespace
452 | $tokens = array();
453 | foreach ($original_tokens as $i=>$token) {
454 | if ($token[self::TOKEN_TYPE] !== self::TOKEN_TYPE_WHITESPACE) {
455 | $token['i'] = $i;
456 | $tokens[] = $token;
457 | }
458 | }
459 |
460 | // Format token by token
461 | foreach ($tokens as $i=>$token) {
462 | // Get highlighted token if doing syntax highlighting
463 | if ($highlight) {
464 | $highlighted = self::highlightToken($token);
465 | } else { // If returning raw text
466 | $highlighted = $token[self::TOKEN_VALUE];
467 | }
468 |
469 | // If we are increasing the special indent level now
470 | if ($increase_special_indent) {
471 | $indent_level++;
472 | $increase_special_indent = false;
473 | array_unshift($indent_types,'special');
474 | }
475 | // If we are increasing the block indent level now
476 | if ($increase_block_indent) {
477 | $indent_level++;
478 | $increase_block_indent = false;
479 | array_unshift($indent_types,'block');
480 | }
481 |
482 | // If we need a new line before the token
483 | if ($newline) {
484 | $return .= "\n" . str_repeat($tab, $indent_level);
485 | $newline = false;
486 | $added_newline = true;
487 | } else {
488 | $added_newline = false;
489 | }
490 |
491 | // Display comments directly where they appear in the source
492 | if ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_COMMENT || $token[self::TOKEN_TYPE] === self::TOKEN_TYPE_BLOCK_COMMENT) {
493 | if ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_BLOCK_COMMENT) {
494 | $indent = str_repeat($tab,$indent_level);
495 | $return .= "\n" . $indent;
496 | $highlighted = str_replace("\n","\n".$indent,$highlighted);
497 | }
498 |
499 | $return .= $highlighted;
500 | $newline = true;
501 | continue;
502 | }
503 |
504 | if ($inline_parentheses) {
505 | // End of inline parentheses
506 | if ($token[self::TOKEN_VALUE] === ')') {
507 | $return = rtrim($return,' ');
508 |
509 | if ($inline_indented) {
510 | array_shift($indent_types);
511 | $indent_level --;
512 | $return .= "\n" . str_repeat($tab, $indent_level);
513 | }
514 |
515 | $inline_parentheses = false;
516 |
517 | $return .= $highlighted . ' ';
518 | continue;
519 | }
520 |
521 | if ($token[self::TOKEN_VALUE] === ',') {
522 | if ($inline_count >= 30) {
523 | $inline_count = 0;
524 | $newline = true;
525 | }
526 | }
527 |
528 | $inline_count += strlen($token[self::TOKEN_VALUE]);
529 | }
530 |
531 | // Opening parentheses increase the block indent level and start a new line
532 | if ($token[self::TOKEN_VALUE] === '(') {
533 | // First check if this should be an inline parentheses block
534 | // Examples are "NOW()", "COUNT(*)", "int(10)", key(`somecolumn`), DECIMAL(7,2)
535 | // Allow up to 3 non-whitespace tokens inside inline parentheses
536 | $length = 0;
537 | for ($j=1;$j<=250;$j++) {
538 | // Reached end of string
539 | if (!isset($tokens[$i+$j])) break;
540 |
541 | $next = $tokens[$i+$j];
542 |
543 | // Reached closing parentheses, able to inline it
544 | if ($next[self::TOKEN_VALUE] === ')') {
545 | $inline_parentheses = true;
546 | $inline_count = 0;
547 | $inline_indented = false;
548 | break;
549 | }
550 |
551 | // Reached an invalid token for inline parentheses
552 | if ($next[self::TOKEN_VALUE]===';' || $next[self::TOKEN_VALUE]==='(') {
553 | break;
554 | }
555 |
556 | // Reached an invalid token type for inline parentheses
557 | if ($next[self::TOKEN_TYPE]===self::TOKEN_TYPE_RESERVED_TOPLEVEL || $next[self::TOKEN_TYPE]===self::TOKEN_TYPE_RESERVED_NEWLINE || $next[self::TOKEN_TYPE]===self::TOKEN_TYPE_COMMENT || $next[self::TOKEN_TYPE]===self::TOKEN_TYPE_BLOCK_COMMENT) {
558 | break;
559 | }
560 |
561 | $length += strlen($next[self::TOKEN_VALUE]);
562 | }
563 |
564 | if ($inline_parentheses && $length > 30) {
565 | $increase_block_indent = true;
566 | $inline_indented = true;
567 | $newline = true;
568 | }
569 |
570 | // Take out the preceding space unless there was whitespace there in the original query
571 | if (isset($original_tokens[$token['i']-1]) && $original_tokens[$token['i']-1][self::TOKEN_TYPE] !== self::TOKEN_TYPE_WHITESPACE) {
572 | $return = rtrim($return,' ');
573 | }
574 |
575 | if (!$inline_parentheses) {
576 | $increase_block_indent = true;
577 | // Add a newline after the parentheses
578 | $newline = true;
579 | }
580 |
581 | }
582 |
583 | // Closing parentheses decrease the block indent level
584 | elseif ($token[self::TOKEN_VALUE] === ')') {
585 | // Remove whitespace before the closing parentheses
586 | $return = rtrim($return,' ');
587 |
588 | $indent_level--;
589 |
590 | // Reset indent level
591 | while ($j=array_shift($indent_types)) {
592 | if ($j==='special') {
593 | $indent_level--;
594 | } else {
595 | break;
596 | }
597 | }
598 |
599 | if ($indent_level < 0) {
600 | // This is an error
601 | $indent_level = 0;
602 |
603 | if ($highlight) {
604 | $return .= "\n".self::highlightError($token[self::TOKEN_VALUE]);
605 | continue;
606 | }
607 | }
608 |
609 | // Add a newline before the closing parentheses (if not already added)
610 | if (!$added_newline) {
611 | $return .= "\n" . str_repeat($tab, $indent_level);
612 | }
613 | }
614 |
615 | // Top level reserved words start a new line and increase the special indent level
616 | elseif ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_RESERVED_TOPLEVEL) {
617 | $increase_special_indent = true;
618 |
619 | // If the last indent type was 'special', decrease the special indent for this round
620 | reset($indent_types);
621 | if (current($indent_types)==='special') {
622 | $indent_level--;
623 | array_shift($indent_types);
624 | }
625 |
626 | // Add a newline after the top level reserved word
627 | $newline = true;
628 | // Add a newline before the top level reserved word (if not already added)
629 | if (!$added_newline) {
630 | $return .= "\n" . str_repeat($tab, $indent_level);
631 | }
632 | // If we already added a newline, redo the indentation since it may be different now
633 | else {
634 | $return = rtrim($return,$tab).str_repeat($tab, $indent_level);
635 | }
636 |
637 | // If the token may have extra whitespace
638 | if (strpos($token[self::TOKEN_VALUE],' ')!==false || strpos($token[self::TOKEN_VALUE],"\n")!==false || strpos($token[self::TOKEN_VALUE],"\t")!==false) {
639 | $highlighted = preg_replace('/\s+/',' ',$highlighted);
640 | }
641 | //if SQL 'LIMIT' clause, start variable to reset newline
642 | if ($token[self::TOKEN_VALUE] === 'LIMIT' && !$inline_parentheses) {
643 | $clause_limit = true;
644 | }
645 | }
646 |
647 | // Checks if we are out of the limit clause
648 | elseif ($clause_limit && $token[self::TOKEN_VALUE] !== "," && $token[self::TOKEN_TYPE] !== self::TOKEN_TYPE_NUMBER && $token[self::TOKEN_TYPE] !== self::TOKEN_TYPE_WHITESPACE) {
649 | $clause_limit = false;
650 | }
651 |
652 | // Commas start a new line (unless within inline parentheses or SQL 'LIMIT' clause)
653 | elseif ($token[self::TOKEN_VALUE] === ',' && !$inline_parentheses) {
654 | //If the previous TOKEN_VALUE is 'LIMIT', resets new line
655 | if ($clause_limit === true) {
656 | $newline = false;
657 | $clause_limit = false;
658 | }
659 | // All other cases of commas
660 | else {
661 | $newline = true;
662 | }
663 | }
664 |
665 | // Newline reserved words start a new line
666 | elseif ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_RESERVED_NEWLINE) {
667 | // Add a newline before the reserved word (if not already added)
668 | if (!$added_newline) {
669 | $return .= "\n" . str_repeat($tab, $indent_level);
670 | }
671 |
672 | // If the token may have extra whitespace
673 | if (strpos($token[self::TOKEN_VALUE],' ')!==false || strpos($token[self::TOKEN_VALUE],"\n")!==false || strpos($token[self::TOKEN_VALUE],"\t")!==false) {
674 | $highlighted = preg_replace('/\s+/',' ',$highlighted);
675 | }
676 | }
677 |
678 | // Multiple boundary characters in a row should not have spaces between them (not including parentheses)
679 | elseif ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_BOUNDARY) {
680 | if (isset($tokens[$i-1]) && $tokens[$i-1][self::TOKEN_TYPE] === self::TOKEN_TYPE_BOUNDARY) {
681 | if (isset($original_tokens[$token['i']-1]) && $original_tokens[$token['i']-1][self::TOKEN_TYPE] !== self::TOKEN_TYPE_WHITESPACE) {
682 | $return = rtrim($return,' ');
683 | }
684 | }
685 | }
686 |
687 | // If the token shouldn't have a space before it
688 | if ($token[self::TOKEN_VALUE] === '.' || $token[self::TOKEN_VALUE] === ',' || $token[self::TOKEN_VALUE] === ';') {
689 | $return = rtrim($return, ' ');
690 | }
691 |
692 | $return .= $highlighted.' ';
693 |
694 | // If the token shouldn't have a space after it
695 | if ($token[self::TOKEN_VALUE] === '(' || $token[self::TOKEN_VALUE] === '.') {
696 | $return = rtrim($return,' ');
697 | }
698 |
699 | // If this is the "-" of a negative number, it shouldn't have a space after it
700 | if($token[self::TOKEN_VALUE] === '-' && isset($tokens[$i+1]) && $tokens[$i+1][self::TOKEN_TYPE] === self::TOKEN_TYPE_NUMBER && isset($tokens[$i-1])) {
701 | $prev = $tokens[$i-1][self::TOKEN_TYPE];
702 | if($prev !== self::TOKEN_TYPE_QUOTE && $prev !== self::TOKEN_TYPE_BACKTICK_QUOTE && $prev !== self::TOKEN_TYPE_WORD && $prev !== self::TOKEN_TYPE_NUMBER) {
703 | $return = rtrim($return,' ');
704 | }
705 | }
706 | }
707 |
708 | // If there are unmatched parentheses
709 | if ($highlight && array_search('block',$indent_types) !== false) {
710 | $return .= "\n".self::highlightError("WARNING: unclosed parentheses or section");
711 | }
712 |
713 | // Replace tab characters with the configuration tab character
714 | $return = trim(str_replace("\t",self::$tab,$return));
715 |
716 | if ($highlight) {
717 | $return = self::output($return);
718 | }
719 |
720 | return $return;
721 | }
722 |
723 | /**
724 | * Add syntax highlighting to a SQL string
725 | *
726 | * @param String $string The SQL string
727 | *
728 | * @return String The SQL string with HTML styles applied
729 | */
730 | public static function highlight($string)
731 | {
732 | $tokens = self::tokenize($string);
733 |
734 | $return = '';
735 |
736 | foreach ($tokens as $token) {
737 | $return .= self::highlightToken($token);
738 | }
739 |
740 | return self::output($return);
741 | }
742 |
743 | /**
744 | * Split a SQL string into multiple queries.
745 | * Uses ";" as a query delimiter.
746 | *
747 | * @param String $string The SQL string
748 | *
749 | * @return Array An array of individual query strings without trailing semicolons
750 | */
751 | public static function splitQuery($string)
752 | {
753 | $queries = array();
754 | $current_query = '';
755 | $empty = true;
756 |
757 | $tokens = self::tokenize($string);
758 |
759 | foreach ($tokens as $token) {
760 | // If this is a query separator
761 | if ($token[self::TOKEN_VALUE] === ';') {
762 | if (!$empty) {
763 | $queries[] = $current_query.';';
764 | }
765 | $current_query = '';
766 | $empty = true;
767 | continue;
768 | }
769 |
770 | // If this is a non-empty character
771 | if ($token[self::TOKEN_TYPE] !== self::TOKEN_TYPE_WHITESPACE && $token[self::TOKEN_TYPE] !== self::TOKEN_TYPE_COMMENT && $token[self::TOKEN_TYPE] !== self::TOKEN_TYPE_BLOCK_COMMENT) {
772 | $empty = false;
773 | }
774 |
775 | $current_query .= $token[self::TOKEN_VALUE];
776 | }
777 |
778 | if (!$empty) {
779 | $queries[] = trim($current_query);
780 | }
781 |
782 | return $queries;
783 | }
784 |
785 | /**
786 | * Remove all comments from a SQL string
787 | *
788 | * @param String $string The SQL string
789 | *
790 | * @return String The SQL string without comments
791 | */
792 | public static function removeComments($string)
793 | {
794 | $result = '';
795 |
796 | $tokens = self::tokenize($string);
797 |
798 | foreach ($tokens as $token) {
799 | // Skip comment tokens
800 | if ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_COMMENT || $token[self::TOKEN_TYPE] === self::TOKEN_TYPE_BLOCK_COMMENT) {
801 | continue;
802 | }
803 |
804 | $result .= $token[self::TOKEN_VALUE];
805 | }
806 | $result = self::format( $result,false);
807 |
808 | return $result;
809 | }
810 |
811 | /**
812 | * Compress a query by collapsing white space and removing comments
813 | *
814 | * @param String $string The SQL string
815 | *
816 | * @return String The SQL string without comments
817 | */
818 | public static function compress($string)
819 | {
820 | $result = '';
821 |
822 | $tokens = self::tokenize($string);
823 |
824 | $whitespace = true;
825 | foreach ($tokens as $token) {
826 | // Skip comment tokens
827 | if ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_COMMENT || $token[self::TOKEN_TYPE] === self::TOKEN_TYPE_BLOCK_COMMENT) {
828 | continue;
829 | }
830 | // Remove extra whitespace in reserved words (e.g "OUTER JOIN" becomes "OUTER JOIN")
831 | elseif ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_RESERVED || $token[self::TOKEN_TYPE] === self::TOKEN_TYPE_RESERVED_NEWLINE || $token[self::TOKEN_TYPE] === self::TOKEN_TYPE_RESERVED_TOPLEVEL) {
832 | $token[self::TOKEN_VALUE] = preg_replace('/\s+/',' ',$token[self::TOKEN_VALUE]);
833 | }
834 |
835 | if ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_WHITESPACE) {
836 | // If the last token was whitespace, don't add another one
837 | if ($whitespace) {
838 | continue;
839 | } else {
840 | $whitespace = true;
841 | // Convert all whitespace to a single space
842 | $token[self::TOKEN_VALUE] = ' ';
843 | }
844 | } else {
845 | $whitespace = false;
846 | }
847 |
848 | $result .= $token[self::TOKEN_VALUE];
849 | }
850 |
851 | return rtrim($result);
852 | }
853 |
854 | /**
855 | * Highlights a token depending on its type.
856 | *
857 | * @param Array $token An associative array containing type and value.
858 | *
859 | * @return String HTML code of the highlighted token.
860 | */
861 | protected static function highlightToken($token)
862 | {
863 | $type = $token[self::TOKEN_TYPE];
864 |
865 | if (self::is_cli()) {
866 | $token = $token[self::TOKEN_VALUE];
867 | } else {
868 | if (defined('ENT_IGNORE')) {
869 | $token = htmlentities($token[self::TOKEN_VALUE],ENT_COMPAT | ENT_IGNORE ,'UTF-8');
870 | } else {
871 | $token = htmlentities($token[self::TOKEN_VALUE],ENT_COMPAT,'UTF-8');
872 | }
873 | }
874 |
875 | if ($type===self::TOKEN_TYPE_BOUNDARY) {
876 | return self::highlightBoundary($token);
877 | } elseif ($type===self::TOKEN_TYPE_WORD) {
878 | return self::highlightWord($token);
879 | } elseif ($type===self::TOKEN_TYPE_BACKTICK_QUOTE) {
880 | return self::highlightBacktickQuote($token);
881 | } elseif ($type===self::TOKEN_TYPE_QUOTE) {
882 | return self::highlightQuote($token);
883 | } elseif ($type===self::TOKEN_TYPE_RESERVED) {
884 | return self::highlightReservedWord($token);
885 | } elseif ($type===self::TOKEN_TYPE_RESERVED_TOPLEVEL) {
886 | return self::highlightReservedWord($token);
887 | } elseif ($type===self::TOKEN_TYPE_RESERVED_NEWLINE) {
888 | return self::highlightReservedWord($token);
889 | } elseif ($type===self::TOKEN_TYPE_NUMBER) {
890 | return self::highlightNumber($token);
891 | } elseif ($type===self::TOKEN_TYPE_VARIABLE) {
892 | return self::highlightVariable($token);
893 | } elseif ($type===self::TOKEN_TYPE_COMMENT || $type===self::TOKEN_TYPE_BLOCK_COMMENT) {
894 | return self::highlightComment($token);
895 | }
896 |
897 | return $token;
898 | }
899 |
900 | /**
901 | * Highlights a quoted string
902 | *
903 | * @param String $value The token's value
904 | *
905 | * @return String HTML code of the highlighted token.
906 | */
907 | protected static function highlightQuote($value)
908 | {
909 | if (self::is_cli()) {
910 | return self::$cli_quote . $value . "\x1b[0m";
911 | } else {
912 | return '' . $value . '';
913 | }
914 | }
915 |
916 | /**
917 | * Highlights a backtick quoted string
918 | *
919 | * @param String $value The token's value
920 | *
921 | * @return String HTML code of the highlighted token.
922 | */
923 | protected static function highlightBacktickQuote($value)
924 | {
925 | if (self::is_cli()) {
926 | return self::$cli_backtick_quote . $value . "\x1b[0m";
927 | } else {
928 | return '' . $value . '';
929 | }
930 | }
931 |
932 | /**
933 | * Highlights a reserved word
934 | *
935 | * @param String $value The token's value
936 | *
937 | * @return String HTML code of the highlighted token.
938 | */
939 | protected static function highlightReservedWord($value)
940 | {
941 | if (self::is_cli()) {
942 | return self::$cli_reserved . $value . "\x1b[0m";
943 | } else {
944 | return '' . $value . '';
945 | }
946 | }
947 |
948 | /**
949 | * Highlights a boundary token
950 | *
951 | * @param String $value The token's value
952 | *
953 | * @return String HTML code of the highlighted token.
954 | */
955 | protected static function highlightBoundary($value)
956 | {
957 | if ($value==='(' || $value===')') return $value;
958 |
959 | if (self::is_cli()) {
960 | return self::$cli_boundary . $value . "\x1b[0m";
961 | } else {
962 | return '' . $value . '';
963 | }
964 | }
965 |
966 | /**
967 | * Highlights a number
968 | *
969 | * @param String $value The token's value
970 | *
971 | * @return String HTML code of the highlighted token.
972 | */
973 | protected static function highlightNumber($value)
974 | {
975 | if (self::is_cli()) {
976 | return self::$cli_number . $value . "\x1b[0m";
977 | } else {
978 | return '' . $value . '';
979 | }
980 | }
981 |
982 | /**
983 | * Highlights an error
984 | *
985 | * @param String $value The token's value
986 | *
987 | * @return String HTML code of the highlighted token.
988 | */
989 | protected static function highlightError($value)
990 | {
991 | if (self::is_cli()) {
992 | return self::$cli_error . $value . "\x1b[0m";
993 | } else {
994 | return '' . $value . '';
995 | }
996 | }
997 |
998 | /**
999 | * Highlights a comment
1000 | *
1001 | * @param String $value The token's value
1002 | *
1003 | * @return String HTML code of the highlighted token.
1004 | */
1005 | protected static function highlightComment($value)
1006 | {
1007 | if (self::is_cli()) {
1008 | return self::$cli_comment . $value . "\x1b[0m";
1009 | } else {
1010 | return '' . $value . '';
1011 | }
1012 | }
1013 |
1014 | /**
1015 | * Highlights a word token
1016 | *
1017 | * @param String $value The token's value
1018 | *
1019 | * @return String HTML code of the highlighted token.
1020 | */
1021 | protected static function highlightWord($value)
1022 | {
1023 | if (self::is_cli()) {
1024 | return self::$cli_word . $value . "\x1b[0m";
1025 | } else {
1026 | return '' . $value . '';
1027 | }
1028 | }
1029 |
1030 | /**
1031 | * Highlights a variable token
1032 | *
1033 | * @param String $value The token's value
1034 | *
1035 | * @return String HTML code of the highlighted token.
1036 | */
1037 | protected static function highlightVariable($value)
1038 | {
1039 | if (self::is_cli()) {
1040 | return self::$cli_variable . $value . "\x1b[0m";
1041 | } else {
1042 | return '' . $value . '';
1043 | }
1044 | }
1045 |
1046 | /**
1047 | * Helper function for building regular expressions for reserved words and boundary characters
1048 | *
1049 | * @param String $a The string to be quoted
1050 | *
1051 | * @return String The quoted string
1052 | */
1053 | private static function quote_regex($a)
1054 | {
1055 | return preg_quote($a,'/');
1056 | }
1057 |
1058 | /**
1059 | * Helper function for building string output
1060 | *
1061 | * @param String $string The string to be quoted
1062 | *
1063 | * @return String The quoted string
1064 | */
1065 | private static function output($string)
1066 | {
1067 | if (self::is_cli()) {
1068 | return $string."\n";
1069 | } else {
1070 | $string=trim($string);
1071 | if (!self::$use_pre) {
1072 | return $string;
1073 | }
1074 |
1075 | return '' . $string . '
';
1076 | }
1077 | }
1078 |
1079 | private static function is_cli()
1080 | {
1081 | if (isset(self::$cli)) return self::$cli;
1082 | else return php_sapi_name() === 'cli';
1083 | }
1084 |
1085 | }
1086 |
--------------------------------------------------------------------------------