├── 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 | Zebra_Database Example 6 | 7 | 8 | 9 |

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 |

15 | 16 |

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 8 | * 9 | */ 10 | 11 | $this->language = array( 12 | 13 | 'affected_rows' => 'affected rows', 14 | 'backtrace' => 'backtrace', 15 | 'cache_path_not_writable' => 'Could not cache query. Make sure path exists and is writable.', 16 | 'cannot_use_parameter_marker' => 'Cannot use a parameter marker ("?", question mark) in

%s

Use an actual value instead as it will be automatically escaped.', 17 | 'close_all' => 'close all', 18 | 'could_not_connect_to_database' => 'Could not connect to database', 19 | 'could_not_connect_to_memcache_server' => 'Could not connect to the memcache server', 20 | 'could_not_connect_to_redis_server' => 'Could not connect to the redis server', 21 | 'could_not_seek' => 'could not seek to specified row', 22 | 'could_not_select_database' => 'Could not select database', 23 | 'could_not_write_to_log' => 'Could not write to log file. Make sure the folder exists and is writable', 24 | 'date' => 'Date', 25 | 'email_subject' => 'Slow query on %s!', 26 | 'email_content' => "The following query exceeded normal running time of %s seconds by running %s seconds: \n\n %s", 27 | 'error' => 'Error', 28 | 'errors' => 'errors', 29 | 'execution_time' => 'duration', 30 | 'explain' => 'explain', 31 | 'data_not_an_array' => 'The third argument of insert_bulk() needs to be an array of arrays.', 32 | 'file' => 'File', 33 | 'file_could_not_be_opened' => 'Could not open file', 34 | 'from_cache' => 'from cache', 35 | 'function' => 'Function', 36 | 'globals' => 'globals', 37 | 'invalid_option' => '%s is an invalid connection option', 38 | 'line' => 'Line', 39 | 'memcache_extension_not_installed' => 'Memcache extension not found.
40 | For using memcache as caching method, PHP version must be 4.3.3+, must be compiled with the 41 | memcached extension, and needs to be 42 | configured with --with-zlib[=DIR].', 43 | 'redis_extension_not_installed' => 'Redis extension not found.
44 | For using redis as caching method, PHP version must be 4.3.3+, must be compiled with the 45 | reids extension, and needs to be 46 | configured with --with-zlib[=DIR].', 47 | 'milliseconds' => 'ms', 48 | 'mysql_error' => 'MySQL error', 49 | 'no' => 'No', 50 | 'no_active_session' => 'You have chosen to cache query results in sessions but there are no active session. Call session_start() before using the library!', 51 | 'no_transaction_in_progress' => 'No transaction in progress.', 52 | 'not_a_valid_resource' => 'Not a valid resource (make sure you specify a resource as argument for fetch_assoc()/fetch_obj() if you are executing a query inside the loop)', 53 | 'optimization_needed' => 'WARNING: The first few results returned by this query are the same as returned by %s other queries!', 54 | 'options_before_connect' => 'The "option" method must be called before connecting to a MySQL server', 55 | 'returned_rows' => 'returned rows', 56 | 'seconds' => 'seconds', 57 | 'successful_queries' => 'successful queries', 58 | 'to_top' => 'to the top', 59 | 'transaction_in_progress' => 'Transaction could not be started as another transaction is in progress.', 60 | 'unbuffered' => 'unbuffered', 61 | 'unbuffered_queries_cannot_be_cached' => 'Unbuffered queries cannot be cached', 62 | 'unsuccessful_queries' => 'unsuccessful queries', 63 | 'unusable_method_unbuffered_queries' => '%s() method cannot be used with unbuffered queries', 64 | 'warning_charset' => 'No default charset and collation were set. Call set_charset() after connecting to the database.', 65 | 'warning_memcache' => 'The "memcache" extension is available on your server - consider using memcache for caching query results.
See the documentation for more information.', 66 | 'warning_redis' => 'The "redis" extension is available on your server - consider using redis for caching query results.
See the documentation for more information.', 67 | 'warning_replacements_not_array' => '$replacements must be an arrays of values', 68 | 'warning_replacements_wrong_number' => 'the number of items to replace is different than the number of items in the $replacements array', 69 | 'warning_wait_timeout' => 'The value of MySQL\'s wait_timeout variable is set to %s. The wait_timeout variable represents the time, in seconds, that MySQL will wait before killing an idle connection. After a script finishes execution, the MySQL connection is not actually terminated but it is put in an idle state and is being reused if the same user requires a database connection (a very common scenario is when users navigate through the pages of a website). The default value of wait_timeout is 28800 seconds, or 8 hours. If you have lots of visitors this can lead to a Too many connections error, as eventualy there will be times when no free connections will be available. The recommended value is 300 seconds (5 minutes).', 70 | 'warning' => 'Warning', 71 | 'warnings' => 'warnings', 72 | 'yes' => 'Yes', 73 | 74 | ); 75 | 76 | ?> 77 | -------------------------------------------------------------------------------- /public/javascript/zebra_database.min.js: -------------------------------------------------------------------------------- 1 | "undefined"==typeof _$&&(!function(){"use strict";function a(t,e,s){var n,c=[];if("string"==typeof(t="string"==typeof t&&"body"===t.toLocaleLowerCase()?document.body:t))if(0===t.indexOf("<")&&1")&&2 li a").on("click",function(){window.scrollTo({left:0,top:0})}),v(document).on("click",function(t){var e,s,n;(t.target.matches(".zdc-toggle")||t.target.matches(".zdc-toggle *"))&&(e=(t=t.target.matches(".zdc-toggle")?v(t.target):v(t.target.parentNode)).attr("class").replace(/^.*?zdc\-toggle/,"").replace(/\zdc\-toggle\-id/,"").trim(),s=v("#"+e),t.hasClass("zdc-toggle-id")||!s.length?(n=(s=s.length?s:v("div."+e,t.closest(".zdc-data"))).hasClass("zdc-visible"),(t.attr("class").match(/zdc\-globals\-/)&&!t.attr("class").match(/zdc\-globals\-submenu/)?v("#zdc .zdc-visible").not("#zdc-globals-submenu"):s.hasClass("zdc-data-table")?v("#zdc .zdc-data-table.zdc-visible"):v("#zdc .zdc-visible")).not("#zdc-main").removeClass("zdc-visible")):(n=v(".zdc-entry.zdc-visible",s).length,v("#zdc .zdc-visible").not("#zdc-main").removeClass("zdc-visible"),s=v(".zdc-entry",s)),n?s.removeClass("zdc-visible"):s.addClass("zdc-visible"))}),void 0!==window.zdb_log_path&&(t=XMLHttpRequest,i=t.prototype.open,r=t.prototype.send,t.prototype.open=function(t,e,s,n,c){this._url=e,i.call(this,t,e,s,n,c)},t.prototype.send=function(t){for(var e,s=this,n="0123456789",c="",i=0;i<12;i++)c+=n.charAt(Math.floor(Math.random()*n.length));function o(){var y;4===s.readyState&&200===s.status&&-1===s._url.indexOf(window.zdb_log_path)?((y=new XMLHttpRequest).open("GET",window.zdb_log_path+"?id="+c,!0),y.onreadystatechange=function(){var t,e,s,n,c,i,o,r,a,l,d,u,f,h,z,p=0,m=["errors","successful","unsuccessful"];if(4===y.readyState&&200===y.status&&-1parseInt(t.trim(),10)),r=d.textContent||0),e=v(("successful"===m[o]?t.match(/
([\s\S]*)<\/div class=\"zdc\-end\">/g):"unsuccessful"===m[o]?t.match(/
([\s\S]*)<\/div class=\"zdc\-end\">/g):t.match(/
([\s\S]*)<\/div class=\"zdc\-end\">/g))[0]),"successful"===m[o]&&v(".zdc-time",e).each(function(){p+=parseFloat(this.textContent.match(/^.*?\:\s([0-9\.]+)/)[1])}),z.html((parseFloat(z[0].textContent)+p).toFixed(5)),c=v("errors"===m[o]?"#zdc-"+m[o]:"#zdc-"+m[o]+"-queries"),h=0. 5 | * 6 | * @version 1.1 7 | * @author Andrew Rumm 8 | * 9 | */ 10 | 11 | $this->language = array( 12 | 13 | 'affected_rows' => 'затронутые строки', 14 | 'backtrace' => 'Обратная трассировка', 15 | 'cache_path_not_writable' => 'Невозможно создать кэш запроса. Убедитесь в доступности и возможности записи пути.', 16 | 'cannot_use_parameter_marker' => 'Невозможно использовать маркер параметра ("?", вопросительный знак) в

%s

Используйте исходное значение, которое будет автоматически экранировано.', 17 | 'close_all' => 'закрыть все', 18 | 'could_not_connect_to_database' => 'Невозможно приконнектится к базе данных', 19 | 'could_not_connect_to_memcache_server' => 'Невозможно приконнектится к memcache', 20 | 'could_not_connect_to_redis_server' => 'Could not connect to the redis server', 21 | 'could_not_seek' => 'невозможно перейти на указанную запись', 22 | 'could_not_select_database' => 'Невозможно выбрать базу данных', 23 | 'could_not_write_to_log' => 'Невозможно записать в лог файл. Убедитесь в его доступности', 24 | 'date' => 'Дата', 25 | 'email_subject' => 'Медленный запрос в %s!', 26 | 'email_content' => "Следующий запрос превысил нормальное время выполнения в %s секунд, был выполнен за %s секунд: \n\n %s", 27 | 'error' => 'Ошибка', 28 | 'errors' => 'ошибки', 29 | 'execution_time' => 'время выполнения', 30 | 'explain' => 'Explain', 31 | 'data_not_an_array' => 'Третий параметр insert_bulk() должен быть массивом массивов', 32 | 'file' => 'файл', 33 | 'file_could_not_be_opened' => 'Невозможно открыть файл', 34 | 'from_cache' => 'из кэша', 35 | 'function' => 'функция', 36 | 'globals' => 'Глобальные переменные', 37 | 'invalid_option' => 'Неверный параметр поделючения %s', 38 | 'line' => 'строка', 39 | 'memcache_extension_not_installed' => 'расширение Memcache не найдено.
40 | Для использования memcache в качестве кэширующего средства, PHP должен быть не ниже 4.3.3, скомпилирован 41 | с memcached, и настроен с опцией --with-zlib[=DIR].', 42 | 'redis_extension_not_installed' => 'Redis extension not found.
43 | For using redis as caching method, PHP version must be 4.3.3+, must be compiled with the 44 | reids extension, and needs to be 45 | configured with --with-zlib[=DIR].', 46 | 'milliseconds' => 'мс.', 47 | 'mysql_error' => 'Ошибка MySQL', 48 | 'no' => 'Нет', 49 | 'no_active_session' => 'Вы выбрали кэширование результатов запросоа в сессии, но активных сессий не найдено. Перед использованием библиотеки используйте session_start()!', 50 | 'no_transaction_in_progress' => 'Нет активных транзакций.', 51 | 'not_a_valid_resource' => 'Недопустимый ресурс (убедитесь что вы указали ресурс в качестве аргумента для fetch_assoc()/fetch_obj() если вы выполняете запрос внутри цикла)', 52 | 'optimization_needed' => 'ВНИМАНИЕ: Несколько первых результатов текущего запроса такие же как у %s других запросов!', 53 | 'options_before_connect' => 'The "option" method must be called before connecting to a MySQL server', 54 | 'returned_rows' => 'строк возвращено', 55 | 'seconds' => 'сек.', 56 | 'successful_queries' => 'успешных запросов', 57 | 'to_top' => 'в начало', 58 | 'transaction_in_progress' => 'Транзакция не может быть запущена, пока выполняется другая.', 59 | 'unbuffered' => 'небуферизовано', 60 | 'unbuffered_queries_cannot_be_cached' => 'Небуфиризированные запросы не могут быть закэшированы', 61 | 'unsuccessful_queries' => 'неуспешных запросов', 62 | 'unusable_method_unbuffered_queries' => 'Mетод %s() не может быть использован наряду с небуферизованными запросами', 63 | 'warning_charset' => 'Значения по умолчанию для charset и collation небыли установлены. Вызовите set_charset() после подключения к базе.', 64 | 'warning_memcache' => 'Расширение "memcache" доступно на вашем сервере, рассмотрите возможность использования memcache для кэширования результатов ваших запросов.
Подробности в документации.', 65 | 'warning_redis' => 'The "redis" extension is available on your server - consider using redis for caching query results.
See the documentation for more information.', 66 | 'warning_replacements_not_array' => '$replacements должен быть массивом значений', 67 | 'warning_replacements_wrong_number' => 'количество значений для замены отличается от количества значений в массиве $replacements', 68 | 'warning_wait_timeout' => 'Значение переменной MySQL wait_timeout установлено в %s. 69 | Переменная wait_timeout обозначает время в секундах, которое MySQL будет ожидать 70 | перед тем как отключить неиспользуемое подключение. 71 | После выполнения скрипта, подключение MySQL не уничтожается, а отправляется в состояние idle для 72 | будущего использования тем же пользователем, требующим подключение к базе 73 | (обычное поведение, когда пользователи пользуются веб-сайтом). Значение wait_timeout 74 | по умолчанию 28800 секунд или 8 часов. В случае, когда у вас большое количество пользователей это приводит 75 | к ошибке 76 | Слишком много соединений, и возможны ситуации, когда нет возможности настроить максимальное количество 77 | свободных подключений. 78 | Рекомендуемое значение - 300 секунд (5 минут).', 79 | 'warning' => 'Предупреждение', 80 | 'warnings' => 'предупреждения', 81 | 'yes' => 'Да', 82 | ); 83 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ### GNU LESSER GENERAL PUBLIC LICENSE 2 | 3 | Version 3, 29 June 2007 4 | 5 | Copyright (C) 2007 Free Software Foundation, Inc. 6 | 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this 9 | license document, but changing it is not allowed. 10 | 11 | This version of the GNU Lesser General Public License incorporates the 12 | terms and conditions of version 3 of the GNU General Public License, 13 | supplemented by the additional permissions listed below. 14 | 15 | #### 0. Additional Definitions. 16 | 17 | As used herein, "this License" refers to version 3 of the GNU Lesser 18 | General Public License, and the "GNU GPL" refers to version 3 of the 19 | GNU General Public License. 20 | 21 | "The Library" refers to a covered work governed by this License, other 22 | than an Application or a Combined Work as defined below. 23 | 24 | An "Application" is any work that makes use of an interface provided 25 | by the Library, but which is not otherwise based on the Library. 26 | Defining a subclass of a class defined by the Library is deemed a mode 27 | of using an interface provided by the Library. 28 | 29 | A "Combined Work" is a work produced by combining or linking an 30 | Application with the Library. The particular version of the Library 31 | with which the Combined Work was made is also called the "Linked 32 | Version". 33 | 34 | The "Minimal Corresponding Source" for a Combined Work means the 35 | Corresponding Source for the Combined Work, excluding any source code 36 | for portions of the Combined Work that, considered in isolation, are 37 | based on the Application, and not on the Linked Version. 38 | 39 | The "Corresponding Application Code" for a Combined Work means the 40 | object code and/or source code for the Application, including any data 41 | and utility programs needed for reproducing the Combined Work from the 42 | Application, but excluding the System Libraries of the Combined Work. 43 | 44 | #### 1. Exception to Section 3 of the GNU GPL. 45 | 46 | You may convey a covered work under sections 3 and 4 of this License 47 | without being bound by section 3 of the GNU GPL. 48 | 49 | #### 2. Conveying Modified Versions. 50 | 51 | If you modify a copy of the Library, and, in your modifications, a 52 | facility refers to a function or data to be supplied by an Application 53 | that uses the facility (other than as an argument passed when the 54 | facility is invoked), then you may convey a copy of the modified 55 | version: 56 | 57 | - a) under this License, provided that you make a good faith effort 58 | to ensure that, in the event an Application does not supply the 59 | function or data, the facility still operates, and performs 60 | whatever part of its purpose remains meaningful, or 61 | - b) under the GNU GPL, with none of the additional permissions of 62 | this License applicable to that copy. 63 | 64 | #### 3. Object Code Incorporating Material from Library Header Files. 65 | 66 | The object code form of an Application may incorporate material from a 67 | header file that is part of the Library. You may convey such object 68 | code under terms of your choice, provided that, if the incorporated 69 | material is not limited to numerical parameters, data structure 70 | layouts and accessors, or small macros, inline functions and templates 71 | (ten or fewer lines in length), you do both of the following: 72 | 73 | - a) Give prominent notice with each copy of the object code that 74 | the Library is used in it and that the Library and its use are 75 | covered by this License. 76 | - b) Accompany the object code with a copy of the GNU GPL and this 77 | license document. 78 | 79 | #### 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, taken 82 | together, effectively do not restrict modification of the portions of 83 | the Library contained in the Combined Work and reverse engineering for 84 | debugging such modifications, if you also do each of the following: 85 | 86 | - a) Give prominent notice with each copy of the Combined Work that 87 | the Library is used in it and that the Library and its use are 88 | covered by this License. 89 | - b) Accompany the Combined Work with a copy of the GNU GPL and this 90 | license document. 91 | - c) For a Combined Work that displays copyright notices during 92 | execution, include the copyright notice for the Library among 93 | these notices, as well as a reference directing the user to the 94 | copies of the GNU GPL and this license document. 95 | - d) Do one of the following: 96 | - 0) Convey the Minimal Corresponding Source under the terms of 97 | this License, and the Corresponding Application Code in a form 98 | suitable for, and under terms that permit, the user to 99 | recombine or relink the Application with a modified version of 100 | the Linked Version to produce a modified Combined Work, in the 101 | manner specified by section 6 of the GNU GPL for conveying 102 | Corresponding Source. 103 | - 1) Use a suitable shared library mechanism for linking with 104 | the Library. A suitable mechanism is one that (a) uses at run 105 | time a copy of the Library already present on the user's 106 | computer system, and (b) will operate properly with a modified 107 | version of the Library that is interface-compatible with the 108 | Linked Version. 109 | - e) Provide Installation Information, but only if you would 110 | otherwise be required to provide such information under section 6 111 | of the GNU GPL, and only to the extent that such information is 112 | necessary to install and execute a modified version of the 113 | Combined Work produced by recombining or relinking the Application 114 | with a modified version of the Linked Version. (If you use option 115 | 4d0, the Installation Information must accompany the Minimal 116 | Corresponding Source and Corresponding Application Code. If you 117 | use option 4d1, you must provide the Installation Information in 118 | the manner specified by section 6 of the GNU GPL for conveying 119 | Corresponding Source.) 120 | 121 | #### 5. Combined Libraries. 122 | 123 | You may place library facilities that are a work based on the Library 124 | side by side in a single library together with other library 125 | facilities that are not Applications and are not covered by this 126 | License, and convey such a combined library under terms of your 127 | choice, if you do both of the following: 128 | 129 | - a) Accompany the combined library with a copy of the same work 130 | based on the Library, uncombined with any other library 131 | facilities, conveyed under the terms of this License. 132 | - b) Give prominent notice with the combined library that part of it 133 | is a work based on the Library, and explaining where to find the 134 | accompanying uncombined form of the same work. 135 | 136 | #### 6. Revised Versions of the GNU Lesser General Public License. 137 | 138 | The Free Software Foundation may publish revised and/or new versions 139 | of the GNU Lesser General Public License from time to time. Such new 140 | versions will be similar in spirit to the present version, but may 141 | differ in detail to address new problems or concerns. 142 | 143 | Each version is given a distinguishing version number. If the Library 144 | as you received it specifies that a certain numbered version of the 145 | GNU Lesser General Public License "or any later version" applies to 146 | it, you have the option of following the terms and conditions either 147 | of that published version or of any later version published by the 148 | Free Software Foundation. If the Library as you received it does not 149 | specify a version number of the GNU Lesser General Public License, you 150 | may choose any version of the GNU Lesser General Public License ever 151 | published by the Free Software Foundation. 152 | 153 | If the Library as you received it specifies that a proxy can decide 154 | whether future versions of the GNU Lesser General Public License shall 155 | apply, that proxy's public statement of acceptance of any version is 156 | permanent authorization for you to choose that version for the 157 | Library. -------------------------------------------------------------------------------- /languages/german.php: -------------------------------------------------------------------------------- 1 | . 5 | * 6 | * @version 2.0 7 | * @author etb09 8 | * @author Bernhard Morgenstern (https://github.com/bmorg) 9 | * 10 | */ 11 | 12 | $this->language = array( 13 | 14 | 'affected_rows' => 'Betroffene Zeilen', 15 | 'backtrace' => 'Ablaufverfolgung', 16 | 'cache_path_not_writable' => 'Abfrage konnte nicht gecacht werden. Stellen Sie sicher, dass der Pfad existiert und beschreibbar ist.', 17 | 'cannot_use_parameter_marker' => 'Der Parameter-Marker ("?", Fragezeichen) kann nicht in

%s
verwendet werden.
Verwenden Sie den tatsächlichen Wert. Er wird automatisch escaped.', 18 | 'close_all' => 'Alle Einträge ausblenden', 19 | 'could_not_connect_to_database' => 'Verbindung zur Datenbank konnte nicht hergestellt werden', 20 | 'could_not_connect_to_memcache_server' => 'Verbindung zum Memcache Server konnte nicht hergestellt werden', 21 | 'could_not_connect_to_redis_server' => 'Could not connect to the redis server', 22 | 'could_not_seek' => 'Interner Datensatz-Zeiger konnte nicht zur angegebenen Zeile bewegt werden', // same wording as https://www.php.net/manual/de/function.mysql-data-seek.php 23 | 'could_not_select_database' => 'Datenbank konnte nicht ausgewählt werden', 24 | 'could_not_write_to_log' => 'Log-Datei konnte nicht geschrieben werden. Stellen Sie sicher, dass der Ordner existiert und beschreibbar ist', 25 | 'date' => 'Datum', 26 | 'email_subject' => 'Langsame Abfrage auf %s!', 27 | 'email_content' => "Die nachfolgende Abfrage hat die normale Ausführungszeit von %s Sekunden überschritten. Die Abfrage dauerte %s Sekunden: \n\n %s", 28 | 'error' => 'Fehler', 29 | 'errors' => 'Fehler', 30 | 'execution_time' => 'Ausführungszeit', 31 | 'explain' => 'Abfrage erklären', 32 | 'data_not_an_array' => 'Das dritte Argument von insert_bulk() muss ein Array von Arrays sein.', 33 | 'file' => 'Datei', 34 | 'file_could_not_be_opened' => 'Datei konnte nicht geöffnet werden', 35 | 'from_cache' => 'aus dem Cache', 36 | 'function' => 'Funktion', 37 | 'globals' => 'Globale Variablen', 38 | 'invalid_option' => 'Ungültige Verbindungsoption: %s', 39 | 'line' => 'Zeile', 40 | 'memcache_extension_not_installed' => 'Die Memcache-Erweiterung wurde nicht gefunden.
41 | Um Memcache als Caching-Methode zu verwenden ist PHP Version 4.3.3+ notwendig, PHP muss mit der 42 | memcached Erweiterung kompiliert werden und muss mit 43 | --with-zlib[=DIR] konfiguriert werden.', 44 | 'redis_extension_not_installed' => 'Redis extension not found.
45 | For using redis as caching method, PHP version must be 4.3.3+, must be compiled with the 46 | reids extension, and needs to be 47 | configured with --with-zlib[=DIR].', 48 | 'milliseconds' => 'ms', 49 | 'mysql_error' => 'MySQL-Fehler', 50 | 'no' => 'Nein', 51 | 'no_active_session' => 'Sie haben ausgewählt, dass die Abfrageresultate in der Session zwischengespeichert (gecacht) werden sollen, aber es gibt keine aktive Session. Rufen Sie session_start() auf bevor Sie Funktionen dieser Bibliothek benutzen!', 52 | 'no_transaction_in_progress' => 'Keine Transaktion aktiv.', 53 | 'not_a_valid_resource' => 'Keine gültige Ressource (stellen Sie sicher, dass sie eine Ressource als Argument für fetch_assoc()/fetch_obj() übergeben, falls sie die Abfrage in einer Schleife ausführen)', 54 | 'optimization_needed' => 'WARNUNG: Die ersten Ergebnisse dieser Abfrage sind identisch mit den Ergebnissen von %s anderen Abfragen!', 55 | 'options_before_connect' => 'Die Methode "option" muss aufgerufen werden bevor die Verbindung zum MySQL-Server hergestellt wird.', 56 | 'returned_rows' => 'Abgefragte Zeilen', 57 | 'seconds' => 'Sekunden', 58 | 'successful_queries' => 'Erfolgreiche Abfragen', 59 | 'to_top' => 'nach oben', 60 | 'transaction_in_progress' => 'Die Transaktion konnte nicht durchgeführt werden, da bereits eine andere Transaktion aktiv ist.', 61 | 'unbuffered' => 'ungepuffert', 62 | 'unbuffered_queries_cannot_be_cached' => 'Ungepufferte Abfragen können nicht gecacht werden', 63 | 'unsuccessful_queries' => 'Fehlerhafte Abfragen', 64 | 'unusable_method_unbuffered_queries' => 'Methode %s() kann nicht mit ungepufferten Abfragen verwendet werden', 65 | 'warning_charset' => 'Standard-Zeichenkodierung und Standard-Kollation wurden nicht gesetzt. Bitte rufen Sie set_charset() nach dem Verbindungsaufbau auf.', 66 | 'warning_memcache' => 'Die "memcache"-Erweiterung ist auf diesem Server verfügbar. Memcache kann zum Cachen von Ergebnissen verwendet werden.
Weitere Informationen in der Dokumentation.', 67 | 'warning_redis' => 'The "redis" extension is available on your server - consider using redis for caching query results.
See the documentation for more information.', 68 | 'warning_replacements_not_array' => '$replacements muss ein Array mit Werten enthalten.', 69 | 'warning_replacements_wrong_number' => 'Die Anzahl der Ersetzungen stimmt nicht mit der Anzahl der Elemente im $replacements-Array überein.', 70 | 'warning_wait_timeout' => 'Die MySQL-Variable wait_timeout hat den Wert %s. Die Variable wait_timeout repräsentiert die Zeit in Sekunden, die MySQL wartet, bevor eine inaktive Verbindung beendet wird. Nach Beendigung der Ausführung wird die MySQL Verbindung nicht beendet sondern in einen Inaktiv-Status gesetzt und erneut benutzt, wenn derselbe Nutzer wieder eine Datenbankverbindung benötigt (ein gängiges Beispiel ist die Navigation durch die einzelnen Seiten einer Webseite). Der Standardwert für wait_timeout ist 28800 Sekunden bzw. 8 Stunden. Sofern Sie viele Besucher haben, kann dies zu Too many connections-Fehlern führen, weil es möglicherweise Zeiten gibt, in denen keine freien Verbindungen mehr verfügbar sind. Der empfohlene Wert ist 300 Sekunden (5 Minuten).', 71 | 'warning' => 'Warnung', 72 | 'warnings' => 'Warnungen', 73 | 'yes' => 'Ja', 74 | 75 | ); 76 | 77 | ?> 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | zebrajs 2 | 3 | # Zebra Database  [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](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 | [![Latest Stable Version](https://poser.pugx.org/stefangabos/zebra_database/v)](https://packagist.org/packages/stefangabos/zebra_database) [![Total Downloads](https://poser.pugx.org/stefangabos/zebra_database/downloads)](https://packagist.org/packages/stefangabos/zebra_database) [![Monthly Downloads](https://poser.pugx.org/stefangabos/zebra_database/d/monthly)](https://packagist.org/packages/stefangabos/zebra_database) [![Daily Downloads](https://poser.pugx.org/stefangabos/zebra_database/d/daily)](https://packagist.org/packages/stefangabos/zebra_database) [![License](https://poser.pugx.org/stefangabos/zebra_database/license)](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 | >
:microscope: See the [debugging console](https://stefangabos.github.io/Zebra_Database/examples/index.html) in action

20 | 21 | ## Key Features of this PHP MySQLi Wrapper 22 | 23 | - it uses the [mysqli extension](https://www.php.net/manual/en/book.mysqli.php) extension for communicating with the database instead of the old *mysql* extension, which is officially deprecated as of PHP v5.5.0 and will be removed in the future; **this is not a wrapper for the PDO extension which is already a wrapper in itself!** 24 | 25 | - can provide debugging information even when called from **CLI** (command-line interface) 26 | 27 | - logs all queries, including those run through **AJAX** 28 | 29 | - offers [lots of powerful methods](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html) for easier interaction with MySQL 30 | 31 | - supports [unbuffered queries](https://www.php.net/manual/en/mysqlinfo.concepts.buffering.php) 32 | 33 | - provides a better security layer by encouraging the use of prepared statements, where parameters are automatically escaped 34 | 35 | - provides a very detailed debugging interface with lots of useful information about executed queries; it also automatically [EXPLAIN](https://dev.mysql.com/doc/refman/8.0/en/explain.html)s each SELECT query 36 | 37 | - supports caching of query results to the disk, in the session, or to a [memcache](https://memcached.org/) or to a [redis](https://redis.io/) server 38 | 39 | - has [really good documentation](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html) 40 | 41 | - code is heavily commented and generates no warnings/errors/notices when PHP's error reporting level is set to **E_ALL** 42 | 43 | ![zebra database debugging console showing query details](https://raw.githubusercontent.com/stefangabos/Zebra_Database/master/docs/media/zebra-database-debug-console.png) 44 | 45 | ## :notebook_with_decorative_cover: Documentation 46 | 47 | Check out the [comprehensive PHP MySQLi wrapper documentation](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html). 48 | 49 | ## 🎂 Support the development of this project 50 | 51 | Your support is greatly appreciated and it keeps me motivated continue working on open source projects. If you enjoy this project please star it by clicking on the star button at the top of the page. If you're feeling generous, you can also buy me a coffee through PayPal or become a sponsor. 52 | **Thank you for your support!** 🎉 53 | 54 | [Star it on GitHub](https://github.com/stefangabos/Zebra_Database) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3WTC6RTC7496Q) [](https://github.com/sponsors/stefangabos) 55 | 56 | ## Requirements 57 | 58 | PHP 5.4.0+ with the **mysqli extension** activated, MySQL 4.1.22+ 59 | 60 | For using **memcache** as caching method, PHP must be compiled with the [memcache](https://pecl.php.net/package/memcache) extension and, if [memcache_compressed](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$memcache_compressed) property is set to TRUE, needs to be configured with `–with-zlib[=DIR]` 61 | 62 | For using **redis** as caching method, PHP must be compiled with the [redis](https://pecl.php.net/package/redis) extension and, if [redis_compressed](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$redis_compressed) property is set to TRUE, needs to be configured with `–with-zlib[=DIR]` 63 | 64 | ## Installation 65 | 66 | You can install Zebra Database via [Composer](https://packagist.org/packages/stefangabos/zebra_database) 67 | 68 | ```bash 69 | # get the latest stable release 70 | composer require stefangabos/zebra_database 71 | 72 | # get the latest commit 73 | composer require stefangabos/zebra_database:dev-master 74 | ``` 75 | 76 | Or you can install it manually by downloading the latest version, unpacking it, and then including it in your project 77 | 78 | ```php 79 | require_once 'path/to/Zebra_Database.php'; 80 | ``` 81 | 82 | ## How to use 83 | 84 | ##### Connecting to a database 85 | 86 | ```php 87 | // instantiate the library 88 | $db = new Zebra_Database(); 89 | 90 | // connect to a server and select a database 91 | $db->connect('host', 'username', 'password', 'database'); 92 | ``` 93 | 94 | ##### Running queries 95 | 96 | ```php 97 | // question marks will re replaced automatically with the escaped values from the array 98 | // I ENCOURAGE YOU TO WRITE YOUR QUERIES IN A READABLE FORMAT, LIKE BELOW 99 | $db->query(' 100 | SELECT 101 | column1 102 | , column2 103 | , column3 104 | FROM 105 | tablename1 106 | LEFT JOIN tablename2 ON tablename1.column1 = tablename2.column1 107 | WHERE 108 | somecriteria = ? 109 | AND someothercriteria = ? 110 | ', array($somevalue, $someothervalue)); 111 | 112 | // any fetch method will work with the last result so 113 | // there's no need to explicitly pass that around 114 | 115 | // you could fetch all records to one associative array... 116 | $records = $db->fetch_assoc_all(); 117 | 118 | // you could fetch all records to one associative array 119 | // using the values in a specific column as keys 120 | $records = $db->fetch_assoc_all('column1'); 121 | 122 | // or fetch records one by one, as associative arrays 123 | while ($row = $db->fetch_assoc()) { 124 | // do stuff 125 | } 126 | ``` 127 | 128 | ##### An INSERT statement 129 | 130 | ```php 131 | // notice that you can use MySQL functions in values 132 | $db->insert( 133 | 'tablename', 134 | array( 135 | 'column1' => $value1, 136 | 'column2' => $value2, 137 | 'date_updated' => 'NOW()' 138 | ) 139 | ); 140 | ``` 141 | 142 | ##### An UPDATE statement 143 | 144 | ```php 145 | // $criteria will be escaped and enclosed in grave accents, and will 146 | // replace the corresponding ? (question mark) automatically 147 | // also, notice that you can use MySQL functions in values 148 | // when using MySQL functions, the value will be used as it is without being escaped! 149 | // while this is ok when using a function without any arguments like NOW(), this may 150 | // pose a security concern if the argument(s) come from user input. 151 | // in this case we have to escape the value ourselves 152 | $db->update( 153 | 'tablename', 154 | array( 155 | 'column1' => $value1, 156 | 'column2' => 'TRIM(UCASE("value2"))', 157 | 'column3' => 'TRIM(UCASE("'' . $db->escape($value3) . "))', 158 | 'date_updated' => 'NOW()' 159 | ), 160 | 'criteria = ?', 161 | array($criteria) 162 | ); 163 | ``` 164 | > There are over **40 methods** and 20 properties that you can use and **lots** of things you can do with this library. I've prepared an [awesome documentation](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html) so that you can easily get an overview of what can be done. Go ahead, [check it out](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html)! 165 | -------------------------------------------------------------------------------- /public/css/default/zebra_database.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:color'; 2 | 3 | $main_menu_background_color: #151C24; 4 | $main_menu_background_color_hover: color.adjust($main_menu_background_color, $lightness: 20%); 5 | $main_menu_text_color: #FFF; 6 | $main_menu_text_color_hover: #FFF; 7 | $min_menu_badge_background_color: orange; 8 | $min_menu_badge_text_color: #000; 9 | 10 | $globals_submenu_background_color: #403D32; 11 | $globals_submenu_background_color_hover: color.adjust($globals_submenu_background_color, $lightness: 20%); 12 | $globals_submenu_text_color: #FFF; 13 | $globals_submenu_text_color_hover: #FFF; 14 | 15 | $entry_border_color: #9CA68F; 16 | 17 | $entry_counter_background_color: color.adjust($entry_border_color, $lightness: -20%); 18 | $entry_counter_text_color: #FFF; 19 | $entry_counter_transaction_background_color: #888; 20 | $entry_counter_transaction_text_color: #FFF; 21 | $entry_counter_highlight_background_color: #926492; 22 | $entry_counter_highlight_text_color: #FFF; 23 | $entry_counter_ajax_background_color: color.adjust($entry_border_color, $lightness: -20%); 24 | $entry_counter_ajax_text_color: #CDDC39; 25 | 26 | $entry_box_error_background_color: #CC3333; 27 | $entry_box_error_text_color: #FFF; 28 | 29 | $entry_box_actions_menu_background_color: color.adjust($entry_border_color, $lightness: 20%); 30 | $entry_box_actions_menu_ajax_accent_background_color: #E63570; 31 | $entry_box_actions_menu_ajax_accent_text_color: #FFF; 32 | $entry_box_actions_menu_cached_accent_background_color: #CCFF99; 33 | $entry_box_actions_menu_cached_accent_text_color: #222; 34 | $entry_box_actions_menu_unbuffered_accent_background_color: #FFFFCC; 35 | $entry_box_actions_menu_unbuffered_accent_text_color: #222; 36 | 37 | $entry_syntax_highlighter_background_color: #EEE; 38 | $entry_syntax_highlighter_line_height: 1.3; 39 | $entry_syntax_highlighter_keyword: #990099; 40 | $entry_syntax_highlighter_string: #008000; 41 | $entry_syntax_highlighter_symbol: #FF00FF; 42 | $entry_syntax_highlighter_comment: teal; 43 | 44 | $entry_data_table_header_text_color: #F0F0F0; 45 | $entry_data_table_odd_row_background_color: color.adjust($entry_syntax_highlighter_background_color, $lightness: -5%); 46 | 47 | #zdc { 48 | position: absolute; 49 | top: 0; 50 | left: 0; 51 | width: 100%; 52 | font-family: Tahoma, Arial, Helvetica, sans-serif; 53 | font-size: 13px; 54 | color: #222; 55 | text-align: left; 56 | padding-top: 32px; 57 | z-index: 9998; 58 | 59 | * { 60 | margin: 0; 61 | padding: 0; 62 | font-family: inherit; 63 | font-size: inherit; 64 | line-height: 1.3; 65 | } 66 | 67 | /* --------------------------------------------------------------------------------------------------------------------*/ 68 | /* GLOBALS */ 69 | /* --------------------------------------------------------------------------------------------------------------------*/ 70 | 71 | .zdc-visible { 72 | display: block !important; 73 | } 74 | 75 | a { 76 | color: #000; 77 | text-decoration: none; 78 | } 79 | 80 | ul { 81 | list-style-type: none; 82 | margin: 0; 83 | padding: 0; 84 | display: block; 85 | width: 100%; 86 | 87 | &:after { 88 | content: ""; 89 | display: table; 90 | clear: both; 91 | } 92 | 93 | li { 94 | float: left; 95 | 96 | a { 97 | display: block; 98 | float: left; 99 | } 100 | } 101 | } 102 | 103 | table { 104 | border-collapse: collapse; 105 | td, th { 106 | padding: 4px; 107 | border: none; 108 | vertical-align: top; 109 | } 110 | } 111 | 112 | /* --------------------------------------------------------------------------------------------------------------------*/ 113 | /* ENTRIES */ 114 | /* --------------------------------------------------------------------------------------------------------------------*/ 115 | 116 | .zdc-entry { 117 | background: $entry_border_color; 118 | border-top: 1px solid #FFF; 119 | .zdc-counter { 120 | background: $entry_counter_background_color; 121 | color: $entry_counter_text_color; 122 | &.zdc-counter-ajax { 123 | background: $entry_counter_ajax_background_color; 124 | color: $entry_counter_ajax_text_color; 125 | } 126 | } 127 | &.zdc-transaction .zdc-counter { 128 | background: $entry_counter_transaction_background_color; 129 | color: $entry_counter_transaction_text_color; 130 | } 131 | &.zdc-highlight .zdc-counter { 132 | background: $entry_counter_highlight_background_color; 133 | color: $entry_counter_highlight_text_color; 134 | } 135 | .zdc-data { 136 | width: 100%; 137 | padding: 5px; 138 | .zdc-box { 139 | background: $entry_syntax_highlighter_background_color; 140 | padding: 8px; 141 | &.zdc-error { 142 | background: $entry_box_error_background_color; 143 | color: $entry_box_error_text_color; 144 | padding: 5px; 145 | a { 146 | text-decoration: underline; 147 | color: inherit; 148 | } 149 | } 150 | &.zdc-actions { 151 | background: $entry_box_actions_menu_background_color; 152 | padding: 2px; 153 | ul li { 154 | padding: 5px 5px 5px 28px; 155 | background-repeat: no-repeat; 156 | background-position: 5px center; 157 | &.zdc-time { 158 | background-image: url(clock.png); 159 | } 160 | &.zdc-ajax { 161 | background-color: $entry_box_actions_menu_ajax_accent_background_color; 162 | color: $entry_box_actions_menu_ajax_accent_text_color; 163 | padding: 5px; 164 | border-radius: 5px; 165 | margin-right: 2px; 166 | } 167 | &.zdc-cache { 168 | background-image: url(cache.png); 169 | background-color: $entry_box_actions_menu_cached_accent_background_color; 170 | color: $entry_box_actions_menu_cached_accent_text_color; 171 | border-radius: 5px; 172 | margin-right: 2px; 173 | } 174 | &.zdc-unbuffered { 175 | background-image: url(unbuffered.png); 176 | background-color: $entry_box_actions_menu_unbuffered_accent_background_color; 177 | color: $entry_box_actions_menu_unbuffered_accent_text_color; 178 | border-radius: 5px; 179 | margin-right: 2px; 180 | } 181 | &.zdc-top { 182 | background-image: url(top.png); 183 | } 184 | &.zdc-close { 185 | background-image: url(close.png); 186 | background-position: 5px 6px !important; 187 | } 188 | &.zdc-records { 189 | background-image: url(records.png); 190 | } 191 | &.zdc-affected { 192 | background-image: url(affected.png); 193 | } 194 | &.zdc-explain { 195 | background-image: url(explain.png); 196 | } 197 | &.zdc-backtrace { 198 | background-image: url(backtrace.png); 199 | } 200 | &.zdc-database-manager { 201 | background-image: url(db-manager.png); 202 | } 203 | a { 204 | text-decoration: underline !important; 205 | } 206 | } 207 | } 208 | &.zdc-data-table { 209 | tr:nth-child(even) { 210 | background: $entry_data_table_odd_row_background_color; 211 | } 212 | th, td { 213 | border: 1px solid #888; 214 | } 215 | th { 216 | background: $entry_border_color; 217 | color: $entry_data_table_header_text_color; 218 | } 219 | } 220 | pre { 221 | padding: 8px; 222 | } 223 | } 224 | } 225 | } 226 | } 227 | 228 | /* --------------------------------------------------------------------------------------------------------------------*/ 229 | /* MAIN MENU */ 230 | /* --------------------------------------------------------------------------------------------------------------------*/ 231 | 232 | #zdc-main { 233 | position: fixed; 234 | top: 0; 235 | left: 0; 236 | background: $main_menu_background_color; 237 | font-size: 13px; 238 | display: none !important; 239 | a { 240 | border-right: 1px solid $main_menu_text_color; 241 | color: $main_menu_text_color; 242 | padding: 8px; 243 | &:hover { 244 | background: $main_menu_background_color_hover; 245 | color: $main_menu_text_color_hover; 246 | } 247 | } 248 | span { 249 | background: $min_menu_badge_background_color; 250 | padding: 2px 4px; 251 | font-weight: bold; 252 | color: $min_menu_badge_text_color; 253 | margin: 0 0 0 2px; 254 | border-radius: 2px; 255 | } 256 | } 257 | 258 | /* --------------------------------------------------------------------------------------------------------------------*/ 259 | /* GLOBALS SUBMENU */ 260 | /* --------------------------------------------------------------------------------------------------------------------*/ 261 | 262 | #zdc-globals-submenu { 263 | background: $globals_submenu_background_color; 264 | a { 265 | padding: 7px 8px 9px 8px; 266 | color: $globals_submenu_text_color; 267 | &:hover { 268 | background: $globals_submenu_background_color_hover; 269 | color: $globals_submenu_text_color_hover; 270 | } 271 | } 272 | } 273 | 274 | /* --------------------------------------------------------------------------------------------------------------------*/ 275 | /* SYNTAX HIGHLIGHTING */ 276 | /* --------------------------------------------------------------------------------------------------------------------*/ 277 | 278 | .zdc-syntax-highlight { 279 | pre { 280 | line-height: $entry_syntax_highlighter_line_height !important; 281 | background: $entry_syntax_highlighter_background_color !important; 282 | border: transparent !important; 283 | } 284 | .keyword { 285 | font-weight: bold; 286 | color: $entry_syntax_highlighter_keyword; 287 | text-transform: uppercase; 288 | } 289 | .string { 290 | font-weight: bold; 291 | color: $entry_syntax_highlighter_string; 292 | } 293 | .symbol { 294 | font-weight: bold; 295 | color: $entry_syntax_highlighter_symbol; 296 | } 297 | .comment { 298 | color: $entry_syntax_highlighter_comment; 299 | } 300 | } 301 | 302 | /* --------------------------------------------------------------------------------------------------------------------*/ 303 | /* MINI CONSOLE */ 304 | /* --------------------------------------------------------------------------------------------------------------------*/ 305 | 306 | #zdc-mini { 307 | position: fixed; 308 | top: 0; 309 | right: 0; 310 | z-index: 9999; 311 | font-family: Tahoma, Arial, Helvetica, sans-serif; 312 | font-size: 13px; 313 | text-align: left; 314 | display: block; 315 | a { 316 | background: $main_menu_background_color url(db.png) no-repeat 10px center; 317 | display: block; 318 | padding: 8px 8px 8px 35px; 319 | color: $main_menu_text_color; 320 | text-decoration: none; 321 | line-height: 1.3; 322 | span { 323 | font-weight: bold; 324 | background-color: $entry_box_error_background_color; 325 | margin: 0 0 0 6px; 326 | padding: 2px 7px; 327 | border-radius: 2px; 328 | } 329 | } 330 | } 331 | 332 | /* --------------------------------------------------------------------------------------------------------------------*/ 333 | /* ALL IS COLLAPSED BY DEFAULT */ 334 | /* --------------------------------------------------------------------------------------------------------------------*/ 335 | #zdc-globals-submenu, 336 | .zdc-entry, 337 | .zdc-records-table, 338 | .zdc-explain-table, 339 | .zdc-backtrace-table { 340 | display: none; 341 | overflow: auto; 342 | } 343 | -------------------------------------------------------------------------------- /public/javascript/zebra_database.src.js: -------------------------------------------------------------------------------- 1 | /* global _$ */ 2 | 3 | // prevent multiple initializations 4 | if (typeof _$ === 'undefined') { 5 | 6 | // custom build of zebrajs (see https://github.com/stefangabos/zebrajs) 7 | /* eslint-disable */ 8 | /* jshint ignore:start */ 9 | !function(){"use strict";function u(t,e,n){var o,r=[];if("string"==typeof(t="string"==typeof t&&"body"===t.toLocaleLowerCase()?document.body:t))if(0===t.indexOf("<")&&1")&&2 li a').on('click', function() { 27 | window.scrollTo({ 28 | left: 0, 29 | top: 0 30 | }); 31 | }); 32 | 33 | // handle visibility toggling 34 | $(document).on('click', function(e) { 35 | 36 | var $element, target, $target, is_open; 37 | 38 | // stop if we didn't click on a toggle button 39 | if (!e.target.matches('.zdc-toggle') && !e.target.matches('.zdc-toggle *')) return; 40 | 41 | // handle clicking on inner tag elements 42 | if (!e.target.matches('.zdc-toggle')) $element = $(e.target.parentNode); 43 | else $element = $(e.target); 44 | 45 | // the section that needs to be toggled 46 | target = $element.attr('class').replace(/^.*?zdc\-toggle/, '').replace(/\zdc\-toggle\-id/, '').trim(); 47 | 48 | // the element to be toggled 49 | $target = $('#' + target); 50 | 51 | // if we have to open a section by ID 52 | if ($element.hasClass('zdc-toggle-id') || !$target.length) { 53 | 54 | // if we need to toggle the visibility of a data table, lock on the data table 55 | if (!$target.length) $target = $('div.' + target, $element.closest('.zdc-data')); 56 | 57 | // is the section open? 58 | // (we have to do this before hiding all the sections) 59 | is_open = $target.hasClass('zdc-visible'); 60 | 61 | // if we are viewing a global section, keep the submenu open 62 | if ($element.attr('class').match(/zdc\-globals\-/) && !$element.attr('class').match(/zdc\-globals\-submenu/)) 63 | 64 | $('#zdc .zdc-visible').not('#zdc-globals-submenu').not('#zdc-main').removeClass('zdc-visible'); 65 | 66 | // if we are *not* toggling the visibility of a data table, hide any open section 67 | else if (!$target.hasClass('zdc-data-table')) $('#zdc .zdc-visible').not('#zdc-main').removeClass('zdc-visible'); 68 | 69 | // if we are toggling a data table, hide any open data table 70 | else $('#zdc .zdc-data-table.zdc-visible').not('#zdc-main').removeClass('zdc-visible'); 71 | 72 | // if we have to open a section by class name 73 | } else { 74 | 75 | // is the section open? 76 | // (we have to do this before hiding all the sections) 77 | is_open = $('.zdc-entry.zdc-visible', $target).length; 78 | 79 | // hide open section 80 | $('#zdc .zdc-visible').not('#zdc-main').removeClass('zdc-visible'); 81 | 82 | // the element(s) for which we are going to toggle visibility 83 | $target = $('.zdc-entry', $target); 84 | 85 | } 86 | 87 | // if section is open, close it 88 | if (is_open) $target.removeClass('zdc-visible'); 89 | 90 | // if section is closed, open it 91 | else $target.addClass('zdc-visible'); 92 | 93 | }); 94 | 95 | // if we need to capture AJAX requests 96 | if (undefined !== window.zdb_log_path) 97 | 98 | // capture all AJAX request and check if we have logs in them 99 | (function(XHR) { 100 | var open = XHR.prototype.open, 101 | send = XHR.prototype.send; 102 | 103 | XHR.prototype.open = function(method, url, async, user, pass) { 104 | this._url = url; 105 | open.call(this, method, url, async, user, pass); 106 | }; 107 | 108 | XHR.prototype.send = function(data) { 109 | var self = this, 110 | oldOnReadyStateChange, 111 | chars = '0123456789', 112 | length = 12, 113 | identifier = '', i; 114 | 115 | for (i = 0; i < length; i++) identifier += chars.charAt(Math.floor(Math.random() * chars.length)); 116 | 117 | // send an identifier as a custom header so that the library can use it to manage debug information 118 | // (we set if for all AJAX requests but only those using Zebra_Database will make use of it) 119 | this.setRequestHeader('zdb-log-id', identifier); 120 | 121 | function onReadyStateChange() { 122 | 123 | // if everything was ok with the request and it is *not* the AJAX request that appends the information to the debug interface 124 | if (self.readyState === 4 && self.status === 200 && self._url.indexOf(window.zdb_log_path) === -1) { 125 | 126 | // we'll call the script that will update the debug panel via AJAX 127 | // and we'll feed the ID of the log file it needs to read the data from 128 | var httpRequest = new XMLHttpRequest(); 129 | 130 | httpRequest.open('GET', window.zdb_log_path + '?id=' + identifier, true); 131 | httpRequest.onreadystatechange = function() { 132 | 133 | var response, $element, $errors_tab_toggler, $unsuccessful_queries_tab_toggler, $container, $main_counter, i, queries_count, errors_count, successful_queries_count, unsuccessful_queries_count, 134 | $errors_count, $successful_queries_count, $unsuccessful_queries_count, make_visible, $main_duration, incoming_duration = 0, sections = ['errors', 'successful', 'unsuccessful']; 135 | 136 | // if everything was ok with the request and the content is generated by the library 137 | if (httpRequest.readyState === 4 && httpRequest.status === 200 && httpRequest.responseText.indexOf('-queries-ajax') > -1) { 138 | 139 | response = httpRequest.responseText; 140 | 141 | // iterate over the two sections 142 | for (i in sections) 143 | 144 | // if we have queries in the current section 145 | if (response.indexOf('zdc-' + sections[i] + '-queries-ajax') > -1 || response.indexOf('zdc-' + sections[i] + '-ajax') > -1) { 146 | 147 | // if not yet cached 148 | if (!$main_counter) { 149 | $main_counter = $('#zdc-mini a'); 150 | $main_duration = $('.zdc-total-duration'); 151 | $errors_tab_toggler = $('a.zdc-errors'); 152 | $unsuccessful_queries_tab_toggler = $('a.zdc-unsuccessful-queries'); 153 | $errors_count = $('.zdc-errors span'); 154 | $successful_queries_count = $('.zdc-successful-queries span'); 155 | $unsuccessful_queries_count = $('.zdc-unsuccessful-queries span'); 156 | [successful_queries_count, unsuccessful_queries_count] = $main_counter[0].textContent.split(' / ').map(v => parseInt(v.trim(), 10)); 157 | errors_count = $errors_count.textContent || 0; 158 | } 159 | 160 | // extract the HTML with the section we are working with 161 | $element = sections[i] === 'successful' ? 162 | $(response.match(/
([\s\S]*)<\/div class=\"zdc\-end\">/g)[0]) : 163 | (sections[i] === 'unsuccessful' ? 164 | $(response.match(/
([\s\S]*)<\/div class=\"zdc\-end\">/g)[0]) : 165 | $(response.match(/
([\s\S]*)<\/div class=\"zdc\-end\">/g)[0])); 166 | 167 | // if we have successful queries 168 | if (sections[i] === 'successful') 169 | 170 | // sum the duration of the queries 171 | $('.zdc-time', $element).each(function() { 172 | incoming_duration += parseFloat(this.textContent.match(/^.*?\:\s([0-9\.]+)/)[1]); 173 | }); 174 | 175 | // update the total duration 176 | $main_duration.html((parseFloat($main_duration[0].textContent) + incoming_duration).toFixed(5)); 177 | 178 | // this is the container where it will be appended to 179 | $container = sections[i] === 'errors' ? $('#zdc-' + sections[i]) : $('#zdc-' + sections[i] + '-queries'); 180 | 181 | // if section is open, we'll need to make visible the newly added entries also 182 | make_visible = $('.zdc-entry', $container).length > 0 && $('.zdc-visible', $container).length === $('.zdc-entry', $container).length; 183 | 184 | // insert new queries/errors 185 | $container.html($container.html() + $element.html()); 186 | 187 | // if section was already open, make visible the newly added entries also 188 | if (make_visible) $('.zdc-entry', $container).addClass('zdc-visible'); 189 | 190 | // the number of new queries that were just added 191 | queries_count = $('.zdc-entry', $element).length; 192 | 193 | // if we have unsuccessful queries and the "unsuccessful queries" tab is not yet visible 194 | if ( 195 | (sections[i] === 'unsuccessful' && $($unsuccessful_queries_tab_toggler[0].parentNode).attr('style').match(/display\:/)) || 196 | (sections[i] === 'errors' && $($errors_tab_toggler[0].parentNode).attr('style').match(/display\:/)) 197 | ) { 198 | 199 | // make the tab visible 200 | if (sections[i] === 'unsuccessful') $($unsuccessful_queries_tab_toggler[0].parentNode).attr('style', ''); 201 | else $($errors_tab_toggler[0].parentNode).attr('style', ''); 202 | 203 | // and make sure we have the debug console in "expanded" mode 204 | $('#zdc-main').addClass('zdc-visible'); 205 | 206 | // hide everything else 207 | if (sections[i] !== 'unsuccessful') $('.zdc-entry', $('#zdc-unsuccessful-queries')).removeClass('zdc-visible'); 208 | $('.zdc-entry', $('#zdc-successful-queries')).removeClass('zdc-visible'); 209 | $('.zdc-entry', $('#zdc-globals')).removeClass('zdc-visible'); 210 | $('#zdc-globals-submenu').removeClass('zdc-visible'); 211 | if (sections[i] !== 'errors') $('.zdc-entry', $('#zdc-errors')).removeClass('zdc-visible'); else $('.zdc-entry', $('#zdc-errors')).addClass('zdc-visible'); 212 | $('.zdc-entry', $('#zdc-warnings')).removeClass('zdc-visible'); 213 | 214 | } 215 | 216 | // if we have successful queries 217 | if (sections[i] === 'successful') { 218 | 219 | // increase the total number of successful queries 220 | successful_queries_count += queries_count; 221 | 222 | // and update the little box in the respective section 223 | $successful_queries_count[0].textContent = successful_queries_count; 224 | 225 | // if we have unsuccessful queries 226 | } else if (sections[i] === 'unsuccessful') { 227 | 228 | // increase the total number of unsuccessful queries 229 | unsuccessful_queries_count += queries_count; 230 | 231 | // and update the little box in the respective section 232 | $unsuccessful_queries_count[0].textContent = unsuccessful_queries_count; 233 | 234 | // if we have errors 235 | } else { 236 | 237 | // increase the total number of errors 238 | errors_count += queries_count; 239 | 240 | // and update the little box in the respective section 241 | $errors_count[0].textContent = errors_count; 242 | 243 | } 244 | 245 | // update the main counter 246 | $main_counter[0].textContent = successful_queries_count + ' / ' + unsuccessful_queries_count; 247 | 248 | } 249 | 250 | } 251 | 252 | } 253 | 254 | httpRequest.send(); 255 | 256 | return; 257 | 258 | } 259 | 260 | // if a readyStateChange callback already existed prior to setting ours, call that one now, 261 | if (oldOnReadyStateChange) oldOnReadyStateChange(); 262 | 263 | } 264 | 265 | // register our listener in whichever way is available 266 | if (this.addEventListener) 267 | 268 | this.addEventListener('readystatechange', onReadyStateChange, false); 269 | 270 | else { 271 | 272 | oldOnReadyStateChange = this.onreadystatechange; 273 | this.onreadystatechange = onReadyStateChange; 274 | 275 | } 276 | 277 | send.call(this, data); 278 | 279 | }; 280 | 281 | })(XMLHttpRequest); 282 | 283 | })(_$); 284 | 285 | }); 286 | 287 | } 288 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## version 2.13.0 (August 28, 2025) 2 | 3 | - added support for the [flags](https://www.php.net/manual/en/mysqli.real-connect.php#mysqli.real-connect.flags) argument in the [connect](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodconnect) method. 4 | - in support of the newly added argument, a new property was added - [ssl_options](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$ssl_options). setting this argument and using `MYSQLI_CLIENT_SSL` in the flags argument of the connect method will allow you to use SSL (encryption); see [#90](https://github.com/stefangabos/Zebra_Database/issues/90); thanks [Sisytec](https://github.com/Sisytec) for reporting! 5 | - fixed broken AJAX requests debugging 6 | 7 | ## version 2.12.1 (February 07, 2025) 8 | 9 | - fixed a deprecation warning on PHP 8.3+ which was also affecting how `NULL` values were handled; see [#89](https://github.com/stefangabos/Zebra_Database/issues/89); thanks [Sisytec](https://github.com/Sisytec) for reporting! 10 | 11 | ## version 2.12.0 (January 25, 2025) 12 | 13 | - fixed [#88](https://github.com/stefangabos/Zebra_Database/issues/88); thanks to [09173732546](https://github.com/09173732546) for reporting! 14 | - fixed [#83](https://github.com/stefangabos/Zebra_Database/issues/83); thanks [Bernhard Morgenstern](https://github.com/bmorg) for reporting! 15 | - the [debug_info](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$debug_info) property is now `protected` and can be used to customize and extend the debugging interface; see [#87](https://github.com/stefangabos/Zebra_Database/issues/87) 16 | - `halt_on_errors` now supports a new `always` value which, when set, will instruct the library to raise an exception on fatal errors regardless of the value of the `debug` property; see [#82](https://github.com/stefangabos/Zebra_Database/issues/82); thanks [Bernhard Morgenstern](https://github.com/bmorg) for sugesting! 17 | - `set_charset` doesn't force a connection anymore and it is now deferred until a "real" query is run so that the _lazy connection_ feature of the library is preserved; see [#80](https://github.com/stefangabos/Zebra_Database/issues/80) - thank you [Albatroon](https://github.com/Albatroon) for suggesting! 18 | - `ORDER BY` can now be used with the [dlookup](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methoddlookup) method; see [#20](https://github.com/stefangabos/Zebra_Database/issues/20), a request from 10 years ago! thanks [Andrew Rumm](https://github.com/rayzru) for suggesting this one! 19 | - improved detection of MySQL functions in values - thanks to Christian Hupfeld for reporting! 20 | - fixed some incorrect examples in the documentation 21 | 22 | ## version 2.11.1 (January 28, 2024) 23 | 24 | - version bump 25 | 26 | ## version 2.11.0 (January 27, 2024) 27 | 28 | - the library can now log queries run via AJAX requests; see the newly added [debug_ajax](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$debug_ajax) property 29 | - debug information is now also shown when running in CLI (when [debugging](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$debug) is enabled, of course) 30 | - added a new [debug_show_database_manager](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$debug_show_database_manager) property for editing queries in your favorite database manager 31 | - the "unsuccessful queries" tab is now open by default if there are any unsuccessful queries 32 | - fixed [#79](https://github.com/stefangabos/Zebra_Database/issues/76) where the library would try to connect to the database even when using lazy connection because of the logic in the `free_result` method; thanks to [Brian Hare](https://github.com/BHare1985) for reporting! 33 | - fixed an issue where having the `debug` property set to a `string` but debugging not being activated, would result in errors not being logged 34 | - fixed bug where the library would try to `EXPLAIN` queries that could not be explained; like `SHOW TABLE` for example; see [#76](https://github.com/stefangabos/Zebra_Database/issues/76) - thank you [cosinus90](https://github.com/cosinus90)! 35 | - fixed potential warnings being thrown in PHP 8; see [#74](https://github.com/stefangabos/Zebra_Database/pull/74) - thank you [Rémi](https://github.com/Revine-dev)! 36 | - fixed a potential issue when encountering connection errors 37 | - updated the CSS and the icons for the debug interface 38 | 39 | ## version 2.10.2 (May 13, 2022) 40 | 41 | - fixed a deprecation warning shown in PHP 8.1+; see [#70](https://github.com/stefangabos/Zebra_Database/issues/70), thanks [Harry](https://github.com/Dibbyo456) 42 | - fixed a potential bug with `INC` keyword being incorrectly detected in strings looking like `INC(foo)` 43 | - fixed EXPLAIN not working in the debug console 44 | - fixed debug console being always shown once enabled via string 45 | 46 | ## version 2.10.1 (January 07, 2021) 47 | 48 | - fixed bug introduced in previous release, for the [implode](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodimplode) method; see [#65](https://github.com/stefangabos/Zebra_Database/issues/65), thanks [pbm845](https://github.com/pbm845) 49 | 50 | ## version 2.10.0 (December 23, 2020) 51 | 52 | - added option for enabling debugging on the fly via a query string parameter - see [documentation](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$debug) 53 | - added support for caching query results to a [redis](https://redis.io/) server 54 | - the default value of [disable_warnings](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$disable_warnings) is now FALSE 55 | - updated German language; thanks to [Bernhard Morgenstern](https://github.com/bmorg)! 56 | - major documentation overhaul 57 | 58 | ## version 2.9.14 (April 30, 2020) 59 | 60 | - fixed bug with XSS in the debug console; see [#62](https://github.com/stefangabos/Zebra_Database/issues/62) 61 | - fixed incorrect handling of `NULL` values; see [#60](https://github.com/stefangabos/Zebra_Database/issues/60) 62 | - the `global` section in the debugging console can now be disabled or configured to show only specific subsections via the newly added [debug_show_globals](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$debug_show_globals) property; see [#59](https://github.com/stefangabos/Zebra_Database/issues/59) 63 | - fixed bug with setting the caching method to "memcache" but not having memcache properly set up, or setting up memcache but not having the caching method set to "memcache" 64 | - minor layout updates for the debugging console 65 | 66 | ## version 2.9.13 (February 29, 2020) 67 | 68 | - fixed a bug where the library would incorrectly handle MySQL functions in certain scenarios 69 | - fixed [#57](https://github.com/stefangabos/Zebra_Database/issues/57) where in PHP 7.4.0 a warning was shown about `get_magic_quotes_gpc` function being deprecated; thanks [userc479](https://github.com/userc479) for reporting! 70 | - added the `return_error_number` argument to the [error()](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methoderror) method 71 | - added property [auto_quote_replacements](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$auto_quote_replacements) allowing to disable the library's default behavior of automatically quoting escaped values 72 | 73 | ## version 2.9.12 (January 16, 2019) 74 | 75 | - the [insert_bulk](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodinsert_bulk) method now supports INSERT IGNORE and INSERT...ON DUPLICATE KEY UPDATE; this fixes [#42](https://github.com/stefangabos/Zebra_Database/issues/42) and deprecates the `insert_update` method 76 | - the [insert](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodinsert) method now also supports INSERT...ON DUPLICATE KEY UPDATE - this slightly changes the functionality of the method's 3rd agument but stays compatible with previous versions of the library 77 | - fixed [#47](https://github.com/stefangabos/Zebra_Database/issues/47) where setting `log_path` property to a full path to a file with extension would not change the log file's name, as stated in the documentation 78 | - fixed [#37](https://github.com/stefangabos/Zebra_Database/issues/37) where unsuccessful queries were not written to the log file 79 | - fixed bug when the first argument for `fetch_assoc_all` and `fetch_obj_all` methods was skipped 80 | - logs can now be handled via a custom callback function instead of being written to a log file, by setting the [$log_path](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$log_path) property; this answers [#48](https://github.com/stefangabos/Zebra_Database/issues/48) 81 | - changed output written to the log files which is now less verbose, cleaner and taking up less space 82 | - dates in log files are now in Y-m-d format instead of Y M d 83 | - changed how entries are separated in the log file 84 | - updated minimum required PHP version from 5.2.0 to 5.4.0. This fixes [#44](https://github.com/stefangabos/Zebra_Database/issues/44) 85 | 86 | ## version 2.9.11 (June 19, 2018) 87 | 88 | - fixed issue [#43](https://github.com/stefangabos/Zebra_Database/issues/43) where some strings were incorrectly detected as MySQL functions 89 | - fixed issue [#45](https://github.com/stefangabos/Zebra_Database/issues/45) where the [table_exists](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodtable_exists) method was always returning `true` 90 | - fixed issue [#46](https://github.com/stefangabos/Zebra_Database/issues/46) where the [select_database](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodselect_database) was always returning `false` 91 | - fixed issue [#49](https://github.com/stefangabos/Zebra_Database/issues/49) 92 | - fixed issue [#50](https://github.com/stefangabos/Zebra_Database/issues/50) where MySQL functions were incorrectly recognized 93 | - source code improvements 94 | 95 | ## version 2.9.10 (December 03, 2017) 96 | 97 | - updated Russian translation; thanks [@rayzru](https://github.com/rayzru)! 98 | - fixed bug with MySQL functions not being properly handled by the [select](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodselect) method when the `columns` argument was given as an array 99 | - improved documentation for the [select](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodselect) method 100 | - fixed an issue that would trigger an error if other PHP scripts were including the [SqlFormatter](http://github.com/jdorn/sql-formatter) library 101 | - added support for using the `AS` keyword in the `columns` argument for the [select](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodselect) method. Fixes [#34](https://github.com/stefangabos/Zebra_Database/issues/34). 102 | 103 | ## version 2.9.9 (May 21, 2017) 104 | 105 | - unnecessary files are no more included when downloading from GitHub or via Composer 106 | 107 | ## version 2.9.7 (May 10, 2017) 108 | 109 | - fixed a bug introduced in the previous release where `*` character could not be used anymore in the [select()](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodselect) method 110 | - documentation is now available in the repository and on GitHub 111 | - the home of the library is now exclusively on GitHub 112 | 113 | ## version 2.9.6 (May 01, 2017) 114 | 115 | - the debugging console is not shown when AJAX requests are detected 116 | - fixed a bug where executing unbuffered queries was generating warnings 117 | - improved the MySQL function recognition pattern and added all MySQL functions as per [MySQL's documentation](https://dev.mysql.com/doc/refman/5.7/en/func-op-summary-ref.html) 118 | - source code tidying 119 | 120 | ## version 2.9.5 (April 11, 2017) 121 | 122 | > This version somewhat break the compatibility with previous versions! To fix things, you will need to remove the call to the *show_debug_console* method as now the debugging console is automatically shown when script execution ends. If you were using the *write_log* method than you will need to remove the call to it and refer to the *debug* property for more information. 123 | 124 | - added support for [unbuffered queries](http://php.net/manual/en/mysqlinfo.concepts.buffering.php) 125 | - the debugging console is now automatically shown when script execution ends without the need to manually show it; as a consequence the *show_debug_console* and *halt* methods were removed 126 | - the [debug](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$debug) property can now be also an array (instead of just boolean) instructing the library to log debug information instead of showing it on the screen - as a consequence, the *write_log* method was removed 127 | - renamed the *console_show_records* to [debug_show_records](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$debug_show_records) 128 | - EXPLAIN and backtrace information can now be disabled from the debugging interface with the newly added [debug_show_explain](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$debug_show_explain) and [debug_backtrace](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$debug_show_backtrace) properties 129 | - added a new [option](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodoption) method for setting [connection options](http://php.net/manual/en/mysqli.options.php#refsect1-mysqli.options-parameters) 130 | - *database* argument is now optional in the [connect](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodconnect) method; additionally, an explicit selection of a database is not required anymore as now, in all the methods where required, you can prefix table names with database name, like *database.tablename* 131 | - the argument for the [free_result](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodfree_result) method is now optional and the last used resource will be used if not specified, just like for the rest of the methods requiring a result 132 | - fixed a bug where setting the *calc_rows* argument to *TRUE* for the [query](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodquery) method, and having a query starting with a comment would have no effect 133 | - fixed a bug with queries being always reported as being from cache in the log files written 134 | - backtrace information is not written to the log files by default anymore; in can be enabled by setting the [debug](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#var$debug) property to an array 135 | - query execution time is now shown in the debugging console in seconds rather than milliseconds 136 | - pressing *ESC* now closes the debugging console 137 | - changed the occurrences of PHP's [each()](http://php.net/manual/en/function.each.php) function which is being deprecated starting with PHP 7.2.0 138 | - **lots** of source code optimizations and documentation updates 139 | 140 | ## version 2.9.4 (April 01, 2017) 141 | 142 | - fixed a bug where a new connection could not be made after using the [close](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodclose) method 143 | - fixed an issue with the memcache warning message appearing even if no memcache extension was available; thanks **Jeff Buckles** 144 | - the library now supports unbuffered queries using the newly added [query_unbuffered](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodquery_unbuffered) method 145 | - added a new [select_database](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodselect_database) method (as a side-effect, the "database" argument is not mandatory anymore for the [connect](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodconnect) method) 146 | - added *DEFAULT* to the list of known MySQL functions; thanks **Jeff Buckles** 147 | 148 | ## version 2.9.3 (February 19, 2016) 149 | 150 | - fixed an issue that would trigger a warning if a replacement value was an array instead of a string 151 | - fixed a bug where "fetch_obj_all" method would fail if the "index" argument was given; thanks **Milan Kvita** 152 | - minimum required PHP version is now 5.2.0 instead of 5.0.0 153 | 154 | ## version 2.9.2 (January 07, 2016) 155 | 156 | - the library now uses [SqlFormatter](https://github.com/jdorn/sql-formatter) PHP library by [Jeremy Dorn](https://github.com/jdorn) for better highlighting of SQL queries 157 | - the debug console got a few minor visual tweaks 158 | 159 | ## version 2.9.1 (January 04, 2016) 160 | 161 | - added 2 new methods: [error](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methoderror) and [free_result](https://stefangabos.github.io/Zebra_Database/Zebra_Database/Zebra_Database.html#methodfree_result) 162 | - MySQL functions can now be used when setting values in "insert", "insert_update", "insert_bulk" and "update" methods 163 | - fixed caching not working anymore since 2.9.0; thanks **Andrew Rumm** 164 | 165 | ## version 2.9.0 (December 07, 2015) 166 | 167 | - fixed warnings that would be triggered on PHP 5.5+; thanks to **Andrew Rumm** 168 | 169 | ## version 2.8.8 (February 13, 2015) 170 | 171 | - more fixes for warning when no queries are run 172 | 173 | ## version 2.8.7 (February 10, 2015) 174 | 175 | - added a new "get_selected_database" method for getting the name of the currently selected database; thanks to **Stijn** for suggesting 176 | - fixed a bug in the library's destructor method where mysqli_free_result would trigger a notice in your server's log files if the result was not a mysqli result set (as it could also be a boolean for queries that do not return anything or even unset when no queries were run); thanks to **Eike Broda** 177 | - fixed a bug with the log files where the log files were recreated at each execution rather than being updated; thanks to **Eike Broda** 178 | - added Russian language file; thanks to **Andrew Rumm** 179 | - fixed an issue where the library would not check if an email address was set when sending out notifying emails for queries that take too long to execute; thanks to **Andrew Rumm** 180 | 181 | ## version 2.8.6 (November 25, 2014) 182 | 183 | - simpler usage of WHERE-IN conditions; previously you had to use the implode method but now, when an array is given as a replacement item, this method will be automatically used for you; thanks to **primenic** 184 | - updates to the German language file; thanks to **Eike Broda** 185 | 186 | ## version 2.8.5 (November 12, 2014) 187 | 188 | - result sets can now also be cached in sessions 189 | - users can now change resource paths, allowing users to move scripts and stylesheets to whatever locations; thanks to **Joseph Spurrier** 190 | - column names for the select() method can now be given as an array (recommended) and will be automatically enclosed in grave accents; ; thanks to **Joseph Spurrier** 191 | - fixed a bug with determining the path to the library's CSS and JavaScript when using symlinks; thanks to **primeinc** 192 | - fixed a bug with backslashes in the replacement strings; thanks to **primeinc** 193 | - fixed a bug in the "get_tables" method which would trigger warning messages; thanks to **Stefan L** for reporting 194 | - fixed bug with dlookup method & friends and caching 195 | - fixed composer.json and the library is now correctly working with Composer's autoloader; thanks to **Joseph Spurrier** 196 | - fixed a bug where setting the "disable_warnings" property to true would not actually disable warning; thanks to **Joseph Spurrier** 197 | - fixed a bug where setting the "memcache_compressed" property to TRUE had no effect; thanks to **Andrew Rumm** 198 | - fixed a bug with CSS/JS when port is not 80 where the library would not correctly look for the CSS and JS files (used by the debug console) when using a port other than 80; thanks to **Gabriel Moya**! 199 | - fixed a bug where port and socket were not used even if set; thanks to **Nam Trung** 200 | - fixed some typos in comments 201 | 202 | ## version 2.8.4 (December 22, 2013) 203 | 204 | - added a new "memcache_key_prefix" property; this allows separate caching of the same queries by multiple instances of the libraries on the same memcache server, or the library handling multiple domains on the same memcache server; thanks to **primeinc** 205 | - fixed a bug with the insert_bulk method; thanks to **Guillermo** 206 | 207 | ## version 2.8.3 (October 10, 2013) 208 | 209 | - fixed a bug with the connection credentials which were stored in a public property rather than a private one; thanks **etb09** 210 | - fixed a bug with the output generated by the "write_log" method; thanks **etb09** 211 | - fixed a bug where the "insert_bulk" and "write_log" methods would trigger a "strict standards" warning in PHP 5.4+; thanks **etb09** 212 | - fixed a minor issue that could cause the library to trigger a warning (only when debugging was on) 213 | - log files can now be generated by date and hour; thanks **etb09** 214 | - entries in log files now have correct indentation regardless of the used language file 215 | - added German translation; thanks **etb09** 216 | 217 | ## version 2.8.2 (August 24, 2013) 218 | 219 | - table names are now enclosed in grave accents for all the methods that take a table name as argument: dcount, delete, dlookup, dmax, dsum, get_table_columns, insert, insert_bulk, insert_update, select, truncate and update; this allows working with tables having special characters in their name 220 | - minor performance optimizations in the debug console's JavaScript code 221 | 222 | ## version 2.8.1 (August 02, 2013) 223 | 224 | - fixed an issue introduced in the previous release generated by the changing of the arguments order in the "query" method and which affected some of the library's methods; thanks **ziggy** 225 | - inversed the order of last 2 arguments of the "select" method ("calc_rows" now comes before "highlight"); if you were using this argument make sure to addapt your code; thanks **Prathamesh Gharat** 226 | - fixed a bug where when setting a query to be highlighted the debugging console would actually open and show the respective query *only* if the library's "minimize_console" attribute was set to FALSE; now it will work either way 227 | - the "connect" method has 2 new arguments: "port" and "socket"; because of this, the "connect" argument is now the last argument of the method so make sure you update your code if you have to; thanks **Corey** 228 | - proper declaration private and public variables 229 | - some performance tweaks 230 | - fixed some issues in the documentation; thanks **Mark** 231 | - the project is now available on [GitHub](https://github.com/stefangabos/Zebra_Database) and also as a [package for Composer](https://packagist.org/packages/stefangabos/zebra_database) 232 | 233 | ## version 2.8 (March 15, 2013) 234 | 235 | - dropped support for PHP 4; minimum required version is now PHP 5 236 | - dropped support for PHP's *mysql* extension, which is [officially deprecated as of PHP v5.5.0](http://php.net/manual/en/changelog.mysql.php) and will be removed in the future; the extension was originally introduced in PHP v2.0 for MySQL v3.23, and no new features have been added since 2006; the library now relies on PHP's [mysqli](http://php.net/manual/en/book.mysqli.php) extension 237 | - removed the "is_new" argument from the "connect" method as it's not needed by the mysqli extension; this means that "connect" is now the 5th argument and that this may break your code so be sure to update accordingly 238 | - inversed the order of last 2 arguments of the "query" method ("calc_rows" now comes before "highlight"); if you were using this argument make sure to addapt your code or risk opening a black-hole 239 | - changed an instance of mktime() to time() as it was giving a "PHP Strict Standards" error since PHP 5.3.0 240 | - fixed a bug when specifying custom paths for the "write_log" method ; thanks **Andrei Bodeanschi** 241 | - fixed an issue where setting "cache_path" to a path without trailing slash would break the script 242 | - fixed an issue where setting the caching time to 0 would still create (empty) cache files 243 | - the JS and CSS files used by the debugger window are now loaded "protocol-less" in order to solve those mixed-content errors; thanks to **Mark Bjaergager** 244 | - tweaked the CSS file a bit 245 | 246 | ## version 2.7.3 (July 16, 2012) 247 | 248 | - the library now tries to write errors to the system log (if PHP is configured so; read more [here](http://www.php.net/manual/en/errorfunc.configuration.php#ini.log-errors)) when the "debug" property is set to FALSE (as when the "debug" property is set to TRUE the error messages are reported in the debug console) 249 | - the library will now show a warning message in the debug console if the "memcache" extension is loaded but it is not used 250 | - cached data is now gzcompress-ed and base64_encoded which means cached data is a bit more secure and a bit more faster to load; thanks to **PunKeel** for suggesting this a while ago 251 | - changed (again!) the order of the arguments for the "select" method - "limit" now comes before "order"; note that this update made the select() method backward incompatible and that you will have to change the order of the arguments for this to work! 252 | - added a small example on how to use caching through memcache; see the documentation for the "caching_method" property 253 | 254 | ## version 2.7.2 (April 07, 2012) 255 | 256 | - fixed a bug that most likely appeared since 2.7, where the "seek" method (and any method relying on it, like all the "fetch" methods) would produce a warning in the debug console if there were no records in the sought resource 257 | - fixed a bug where NULL could not be used in the "replacements" array of a query; thanks to **בניית אתרים** 258 | 259 | ## version 2.7.1 (February 10, 2012) 260 | 261 | - the select() method took arguments in a different order than specified in the documentation; thanks to **Andrei Bodeanschi**; note that this update made the select() method backward incompatible and that you will have to change the order of the arguments for this to work! 262 | - fixed a bug where the update() and insert_update() methods were not working if in the array with the columns to update, the INC() keyword was used with a replacement marker instead of a value, and the actual value was given in the replacements array; thanks to **Andrei Bodeanschi** 263 | - fixed a bug where the insert_update() method was not working when the only update field used the INC() keyword; the generated query contained an invalid comma between UPDATE and the field name; thanks to **Allan** 264 | 265 | ## version 2.7 (January 07, 2012) 266 | 267 | - added support for caching query results using memcache. thanks to **Balazs** for suggesting it a while ago, and to **Ovidiu Mihalcea** for introducing me to memcache 268 | - fixed a bug where the script would crash if the object was instantiated more than once and the *language* method was being called for each of the instances; thanks to **Edy Galantzan** 269 | - completely rewritten the dlookup method which was not working correctly if anything else than a comma separated list of column names was used (like an expression, for example); thanks to **Allan** 270 | - the "connect" method can now take an additional argument instructing it to connect to the database right away rather than using a "lazy" connection 271 | - fixed a bug where some of the elements in the debug console were incorrectly inheriting the page's body color 272 | 273 | ## version 2.6 (September 03, 2011) 274 | 275 | - changed the name of "get_columns" method to "get_table_columns" as it returned the number of columns in a given table, and added a new "get_columns" method which takes as argument a resource and returns the number of columns in the given resource 276 | - some documentation clarifications 277 | 278 | ## version 2.5 (July 02, 2011) 279 | 280 | - a new method is now available: "get_link" which returns the MySQL link identifier associated with the current connection to the MySQL server. Why as a separate method? Because the library uses "lazy connection" (it is not actually connecting to the database until first query is executed) there's no link identifier available when calling the *connect* method. 281 | - a new argument is now available for the *insert* and *insert_bulk* methods which allows the creation of INSERT IGNORE queries which will skip records that would cause a duplicate entry for a primary key. 282 | - the default value of the "debug" property was set to FALSE 283 | 284 | ## version 2.4 (June 20, 2011) 285 | 286 | - fixed a bug with the *insert_bulk* method (thanks to **Edy Galantzan** for reporting) 287 | - added a new method: *table_exists* which checks to see if a table with the name given as argument exists in the database 288 | - the *select* method now also accepts *limit* and *order* arguments; due to this change, this method is not compatible with previous versions (thanks to **Monil** for suggesting this) 289 | - some documentation refinements 290 | 291 | ## version 2.3 (April 15, 2011) 292 | 293 | - fixed a bug where the script would generate a warning if the "update" method was called with invalid arguments 294 | - changed how the *insert_bulk* method needs to receive arguments, making it more simple to use 295 | 296 | ## version 2.2 (March 05, 2011) 297 | 298 | - fixed a bug where the "select" method war returning a boolean value rather than a resource (thanks to **Monil**) 299 | - the class now uses "lazy connection" meaning that it will not actually connect to the database until the first query is run 300 | - the debug console now shows also session variables 301 | - the "show_debug_console" method can now be instructed to return output rather than print it to the screen 302 | - the highlighter now highlights more keywords 303 | - improved documentation for the "connect" method 304 | 305 | ## version 2.1 (January 27, 2011) 306 | 307 | - fixed a bug where the console inherited CSS properties from the parent application 308 | - fixed some bugs in the JavaScript file that would break the code when parent application was running MooTools 309 | - transactions are now supported 310 | - added a new "insert_bulk" method which allows inserting multiple values into a table using a single query (thanks **Sebi P.** for the suggestion) 311 | - added a new "insert_update" method which will create INSERT statements with ON DUPLICATE UPDATE (thank **Sebi P.** for the suggestion) 312 | - enhanced the "update" method 313 | - the debug console now shows a warning if no charset and collation was specified 314 | - corrections to the documentation 315 | 316 | ## version 2.0 (January 21, 2011) 317 | 318 | - the entire code was improved and some of the properties as well as method names were changed and, therefore, this version breaks compatibility with earlier versions 319 | - fixed a bug where the script would try to also cache action queries; thanks to **Romulo Gomez** 320 | - fixed a bug in the "seek" method 321 | - fixed a bug where on some configurations of Apache/PHP the script would not work 322 | - fixed a bug where if there was a connection error or MySQL generated an error and the debug console was minimized, it would not be shown automatically 323 | - fixed a bug where the "dlookup" method would not return escaped column names (i.e. `order`) 324 | - fixed a bug where the "found_rows" property was incorrect for cached queries 325 | - fixed a bug where the debug console would improperly manage columns enclosed in ` (backtick) 326 | - fixed a bug that caused improper display of some strings in the debug console 327 | - added a new method "select" - a shorthand for selecting queries 328 | - added a new method "get_columns" - returns information about a given table's columns 329 | - added a new method "implode" - similar to PHP's own implode() function, with the difference that this method "escapes" imploded elements and also encloses them in grave accents 330 | - added a new method "set_charset" - sets the characters set and the collation of the database 331 | - improved functionality of fetch_assoc_all() and fetch_obj_all() methods 332 | - the debug console shows more information and in a much better and organized way 333 | - rewritten the method for logging queries to a txt file making the output very easy to read 334 | - dropped the XTemplate templating engine in order to improve speed; every aspect of the debug console can still be changed through the CSS file 335 | 336 | ## version 1.1.4 (July 24, 2008) 337 | 338 | - fixed a bug in the update() method when calling the method without the replacements argument 339 | 340 | ## version 1.1.3 (June 02, 2008) 341 | 342 | - fixed a bug that was causing an E_WARNING error when the caching folder could not be found 343 | - fixed a minor issue that would trigger an error message if replacements were specified even though there was nothing to replace 344 | - documentation is now clearer on how to define the caching folder 345 | 346 | ## version 1.1.2b (May 08, 2008) 347 | 348 | - fixed a huge bug in the highlighter when using $replacements 349 | - fixed an issue where when calling a function/method that executes a method of the class by using of call_user_func_array() and friends, will produce a warning message due to the fact that, in such cases, the information returned by debug_backtrace() function is incomplete 350 | - fixed a small issue in the template file that would produce an odd output when not having anything in the "messages" section 351 | 352 | ## version 1.1.2 (April 01, 2008) 353 | 354 | - fixed a bug where the debug console's position could be influenced by the host application's stylesheet 355 | - fixed a minor bug in the "log_debug_info" method 356 | - fixed a few minor bugs 357 | - added new methods - "fetch_assoc_all" and "fetch_obj_all" which will fetch all the rows in a record set as an associative array or an array of objects respectively; "get_tables" - returns all the tables in the currently used database; "get_table_status" - returns useful information on all or only on specific tables; "optimize" - automatically optimizes tables that have overhead (unused, lost space) 358 | - full backtrace is now available in the debug console 359 | - debug console is now a bit smarter and it does not highlight keywords in strings; also knows some more MySQL keywords 360 | - more accurate reporting of duplicate queries 361 | - better error reporting for when not being able to connect to the MySQL server or select a database 362 | 363 | ## version 1.1.1 (December 03, 2007) 364 | 365 | - fixed a bug in the close() method; thanks **mokster** 366 | - some documentation refinements (thanks to Vincent van Daal) 367 | 368 | ## version 1.1.0 (September 15, 2007) 369 | 370 | - fixed a bug where calling on a fetch method and on a cached query would send the script into an infinite loop 371 | - improved the speed of the "dlookup" method by adding LIMIT to it; thanks to **A.Leeming** 372 | - added some new methods: "close" (alias of mysql_close()), "log_debug_info" which writes debug information to a log file, and "seek" - alias of mysql_data_seek() 373 | - the "connect" method now returns the link identifier of the connection, which can be later used for closing the connection with the "close" method 374 | - the "connect" method now returns the link identifier of the connection, which can be later used for closing the connection with the "close" method 375 | - some documentation refinements; thanks **Vincent van Daal** 376 | - completely rewritten debug console's template file and stylesheet 377 | 378 | ## version 1.0.9 (May 30, 2007) 379 | 380 | - fixed a bug where the script would crash upon executing queries like SHOW TABLES, DESCRIBE, etc 381 | - fixed a bug where due to a typo, no error message was shown if database could not be selected 382 | - fixed a bug with $replacements containing apostrophes 383 | - queries can now be cached 384 | - new methods were added: "delete", "truncate", "insert" and "update" which are 385 | - previously, all records returned by a SELECT query were shown in the debug console and that could crash the script if there were queries returning LOTS of rows; now there's a new property called "showMaxRows" instructing the script on how many rows returned by SELECT queries to be shown in the debug console (thanks **Dee S.**) 386 | - the "debug" property is now TRUE by default 387 | - lots of code cleanups and documentation refinements 388 | 389 | ## version 1.0.8 (January 28, 2007) 390 | 391 | - THIS VERSION BREAKS COMPATIBILITY WITH PREVIOUS ONES! 392 | - fixed a bug with $replacements containing question marks; thanks **Joeri** 393 | - the debug console now shows backtracing information 394 | - a new method was added: "fetch_obj" which is an alias of MySQL's mysql_fetch_object function 395 | - warnings of duplicate queries were incorrectly being displayed in the console window 396 | 397 | ## version 1.0.7 (November 24, 2006) 398 | 399 | - the debug console now also shows the result of EXPLAIN for SELECT queries 400 | - queries can now be highlighted in debug console by setting the newly added $highlight argument for most of the methods 401 | - the debug console now reports if two or more queries returned the same records (previously this feature was based on comparing the MySQL statements rahter than on the returned records) 402 | - a new "halt" method was added; this stops the execution of the script at the line where is called and displays the debug console (if the "debug" property is set to TRUE and the viewer's IP address is in the allowed range set by the "debuggerIP" property) 403 | - fixed some Java Script issues with the debug console 404 | - some code cleanup and documentation refinements 405 | 406 | ## version 1.0.6 (October 21, 2006) 407 | 408 | - fixed a bug where it was not possible to connect to two different databases by running two instances of this class in the same script 409 | - fixed a bug where specifying two or more columns to be returned by the "dlookup" method would produce a warning if the column names were separated by a comma AND a space; there were no warnings if there was NO SPACE just one comma separating the column names 410 | - fixed a bug where the "haltOnErrors" property was not implemented correctly and the script would actually be halted only on a few exceptional cases 411 | - fixed a bug where the language could not be changed from the default one 412 | - added a new method "setLanguage" 413 | - the debug console can be instructed to appear only for a specific IP address 414 | - the debug console now displays the actual returned rows for SELECT queries (thanks **Zed**) 415 | - each entry of each tab in the debug console can now be collapsed/expanded 416 | 417 | ## version 1.0.5 (October 02, 2006) 418 | 419 | - fixed a bug where the "insert_id()" method was not returning correct values (thanks arlc) 420 | - introduced a new boolean property called "haltOnErrors"; when set to TRUE the execution of the script will be halted upon fatal errors and the debug console will be shown (if the "debug" property is set to TRUE) 421 | - the debug console now highlights more keywords 422 | - the debug console now shows MySQL keywords in upper case 423 | 424 | ## version 1.0.4 (September 03, 2006) 425 | 426 | - fixed a bug where the "affectedRows" property always returned 1 427 | - fixed a bug where query execution time was incorrect in PHP 4 428 | - fixed a bug where execution time was also computed for unsuccessful queries 429 | - symbols are now highlighted in the debug console 430 | - added a new method: "insert_id", alias of mysql_insert_id() function 431 | - the debug console now shows the number of returned rows for SELECT queries; this information is also available by reading the newly added "returnedRows" property 432 | - the debug console can now be minimized both in real-time or by default by setting the newly added "minimizeDebugger" property 433 | - the debug console shows queries in a more readable way 434 | - the debug console now highlights more keywords 435 | - tweaked various aesthetical aspects of the debug console 436 | - debug information now can be logged to a file instead of being outputted to the screen 437 | 438 | ## version 1.0.3 (August 12, 2006) 439 | 440 | - the $_FILES and $_SESSION superglobals are now also shown in the debug console 441 | - properties now have default values in PHP 4 442 | 443 | ## version 1.0.2 (August 10, 2006) 444 | 445 | - the debug console will now report if the same query was run more than once 446 | - fixed a bug where the "_connected" method was reporting errors to the debug console; because it is a private method, it should report to the method that called it 447 | - an example is now available in the downloadable package 448 | 449 | ## version 1.0.1 (August 05, 2006) 450 | 451 | - the "escape_string" method now escapes any values - in previous version it didn't escape numbers 452 | 453 | ## version 1.0 (July 20, 2006) 454 | 455 | - initial release 456 | -------------------------------------------------------------------------------- /includes/SqlFormatter.php: -------------------------------------------------------------------------------- 1 | 8 | * @author Florin Patan 9 | * @copyright 2013 Jeremy Dorn 10 | * @license http://opensource.org/licenses/MIT 11 | * @link http://github.com/jdorn/sql-formatter 12 | * @version 1.2.18 13 | */ 14 | class SqlFormatter 15 | { 16 | // Constants for token types 17 | const TOKEN_TYPE_WHITESPACE = 0; 18 | const TOKEN_TYPE_WORD = 1; 19 | const TOKEN_TYPE_QUOTE = 2; 20 | const TOKEN_TYPE_BACKTICK_QUOTE = 3; 21 | const TOKEN_TYPE_RESERVED = 4; 22 | const TOKEN_TYPE_RESERVED_TOPLEVEL = 5; 23 | const TOKEN_TYPE_RESERVED_NEWLINE = 6; 24 | const TOKEN_TYPE_BOUNDARY = 7; 25 | const TOKEN_TYPE_COMMENT = 8; 26 | const TOKEN_TYPE_BLOCK_COMMENT = 9; 27 | const TOKEN_TYPE_NUMBER = 10; 28 | const TOKEN_TYPE_ERROR = 11; 29 | const TOKEN_TYPE_VARIABLE = 12; 30 | 31 | // Constants for different components of a token 32 | const TOKEN_TYPE = 0; 33 | const TOKEN_VALUE = 1; 34 | 35 | // Reserved words (for syntax highlighting) 36 | protected static $reserved = array( 37 | 'ACCESSIBLE', 'ACTION', 'AGAINST', 'AGGREGATE', 'ALGORITHM', 'ALL', 'ALTER', 'ANALYSE', 'ANALYZE', 'AS', 'ASC', 38 | 'AUTOCOMMIT', 'AUTO_INCREMENT', 'BACKUP', 'BEGIN', 'BETWEEN', 'BINLOG', 'BOTH', 'CASCADE', 'CASE', 'CHANGE', 'CHANGED', 'CHARACTER SET', 39 | 'CHARSET', 'CHECK', 'CHECKSUM', 'COLLATE', 'COLLATION', 'COLUMN', 'COLUMNS', 'COMMENT', 'COMMIT', 'COMMITTED', 'COMPRESSED', 'CONCURRENT', 40 | 'CONSTRAINT', 'CONTAINS', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_TIMESTAMP', 'DATABASE', 'DATABASES', 'DAY', 'DAY_HOUR', 'DAY_MINUTE', 41 | 'DAY_SECOND', 'DEFAULT', 'DEFINER', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 42 | 'DO', 'DUMPFILE', 'DUPLICATE', 'DYNAMIC', 'ELSE', 'ENCLOSED', 'END', 'ENGINE', 'ENGINE_TYPE', 'ENGINES', 'ESCAPE', 'ESCAPED', 'EVENTS', 'EXEC', 43 | 'EXECUTE', 'EXISTS', 'EXPLAIN', 'EXTENDED', 'FAST', 'FIELDS', 'FILE', 'FIRST', 'FIXED', 'FLUSH', 'FOR', 'FORCE', 'FOREIGN', 'FULL', 'FULLTEXT', 44 | 'FUNCTION', 'GLOBAL', 'GRANT', 'GRANTS', 'GROUP_CONCAT', 'HEAP', 'HIGH_PRIORITY', 'HOSTS', 'HOUR', 'HOUR_MINUTE', 45 | 'HOUR_SECOND', 'IDENTIFIED', 'IF', 'IFNULL', 'IGNORE', 'IN', 'INDEX', 'INDEXES', 'INFILE', 'INSERT', 'INSERT_ID', 'INSERT_METHOD', 'INTERVAL', 46 | 'INTO', 'INVOKER', 'IS', 'ISOLATION', 'KEY', 'KEYS', 'KILL', 'LAST_INSERT_ID', 'LEADING', 'LEVEL', 'LIKE', 'LINEAR', 47 | 'LINES', 'LOAD', 'LOCAL', 'LOCK', 'LOCKS', 'LOGS', 'LOW_PRIORITY', 'MARIA', 'MASTER', 'MASTER_CONNECT_RETRY', 'MASTER_HOST', 'MASTER_LOG_FILE', 48 | 'MATCH','MAX_CONNECTIONS_PER_HOUR', 'MAX_QUERIES_PER_HOUR', 'MAX_ROWS', 'MAX_UPDATES_PER_HOUR', 'MAX_USER_CONNECTIONS', 49 | 'MEDIUM', 'MERGE', 'MINUTE', 'MINUTE_SECOND', 'MIN_ROWS', 'MODE', 'MODIFY', 50 | 'MONTH', 'MRG_MYISAM', 'MYISAM', 'NAMES', 'NATURAL', 'NOT', 'NOW()','NULL', 'OFFSET', 'ON', 'OPEN', 'OPTIMIZE', 'OPTION', 'OPTIONALLY', 51 | 'ON UPDATE', 'ON DELETE', 'OUTFILE', 'PACK_KEYS', 'PAGE', 'PARTIAL', 'PARTITION', 'PARTITIONS', 'PASSWORD', 'PRIMARY', 'PRIVILEGES', 'PROCEDURE', 52 | 'PROCESS', 'PROCESSLIST', 'PURGE', 'QUICK', 'RANGE', 'RAID0', 'RAID_CHUNKS', 'RAID_CHUNKSIZE','RAID_TYPE', 'READ', 'READ_ONLY', 53 | 'READ_WRITE', 'REFERENCES', 'REGEXP', 'RELOAD', 'RENAME', 'REPAIR', 'REPEATABLE', 'REPLACE', 'REPLICATION', 'RESET', 'RESTORE', 'RESTRICT', 54 | 'RETURN', 'RETURNS', 'REVOKE', 'RLIKE', 'ROLLBACK', 'ROW', 'ROWS', 'ROW_FORMAT', 'SECOND', 'SECURITY', 'SEPARATOR', 55 | 'SERIALIZABLE', 'SESSION', 'SHARE', 'SHOW', 'SHUTDOWN', 'SLAVE', 'SONAME', 'SOUNDS', 'SQL', 'SQL_AUTO_IS_NULL', 'SQL_BIG_RESULT', 56 | 'SQL_BIG_SELECTS', 'SQL_BIG_TABLES', 'SQL_BUFFER_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_LOG_BIN', 'SQL_LOG_OFF', 'SQL_LOG_UPDATE', 57 | 'SQL_LOW_PRIORITY_UPDATES', 'SQL_MAX_JOIN_SIZE', 'SQL_QUOTE_SHOW_CREATE', 'SQL_SAFE_UPDATES', 'SQL_SELECT_LIMIT', 'SQL_SLAVE_SKIP_COUNTER', 58 | 'SQL_SMALL_RESULT', 'SQL_WARNINGS', 'SQL_CACHE', 'SQL_NO_CACHE', 'START', 'STARTING', 'STATUS', 'STOP', 'STORAGE', 59 | 'STRAIGHT_JOIN', 'STRING', 'STRIPED', 'SUPER', 'TABLE', 'TABLES', 'TEMPORARY', 'TERMINATED', 'THEN', 'TO', 'TRAILING', 'TRANSACTIONAL', 'TRUE', 60 | 'TRUNCATE', 'TYPE', 'TYPES', 'UNCOMMITTED', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'USAGE', 'USE', 'USING', 'VARIABLES', 61 | 'VIEW', 'WHEN', 'WITH', 'WORK', 'WRITE', 'YEAR_MONTH' 62 | ); 63 | 64 | // For SQL formatting 65 | // These keywords will all be on their own line 66 | protected static $reserved_toplevel = array( 67 | 'SELECT', 'FROM', 'WHERE', 'SET', 'ORDER BY', 'GROUP BY', 'LIMIT', 'DROP', 68 | 'VALUES', 'UPDATE', 'HAVING', 'ADD', 'AFTER', 'ALTER TABLE', 'DELETE FROM', 'UNION ALL', 'UNION', 'EXCEPT', 'INTERSECT' 69 | ); 70 | 71 | protected static $reserved_newline = array( 72 | 'LEFT OUTER JOIN', 'RIGHT OUTER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'OUTER JOIN', 'INNER JOIN', 'JOIN', 'XOR', 'OR', 'AND' 73 | ); 74 | 75 | protected static $functions = array ( 76 | 'ABS', 'ACOS', 'ADDDATE', 'ADDTIME', 'AES_DECRYPT', 'AES_ENCRYPT', 'AREA', 'ASBINARY', 'ASCII', 'ASIN', 'ASTEXT', 'ATAN', 'ATAN2', 77 | 'AVG', 'BDMPOLYFROMTEXT', 'BDMPOLYFROMWKB', 'BDPOLYFROMTEXT', 'BDPOLYFROMWKB', 'BENCHMARK', 'BIN', 'BIT_AND', 'BIT_COUNT', 'BIT_LENGTH', 78 | 'BIT_OR', 'BIT_XOR', 'BOUNDARY', 'BUFFER', 'CAST', 'CEIL', 'CEILING', 'CENTROID', 'CHAR', 'CHARACTER_LENGTH', 'CHARSET', 'CHAR_LENGTH', 79 | 'COALESCE', 'COERCIBILITY', 'COLLATION', 'COMPRESS', 'CONCAT', 'CONCAT_WS', 'CONNECTION_ID', 'CONTAINS', 'CONV', 'CONVERT', 'CONVERT_TZ', 80 | 'CONVEXHULL', 'COS', 'COT', 'COUNT', 'CRC32', 'CROSSES', 'CURDATE', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 81 | 'CURTIME', 'DATABASE', 'DATE', 'DATEDIFF', 'DATE_ADD', 'DATE_DIFF', 'DATE_FORMAT', 'DATE_SUB', 'DAY', 'DAYNAME', 'DAYOFMONTH', 'DAYOFWEEK', 82 | 'DAYOFYEAR', 'DECODE', 'DEFAULT', 'DEGREES', 'DES_DECRYPT', 'DES_ENCRYPT', 'DIFFERENCE', 'DIMENSION', 'DISJOINT', 'DISTANCE', 'ELT', 'ENCODE', 83 | 'ENCRYPT', 'ENDPOINT', 'ENVELOPE', 'EQUALS', 'EXP', 'EXPORT_SET', 'EXTERIORRING', 'EXTRACT', 'EXTRACTVALUE', 'FIELD', 'FIND_IN_SET', 'FLOOR', 84 | 'FORMAT', 'FOUND_ROWS', 'FROM_DAYS', 'FROM_UNIXTIME', 'GEOMCOLLFROMTEXT', 'GEOMCOLLFROMWKB', 'GEOMETRYCOLLECTION', 'GEOMETRYCOLLECTIONFROMTEXT', 85 | 'GEOMETRYCOLLECTIONFROMWKB', 'GEOMETRYFROMTEXT', 'GEOMETRYFROMWKB', 'GEOMETRYN', 'GEOMETRYTYPE', 'GEOMFROMTEXT', 'GEOMFROMWKB', 'GET_FORMAT', 86 | 'GET_LOCK', 'GLENGTH', 'GREATEST', 'GROUP_CONCAT', 'GROUP_UNIQUE_USERS', 'HEX', 'HOUR', 'IF', 'IFNULL', 'INET_ATON', 'INET_NTOA', 'INSERT', 'INSTR', 87 | 'INTERIORRINGN', 'INTERSECTION', 'INTERSECTS', 'INTERVAL', 'ISCLOSED', 'ISEMPTY', 'ISNULL', 'ISRING', 'ISSIMPLE', 'IS_FREE_LOCK', 'IS_USED_LOCK', 88 | 'LAST_DAY', 'LAST_INSERT_ID', 'LCASE', 'LEAST', 'LEFT', 'LENGTH', 'LINEFROMTEXT', 'LINEFROMWKB', 'LINESTRING', 'LINESTRINGFROMTEXT', 'LINESTRINGFROMWKB', 89 | 'LN', 'LOAD_FILE', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCATE', 'LOG', 'LOG10', 'LOG2', 'LOWER', 'LPAD', 'LTRIM', 'MAKEDATE', 'MAKETIME', 'MAKE_SET', 90 | 'MASTER_POS_WAIT', 'MAX', 'MBRCONTAINS', 'MBRDISJOINT', 'MBREQUAL', 'MBRINTERSECTS', 'MBROVERLAPS', 'MBRTOUCHES', 'MBRWITHIN', 'MD5', 'MICROSECOND', 91 | 'MID', 'MIN', 'MINUTE', 'MLINEFROMTEXT', 'MLINEFROMWKB', 'MOD', 'MONTH', 'MONTHNAME', 'MPOINTFROMTEXT', 'MPOINTFROMWKB', 'MPOLYFROMTEXT', 'MPOLYFROMWKB', 92 | 'MULTILINESTRING', 'MULTILINESTRINGFROMTEXT', 'MULTILINESTRINGFROMWKB', 'MULTIPOINT', 'MULTIPOINTFROMTEXT', 'MULTIPOINTFROMWKB', 'MULTIPOLYGON', 93 | 'MULTIPOLYGONFROMTEXT', 'MULTIPOLYGONFROMWKB', 'NAME_CONST', 'NULLIF', 'NUMGEOMETRIES', 'NUMINTERIORRINGS', 'NUMPOINTS', 'OCT', 'OCTET_LENGTH', 94 | 'OLD_PASSWORD', 'ORD', 'OVERLAPS', 'PASSWORD', 'PERIOD_ADD', 'PERIOD_DIFF', 'PI', 'POINT', 'POINTFROMTEXT', 'POINTFROMWKB', 'POINTN', 'POINTONSURFACE', 95 | 'POLYFROMTEXT', 'POLYFROMWKB', 'POLYGON', 'POLYGONFROMTEXT', 'POLYGONFROMWKB', 'POSITION', 'POW', 'POWER', 'QUARTER', 'QUOTE', 'RADIANS', 'RAND', 96 | 'RELATED', 'RELEASE_LOCK', 'REPEAT', 'REPLACE', 'REVERSE', 'RIGHT', 'ROUND', 'ROW_COUNT', 'RPAD', 'RTRIM', 'SCHEMA', 'SECOND', 'SEC_TO_TIME', 97 | 'SESSION_USER', 'SHA', 'SHA1', 'SIGN', 'SIN', 'SLEEP', 'SOUNDEX', 'SPACE', 'SQRT', 'SRID', 'STARTPOINT', 'STD', 'STDDEV', 'STDDEV_POP', 'STDDEV_SAMP', 98 | 'STRCMP', 'STR_TO_DATE', 'SUBDATE', 'SUBSTR', 'SUBSTRING', 'SUBSTRING_INDEX', 'SUBTIME', 'SUM', 'SYMDIFFERENCE', 'SYSDATE', 'SYSTEM_USER', 'TAN', 99 | 'TIME', 'TIMEDIFF', 'TIMESTAMP', 'TIMESTAMPADD', 'TIMESTAMPDIFF', 'TIME_FORMAT', 'TIME_TO_SEC', 'TOUCHES', 'TO_DAYS', 'TRIM', 'TRUNCATE', 'UCASE', 100 | 'UNCOMPRESS', 'UNCOMPRESSED_LENGTH', 'UNHEX', 'UNIQUE_USERS', 'UNIX_TIMESTAMP', 'UPDATEXML', 'UPPER', 'USER', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 101 | 'UUID', 'VARIANCE', 'VAR_POP', 'VAR_SAMP', 'VERSION', 'WEEK', 'WEEKDAY', 'WEEKOFYEAR', 'WITHIN', 'X', 'Y', 'YEAR', 'YEARWEEK' 102 | ); 103 | 104 | // Punctuation that can be used as a boundary between other tokens 105 | protected static $boundaries = array(',', ';',':', ')', '(', '.', '=', '<', '>', '+', '-', '*', '/', '!', '^', '%', '|', '&', '#'); 106 | 107 | // For HTML syntax highlighting 108 | // Styles applied to different token types 109 | public static $quote_attributes = 'style="color: blue;"'; 110 | public static $backtick_quote_attributes = 'style="color: purple;"'; 111 | public static $reserved_attributes = 'style="font-weight:bold;"'; 112 | public static $boundary_attributes = ''; 113 | public static $number_attributes = 'style="color: green;"'; 114 | public static $word_attributes = 'style="color: #333;"'; 115 | public static $error_attributes = 'style="background-color: red;"'; 116 | public static $comment_attributes = 'style="color: #aaa;"'; 117 | public static $variable_attributes = 'style="color: orange;"'; 118 | public static $pre_attributes = 'style="color: black; background-color: white;"'; 119 | 120 | // Boolean - whether or not the current environment is the CLI 121 | // This affects the type of syntax highlighting 122 | // If not defined, it will be determined automatically 123 | public static $cli; 124 | 125 | // For CLI syntax highlighting 126 | public static $cli_quote = "\x1b[34;1m"; 127 | public static $cli_backtick_quote = "\x1b[35;1m"; 128 | public static $cli_reserved = "\x1b[37m"; 129 | public static $cli_boundary = ""; 130 | public static $cli_number = "\x1b[32;1m"; 131 | public static $cli_word = ""; 132 | public static $cli_error = "\x1b[31;1;7m"; 133 | public static $cli_comment = "\x1b[30;1m"; 134 | public static $cli_functions = "\x1b[37m"; 135 | public static $cli_variable = "\x1b[36;1m"; 136 | 137 | // The tab character to use when formatting SQL 138 | public static $tab = ' '; 139 | 140 | // This flag tells us if queries need to be enclosed in
 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 | --------------------------------------------------------------------------------