├── .github └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src └── db.php └── tests ├── bootstrap.php ├── integration └── SmokeTest.php ├── src └── TestCase.php └── wp-config.php /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - 'master' 7 | paths-ignore: 8 | - '**.md' 9 | workflow_dispatch: 10 | 11 | concurrency: 12 | group: '${{ github.workflow }}-${{ github.ref }}' 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | integration: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | php-version: ['5.6', '7.4', '8.0', '8.1', '8.2', '8.3'] 21 | 22 | name: Unit tests on PHP ${{ matrix.php-version }} 23 | 24 | continue-on-error: ${{ matrix.php-version == '8.3' }} 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | 29 | - uses: shivammathur/setup-php@v2 30 | with: 31 | php-version: ${{ matrix.php-version }} 32 | 33 | - name: 'Validate composer.json' 34 | run: composer validate --strict 35 | 36 | - name: 'Validate PHP syntax' 37 | run: php -l src/db.php 38 | 39 | - name: Install Composer dependencies 40 | uses: ramsey/composer-install@v2 41 | with: 42 | # Bust the cache at least once a month - output format: YYYY-MM. 43 | custom-cache-suffix: $(date -u "+%Y-%m") 44 | 45 | - name: Run tests 46 | run: composer test 47 | 48 | 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | wordpress/ 4 | src/uploads/ 5 | tests/.htaccess 6 | tests/index.php 7 | tests/database.sqlite 8 | tests/debug.txt 9 | .phpunit.result.cache 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.2.0 (2021-08-20) 4 | - Update for compatibility with PHP 8 5 | - Various fixes 6 | 7 | ## v1.1.0 (2020-06-13) 8 | - Update deprecated array/string offset syntax 9 | - Scaffold integration tests 10 | 11 | ## v1.0 (2018-09-01) 12 | - Initial Release! 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wp-sqlite-db 2 | 3 | [![Test](https://github.com/aaemnnosttv/wp-sqlite-db/actions/workflows/test.yml/badge.svg)](https://github.com/aaemnnosttv/wp-sqlite-db/actions/workflows/test.yml) 4 | [![Packagist](https://img.shields.io/packagist/v/aaemnnosttv/wp-sqlite-db.svg)](https://packagist.org/packages/aaemnnosttv/wp-sqlite-db) 5 | [![Packagist](https://img.shields.io/packagist/l/aaemnnosttv/wp-sqlite-db.svg)](https://packagist.org/packages/aaemnnosttv/wp-sqlite-db) 6 | 7 | A single file drop-in for using a SQLite database with WordPress. Based on the original SQLite Integration plugin. 8 | 9 | ## Installation 10 | 11 | #### Quick Start 12 | - Clone or download this repository 13 | - Copy `src/db.php` into the root of your site's `wp-content` directory 14 | 15 | #### Via Composer 16 | - `composer require koodimonni/composer-dropin-installer` 17 | - Add the configuration to your project's `composer.json` under the `extra` key 18 | ``` 19 | "extra": { 20 | "dropin-paths": { 21 | "wp-content/": ["package:aaemnnosttv/wp-sqlite-db:src/db.php"] 22 | } 23 | } 24 | ``` 25 | - `composer require aaemnnosttv/wp-sqlite-db` 26 | 27 | ## Overview 28 | 29 | Once the drop-in is installed, no other configuration is necessary, but some things are configurable. 30 | 31 | By default, the SQLite database is located in `wp-content/database/.ht.sqlite`, but you can change this using a few constants. 32 | 33 | ```php 34 | define('DB_DIR', '/absolute/custom/path/to/directory/for/sqlite/database/file/'); 35 | define('DB_FILE', 'custom_filename_for_sqlite_database'); 36 | ``` 37 | 38 | ## Credit 39 | 40 | This project is based on the [SQLite Integration](https://wordpress.org/plugins/sqlite-integration/) plugin by Kojima Toshiyasu. 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aaemnnosttv/wp-sqlite-db", 3 | "description": "SQLite drop-in database driver for WordPress", 4 | "type": "wordpress-dropin", 5 | "license": "GPL-2.0-or-later", 6 | "authors": [ 7 | { 8 | "name": "Evan Mattson", 9 | "email": "me@aaemnnost.tv", 10 | "role": "Developer" 11 | }, 12 | { 13 | "name": "Kojima Toshiyasu", 14 | "homepage": "https://profiles.wordpress.org/kjmtsh", 15 | "role": "Author of SQLite Integration" 16 | } 17 | ], 18 | "autoload-dev": { 19 | "psr-4": { 20 | "Tests\\": "tests/src" 21 | } 22 | }, 23 | "require": { 24 | "php": ">=5.6", 25 | "ext-PDO": "*", 26 | "composer/installers": "^1.0 || ^2.0" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "^5 || ^6 || ^7 || ^8 || ^9", 30 | "roots/wordpress": "^6", 31 | "wp-cli/core-command": "^2", 32 | "wp-phpunit/wp-phpunit": "^6", 33 | "yoast/phpunit-polyfills": "^1" 34 | }, 35 | "scripts": { 36 | "test": "phpunit" 37 | }, 38 | "config": { 39 | "allow-plugins": { 40 | "roots/wordpress-core-installer": true, 41 | "composer/installers": true 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | ./tests/integration/ 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/db.php: -------------------------------------------------------------------------------- 1 | 35 | * define('USE_MYSQL', true); 36 | * 37 | * 38 | * If you want to use SQLite, the line below will do. Or simply removing the line will 39 | * be enough. 40 | * 41 | * 42 | * define('USE_MYSQL', false); 43 | * 44 | */ 45 | if (defined('USE_MYSQL') && USE_MYSQL) { 46 | return; 47 | } 48 | 49 | function pdo_log_error($message, $data = null) 50 | { 51 | if (strpos($_SERVER['SCRIPT_NAME'], 'wp-admin') !== false) { 52 | $admin_dir = ''; 53 | } else { 54 | $admin_dir = 'wp-admin/'; 55 | } 56 | die(<< 59 | 60 | 61 | WordPress › Error 62 | 63 | 64 | 65 | 66 |

WordPress

67 |

$message

68 |

$data

69 | 70 | 71 | 72 | HTML 73 | ); 74 | } 75 | 76 | if (version_compare(PHP_VERSION, '5.4', '<')) { 77 | pdo_log_error('PHP version on this server is too old.', sprintf("Your server is running PHP version %d but this SQLite driver requires at least 5.4", phpversion())); 78 | } 79 | 80 | if (! extension_loaded('pdo')) { 81 | pdo_log_error('PHP PDO Extension is not loaded.', 82 | 'Your PHP installation appears to be missing the PDO extension which is required for this version of WordPress.'); 83 | } 84 | 85 | if (! extension_loaded('pdo_sqlite')) { 86 | pdo_log_error('PDO Driver for SQLite is missing.', 87 | 'Your PHP installation appears not to have the right PDO drivers loaded. These are required for this version of WordPress and the type of database you have specified.'); 88 | } 89 | 90 | /** 91 | * Notice: 92 | * Your scripts have the permission to create directories or files on your server. 93 | * If you write in your wp-config.php like below, we take these definitions. 94 | * define('DB_DIR', '/full_path_to_the_database_directory/'); 95 | * define('DB_FILE', 'database_file_name'); 96 | */ 97 | 98 | /** 99 | * FQDBDIR is a directory where the sqlite database file is placed. 100 | * If DB_DIR is defined, it is used as FQDBDIR. 101 | */ 102 | if (defined('DB_DIR')) { 103 | if (substr(DB_DIR, -1, 1) != '/') { 104 | define('FQDBDIR', DB_DIR . '/'); 105 | } else { 106 | define('FQDBDIR', DB_DIR); 107 | } 108 | } else { 109 | if (defined('WP_CONTENT_DIR')) { 110 | define('FQDBDIR', WP_CONTENT_DIR . '/database/'); 111 | } else { 112 | define('FQDBDIR', ABSPATH . 'wp-content/database/'); 113 | } 114 | } 115 | 116 | /** 117 | * FQDB is a database file name. If DB_FILE is defined, it is used 118 | * as FQDB. 119 | */ 120 | if (defined('DB_FILE')) { 121 | define('FQDB', FQDBDIR . DB_FILE); 122 | } else { 123 | define('FQDB', FQDBDIR . '.ht.sqlite'); 124 | } 125 | 126 | /** 127 | * This class defines user defined functions(UDFs) for PDO library. 128 | * 129 | * These functions replace those used in the SQL statement with the PHP functions. 130 | * 131 | * Usage: 132 | * 133 | * 134 | * new PDOSQLiteUDFS(ref_to_pdo_obj); 135 | * 136 | * 137 | * This automatically enables ref_to_pdo_obj to replace the function in the SQL statement 138 | * to the ones defined here. 139 | */ 140 | class PDOSQLiteUDFS 141 | { 142 | /** 143 | * The class constructor 144 | * 145 | * Initializes the use defined functions to PDO object with PDO::sqliteCreateFunction(). 146 | * 147 | * @param PDO $pdo 148 | */ 149 | public function __construct($pdo) 150 | { 151 | if (! $pdo) { 152 | wp_die('Database is not initialized.', 'Database Error'); 153 | } 154 | foreach ($this->functions as $f => $t) { 155 | $pdo->sqliteCreateFunction($f, [$this, $t]); 156 | } 157 | } 158 | 159 | /** 160 | * array to define MySQL function => function defined with PHP. 161 | * 162 | * Replaced functions must be public. 163 | * 164 | * @var array 165 | */ 166 | private $functions = [ 167 | 'month' => 'month', 168 | 'year' => 'year', 169 | 'day' => 'day', 170 | 'unix_timestamp' => 'unix_timestamp', 171 | 'now' => 'now', 172 | 'char_length' => 'char_length', 173 | 'md5' => 'md5', 174 | 'curdate' => 'curdate', 175 | 'rand' => 'rand', 176 | 'substring' => 'substring', 177 | 'dayofmonth' => 'day', 178 | 'second' => 'second', 179 | 'minute' => 'minute', 180 | 'hour' => 'hour', 181 | 'date_format' => 'dateformat', 182 | 'from_unixtime' => 'from_unixtime', 183 | 'date_add' => 'date_add', 184 | 'date_sub' => 'date_sub', 185 | 'adddate' => 'date_add', 186 | 'subdate' => 'date_sub', 187 | 'localtime' => 'now', 188 | 'localtimestamp' => 'now', 189 | 'isnull' => 'isnull', 190 | 'if' => '_if', 191 | 'regexpp' => 'regexp', 192 | 'concat' => 'concat', 193 | 'field' => 'field', 194 | 'log' => 'log', 195 | 'least' => 'least', 196 | 'greatest' => 'greatest', 197 | 'get_lock' => 'get_lock', 198 | 'release_lock' => 'release_lock', 199 | 'ucase' => 'ucase', 200 | 'lcase' => 'lcase', 201 | 'inet_ntoa' => 'inet_ntoa', 202 | 'inet_aton' => 'inet_aton', 203 | 'datediff' => 'datediff', 204 | 'locate' => 'locate', 205 | 'utc_date' => 'utc_date', 206 | 'utc_time' => 'utc_time', 207 | 'utc_timestamp' => 'utc_timestamp', 208 | 'version' => 'version', 209 | ]; 210 | 211 | /** 212 | * Method to extract the month value from the date. 213 | * 214 | * @param string representing the date formatted as 0000-00-00. 215 | * 216 | * @return string representing the number of the month between 1 and 12. 217 | */ 218 | public function month($field) 219 | { 220 | $t = strtotime($field); 221 | 222 | return date('n', $t); 223 | } 224 | 225 | /** 226 | * Method to extract the year value from the date. 227 | * 228 | * @param string representing the date formatted as 0000-00-00. 229 | * 230 | * @return string representing the number of the year. 231 | */ 232 | public function year($field) 233 | { 234 | $t = strtotime($field); 235 | 236 | return date('Y', $t); 237 | } 238 | 239 | /** 240 | * Method to extract the day value from the date. 241 | * 242 | * @param string representing the date formatted as 0000-00-00. 243 | * 244 | * @return string representing the number of the day of the month from 1 and 31. 245 | */ 246 | public function day($field) 247 | { 248 | $t = strtotime($field); 249 | 250 | return date('j', $t); 251 | } 252 | 253 | /** 254 | * Method to return the unix timestamp. 255 | * 256 | * Used without an argument, it returns PHP time() function (total seconds passed 257 | * from '1970-01-01 00:00:00' GMT). Used with the argument, it changes the value 258 | * to the timestamp. 259 | * 260 | * @param string representing the date formatted as '0000-00-00 00:00:00'. 261 | * 262 | * @return number of unsigned integer 263 | */ 264 | public function unix_timestamp($field = null) 265 | { 266 | return is_null($field) ? time() : strtotime($field); 267 | } 268 | 269 | /** 270 | * Method to emulate MySQL SECOND() function. 271 | * 272 | * @param string representing the time formatted as '00:00:00'. 273 | * 274 | * @return number of unsigned integer 275 | */ 276 | public function second($field) 277 | { 278 | $t = strtotime($field); 279 | 280 | return intval(date("s", $t)); 281 | } 282 | 283 | /** 284 | * Method to emulate MySQL MINUTE() function. 285 | * 286 | * @param string representing the time formatted as '00:00:00'. 287 | * 288 | * @return number of unsigned integer 289 | */ 290 | public function minute($field) 291 | { 292 | $t = strtotime($field); 293 | 294 | return intval(date("i", $t)); 295 | } 296 | 297 | /** 298 | * Method to emulate MySQL HOUR() function. 299 | * 300 | * @param string representing the time formatted as '00:00:00'. 301 | * 302 | * @return number 303 | */ 304 | public function hour($time) 305 | { 306 | list($hours) = explode(":", $time); 307 | 308 | return intval($hours); 309 | } 310 | 311 | /** 312 | * Method to emulate MySQL FROM_UNIXTIME() function. 313 | * 314 | * @param integer of unix timestamp 315 | * @param string to indicate the way of formatting(optional) 316 | * 317 | * @return string formatted as '0000-00-00 00:00:00'. 318 | */ 319 | public function from_unixtime($field, $format = null) 320 | { 321 | //convert to ISO time 322 | $date = date("Y-m-d H:i:s", $field); 323 | 324 | return is_null($format) ? $date : $this->dateformat($date, $format); 325 | } 326 | 327 | /** 328 | * Method to emulate MySQL NOW() function. 329 | * 330 | * @return string representing current time formatted as '0000-00-00 00:00:00'. 331 | */ 332 | public function now() 333 | { 334 | return date("Y-m-d H:i:s"); 335 | } 336 | 337 | /** 338 | * Method to emulate MySQL CURDATE() function. 339 | * 340 | * @return string representing current time formatted as '0000-00-00'. 341 | */ 342 | public function curdate() 343 | { 344 | return date("Y-m-d"); 345 | } 346 | 347 | /** 348 | * Method to emulate MySQL CHAR_LENGTH() function. 349 | * 350 | * @param string 351 | * 352 | * @return int unsigned integer for the length of the argument. 353 | */ 354 | public function char_length($field) 355 | { 356 | return strlen($field); 357 | } 358 | 359 | /** 360 | * Method to emulate MySQL MD5() function. 361 | * 362 | * @param string 363 | * 364 | * @return string of the md5 hash value of the argument. 365 | */ 366 | public function md5($field) 367 | { 368 | return md5($field); 369 | } 370 | 371 | /** 372 | * Method to emulate MySQL RAND() function. 373 | * 374 | * SQLite does have a random generator, but it is called RANDOM() and returns random 375 | * number between -9223372036854775808 and +9223372036854775807. So we substitute it 376 | * with PHP random generator. 377 | * 378 | * This function uses mt_rand() which is four times faster than rand() and returns 379 | * the random number between 0 and 1. 380 | * 381 | * @return int 382 | */ 383 | public function rand() 384 | { 385 | return mt_rand(0, 1); 386 | } 387 | 388 | /** 389 | * Method to emulate MySQL SUBSTRING() function. 390 | * 391 | * This function rewrites the function name to SQLite compatible substr(), 392 | * which can manipulate UTF-8 characters. 393 | * 394 | * @param string $text 395 | * @param integer $pos representing the start point. 396 | * @param integer $len representing the length of the substring(optional). 397 | * 398 | * @return string 399 | */ 400 | public function substring($text, $pos, $len = null) 401 | { 402 | return "substr($text, $pos, $len)"; 403 | } 404 | 405 | /** 406 | * Method to emulate MySQL DATEFORMAT() function. 407 | * 408 | * @param string date formatted as '0000-00-00' or datetime as '0000-00-00 00:00:00'. 409 | * @param string $format 410 | * 411 | * @return string formatted according to $format 412 | */ 413 | public function dateformat($date, $format) 414 | { 415 | $mysql_php_date_formats = [ 416 | '%a' => 'D', 417 | '%b' => 'M', 418 | '%c' => 'n', 419 | '%D' => 'jS', 420 | '%d' => 'd', 421 | '%e' => 'j', 422 | '%H' => 'H', 423 | '%h' => 'h', 424 | '%I' => 'h', 425 | '%i' => 'i', 426 | '%j' => 'z', 427 | '%k' => 'G', 428 | '%l' => 'g', 429 | '%M' => 'F', 430 | '%m' => 'm', 431 | '%p' => 'A', 432 | '%r' => 'h:i:s A', 433 | '%S' => 's', 434 | '%s' => 's', 435 | '%T' => 'H:i:s', 436 | '%U' => 'W', 437 | '%u' => 'W', 438 | '%V' => 'W', 439 | '%v' => 'W', 440 | '%W' => 'l', 441 | '%w' => 'w', 442 | '%X' => 'Y', 443 | '%x' => 'o', 444 | '%Y' => 'Y', 445 | '%y' => 'y', 446 | ]; 447 | $t = strtotime($date); 448 | $format = strtr($format, $mysql_php_date_formats); 449 | $output = date($format, $t); 450 | 451 | return $output; 452 | } 453 | 454 | /** 455 | * Method to emulate MySQL DATE_ADD() function. 456 | * 457 | * This function adds the time value of $interval expression to $date. 458 | * $interval is a single quoted strings rewritten by SQLiteQueryDriver::rewrite_query(). 459 | * It is calculated in the private function deriveInterval(). 460 | * 461 | * @param string $date representing the start date. 462 | * @param string $interval representing the expression of the time to add. 463 | * 464 | * @return string date formatted as '0000-00-00 00:00:00'. 465 | * @throws Exception 466 | */ 467 | public function date_add($date, $interval) 468 | { 469 | $interval = $this->deriveInterval($interval); 470 | switch (strtolower($date)) { 471 | case "curdate()": 472 | $objDate = new DateTime($this->curdate()); 473 | $objDate->add(new DateInterval($interval)); 474 | $formatted = $objDate->format("Y-m-d"); 475 | break; 476 | case "now()": 477 | $objDate = new DateTime($this->now()); 478 | $objDate->add(new DateInterval($interval)); 479 | $formatted = $objDate->format("Y-m-d H:i:s"); 480 | break; 481 | default: 482 | $objDate = new DateTime($date); 483 | $objDate->add(new DateInterval($interval)); 484 | $formatted = $objDate->format("Y-m-d H:i:s"); 485 | } 486 | 487 | return $formatted; 488 | } 489 | 490 | /** 491 | * Method to emulate MySQL DATE_SUB() function. 492 | * 493 | * This function subtracts the time value of $interval expression from $date. 494 | * $interval is a single quoted strings rewritten by SQLiteQueryDriver::rewrite_query(). 495 | * It is calculated in the private function deriveInterval(). 496 | * 497 | * @param string $date representing the start date. 498 | * @param string $interval representing the expression of the time to subtract. 499 | * 500 | * @return string date formatted as '0000-00-00 00:00:00'. 501 | * @throws Exception 502 | */ 503 | public function date_sub($date, $interval) 504 | { 505 | $interval = $this->deriveInterval($interval); 506 | switch (strtolower($date)) { 507 | case "curdate()": 508 | $objDate = new DateTime($this->curdate()); 509 | $objDate->sub(new DateInterval($interval)); 510 | $returnval = $objDate->format("Y-m-d"); 511 | break; 512 | case "now()": 513 | $objDate = new DateTime($this->now()); 514 | $objDate->sub(new DateInterval($interval)); 515 | $returnval = $objDate->format("Y-m-d H:i:s"); 516 | break; 517 | default: 518 | $objDate = new DateTime($date); 519 | $objDate->sub(new DateInterval($interval)); 520 | $returnval = $objDate->format("Y-m-d H:i:s"); 521 | } 522 | 523 | return $returnval; 524 | } 525 | 526 | /** 527 | * Method to calculate the interval time between two dates value. 528 | * 529 | * @access private 530 | * 531 | * @param string $interval white space separated expression. 532 | * 533 | * @return string representing the time to add or substract. 534 | */ 535 | private function deriveInterval($interval) 536 | { 537 | $interval = trim(substr(trim($interval), 8)); 538 | $parts = explode(' ', $interval); 539 | foreach ($parts as $part) { 540 | if (! empty($part)) { 541 | $_parts[] = $part; 542 | } 543 | } 544 | $type = strtolower(end($_parts)); 545 | switch ($type) { 546 | case "second": 547 | $unit = 'S'; 548 | 549 | return 'PT' . $_parts[0] . $unit; 550 | break; 551 | case "minute": 552 | $unit = 'M'; 553 | 554 | return 'PT' . $_parts[0] . $unit; 555 | break; 556 | case "hour": 557 | $unit = 'H'; 558 | 559 | return 'PT' . $_parts[0] . $unit; 560 | break; 561 | case "day": 562 | $unit = 'D'; 563 | 564 | return 'P' . $_parts[0] . $unit; 565 | break; 566 | case "week": 567 | $unit = 'W'; 568 | 569 | return 'P' . $_parts[0] . $unit; 570 | break; 571 | case "month": 572 | $unit = 'M'; 573 | 574 | return 'P' . $_parts[0] . $unit; 575 | break; 576 | case "year": 577 | $unit = 'Y'; 578 | 579 | return 'P' . $_parts[0] . $unit; 580 | break; 581 | case "minute_second": 582 | list($minutes, $seconds) = explode(':', $_parts[0]); 583 | 584 | return 'PT' . $minutes . 'M' . $seconds . 'S'; 585 | case "hour_second": 586 | list($hours, $minutes, $seconds) = explode(':', $_parts[0]); 587 | 588 | return 'PT' . $hours . 'H' . $minutes . 'M' . $seconds . 'S'; 589 | case "hour_minute": 590 | list($hours, $minutes) = explode(':', $_parts[0]); 591 | 592 | return 'PT' . $hours . 'H' . $minutes . 'M'; 593 | case "day_second": 594 | $days = intval($_parts[0]); 595 | list($hours, $minutes, $seconds) = explode(':', $_parts[1]); 596 | 597 | return 'P' . $days . 'D' . 'T' . $hours . 'H' . $minutes . 'M' . $seconds . 'S'; 598 | case "day_minute": 599 | $days = intval($_parts[0]); 600 | list($hours, $minutes) = explode(':', $parts[1]); 601 | 602 | return 'P' . $days . 'D' . 'T' . $hours . 'H' . $minutes . 'M'; 603 | case "day_hour": 604 | $days = intval($_parts[0]); 605 | $hours = intval($_parts[1]); 606 | 607 | return 'P' . $days . 'D' . 'T' . $hours . 'H'; 608 | case "year_month": 609 | list($years, $months) = explode('-', $_parts[0]); 610 | 611 | return 'P' . $years . 'Y' . $months . 'M'; 612 | } 613 | } 614 | 615 | /** 616 | * Method to emulate MySQL DATE() function. 617 | * 618 | * @param string $date formatted as unix time. 619 | * 620 | * @return string formatted as '0000-00-00'. 621 | */ 622 | public function date($date) 623 | { 624 | return date("Y-m-d", strtotime($date)); 625 | } 626 | 627 | /** 628 | * Method to emulate MySQL ISNULL() function. 629 | * 630 | * This function returns true if the argument is null, and true if not. 631 | * 632 | * @param various types $field 633 | * 634 | * @return boolean 635 | */ 636 | public function isnull($field) 637 | { 638 | return is_null($field); 639 | } 640 | 641 | /** 642 | * Method to emulate MySQL IF() function. 643 | * 644 | * As 'IF' is a reserved word for PHP, function name must be changed. 645 | * 646 | * @param unknonw $expression the statement to be evaluated as true or false. 647 | * @param unknown $true statement or value returned if $expression is true. 648 | * @param unknown $false statement or value returned if $expression is false. 649 | * 650 | * @return unknown 651 | */ 652 | public function _if($expression, $true, $false) 653 | { 654 | return ($expression == true) ? $true : $false; 655 | } 656 | 657 | /** 658 | * Method to emulate MySQL REGEXP() function. 659 | * 660 | * @param string $field haystack 661 | * @param string $pattern : regular expression to match. 662 | * 663 | * @return integer 1 if matched, 0 if not matched. 664 | */ 665 | public function regexp($field, $pattern) 666 | { 667 | $pattern = str_replace('/', '\/', $pattern); 668 | $pattern = "/" . $pattern . "/i"; 669 | 670 | return preg_match($pattern, $field); 671 | } 672 | 673 | /** 674 | * Method to emulate MySQL CONCAT() function. 675 | * 676 | * SQLite does have CONCAT() function, but it has a different syntax from MySQL. 677 | * So this function must be manipulated here. 678 | * 679 | * @param string 680 | * 681 | * @return NULL if the argument is null | string conatenated if the argument is given. 682 | */ 683 | public function concat() 684 | { 685 | $returnValue = ""; 686 | $argsNum = func_num_args(); 687 | $argsList = func_get_args(); 688 | for ($i = 0; $i < $argsNum; $i++) { 689 | if (is_null($argsList[$i])) { 690 | return null; 691 | } 692 | $returnValue .= $argsList[$i]; 693 | } 694 | 695 | return $returnValue; 696 | } 697 | 698 | /** 699 | * Method to emulate MySQL FIELD() function. 700 | * 701 | * This function gets the list argument and compares the first item to all the others. 702 | * If the same value is found, it returns the position of that value. If not, it 703 | * returns 0. 704 | * 705 | * @param int...|float... variable number of string, integer or double 706 | * 707 | * @return int unsigned integer 708 | */ 709 | public function field() 710 | { 711 | global $wpdb; 712 | $numArgs = func_num_args(); 713 | if ($numArgs < 2 or is_null(func_get_arg(0))) { 714 | return 0; 715 | } else { 716 | $arg_list = func_get_args(); 717 | } 718 | $searchString = array_shift($arg_list); 719 | $str_to_check = substr($searchString, 0, strpos($searchString, '.')); 720 | $str_to_check = str_replace($wpdb->prefix, '', $str_to_check); 721 | if ($str_to_check && in_array(trim($str_to_check), $wpdb->tables)) { 722 | return 0; 723 | } 724 | for ($i = 0; $i < $numArgs - 1; $i++) { 725 | if ($searchString === strtolower($arg_list[$i])) { 726 | return $i + 1; 727 | } 728 | } 729 | 730 | return 0; 731 | } 732 | 733 | /** 734 | * Method to emulate MySQL LOG() function. 735 | * 736 | * Used with one argument, it returns the natural logarithm of X. 737 | * 738 | * LOG(X) 739 | * 740 | * Used with two arguments, it returns the natural logarithm of X base B. 741 | * 742 | * LOG(B, X) 743 | * 744 | * In this case, it returns the value of log(X) / log(B). 745 | * 746 | * Used without an argument, it returns false. This returned value will be 747 | * rewritten to 0, because SQLite doesn't understand true/false value. 748 | * 749 | * @param integer representing the base of the logarithm, which is optional. 750 | * @param double value to turn into logarithm. 751 | * 752 | * @return double | NULL 753 | */ 754 | public function log() 755 | { 756 | $numArgs = func_num_args(); 757 | if ($numArgs == 1) { 758 | $arg1 = func_get_arg(0); 759 | 760 | return log($arg1); 761 | } elseif ($numArgs == 2) { 762 | $arg1 = func_get_arg(0); 763 | $arg2 = func_get_arg(1); 764 | 765 | return log($arg1) / log($arg2); 766 | } else { 767 | return null; 768 | } 769 | } 770 | 771 | /** 772 | * Method to emulate MySQL LEAST() function. 773 | * 774 | * This function rewrites the function name to SQLite compatible function name. 775 | * 776 | * @return mixed 777 | */ 778 | public function least() 779 | { 780 | $arg_list = func_get_args(); 781 | 782 | return "min($arg_list)"; 783 | } 784 | 785 | /** 786 | * Method to emulate MySQL GREATEST() function. 787 | * 788 | * This function rewrites the function name to SQLite compatible function name. 789 | * 790 | * @return mixed 791 | */ 792 | public function greatest() 793 | { 794 | $arg_list = func_get_args(); 795 | 796 | return "max($arg_list)"; 797 | } 798 | 799 | /** 800 | * Method to dummy out MySQL GET_LOCK() function. 801 | * 802 | * This function is meaningless in SQLite, so we do nothing. 803 | * 804 | * @param string $name 805 | * @param integer $timeout 806 | * 807 | * @return string 808 | */ 809 | public function get_lock($name, $timeout) 810 | { 811 | return '1=1'; 812 | } 813 | 814 | /** 815 | * Method to dummy out MySQL RELEASE_LOCK() function. 816 | * 817 | * This function is meaningless in SQLite, so we do nothing. 818 | * 819 | * @param string $name 820 | * 821 | * @return string 822 | */ 823 | public function release_lock($name) 824 | { 825 | return '1=1'; 826 | } 827 | 828 | /** 829 | * Method to emulate MySQL UCASE() function. 830 | * 831 | * This is MySQL alias for upper() function. This function rewrites it 832 | * to SQLite compatible name upper(). 833 | * 834 | * @param string 835 | * 836 | * @return string SQLite compatible function name. 837 | */ 838 | public function ucase($string) 839 | { 840 | return "upper($string)"; 841 | } 842 | 843 | /** 844 | * Method to emulate MySQL LCASE() function. 845 | * 846 | * 847 | * This is MySQL alias for lower() function. This function rewrites it 848 | * to SQLite compatible name lower(). 849 | * 850 | * @param string 851 | * 852 | * @return string SQLite compatible function name. 853 | */ 854 | public function lcase($string) 855 | { 856 | return "lower($string)"; 857 | } 858 | 859 | /** 860 | * Method to emulate MySQL INET_NTOA() function. 861 | * 862 | * This function gets 4 or 8 bytes integer and turn it into the network address. 863 | * 864 | * @param unsigned long integer 865 | * 866 | * @return string 867 | */ 868 | public function inet_ntoa($num) 869 | { 870 | return long2ip($num); 871 | } 872 | 873 | /** 874 | * Method to emulate MySQL INET_ATON() function. 875 | * 876 | * This function gets the network address and turns it into integer. 877 | * 878 | * @param string 879 | * 880 | * @return int long integer 881 | */ 882 | public function inet_aton($addr) 883 | { 884 | return absint(ip2long($addr)); 885 | } 886 | 887 | /** 888 | * Method to emulate MySQL DATEDIFF() function. 889 | * 890 | * This function compares two dates value and returns the difference. 891 | * 892 | * @param string start 893 | * @param string end 894 | * 895 | * @return string 896 | */ 897 | public function datediff($start, $end) 898 | { 899 | $start_date = new DateTime($start); 900 | $end_date = new DateTime($end); 901 | $interval = $end_date->diff($start_date, false); 902 | 903 | return $interval->format('%r%a'); 904 | } 905 | 906 | /** 907 | * Method to emulate MySQL LOCATE() function. 908 | * 909 | * This function returns the position if $substr is found in $str. If not, 910 | * it returns 0. If mbstring extension is loaded, mb_strpos() function is 911 | * used. 912 | * 913 | * @param string needle 914 | * @param string haystack 915 | * @param integer position 916 | * 917 | * @return integer 918 | */ 919 | public function locate($substr, $str, $pos = 0) 920 | { 921 | if (! extension_loaded('mbstring')) { 922 | if (($val = strpos($str, $substr, $pos)) !== false) { 923 | return $val + 1; 924 | } else { 925 | return 0; 926 | } 927 | } else { 928 | if (($val = mb_strpos($str, $substr, $pos)) !== false) { 929 | return $val + 1; 930 | } else { 931 | return 0; 932 | } 933 | } 934 | } 935 | 936 | /** 937 | * Method to return GMT date in the string format. 938 | * 939 | * @param none 940 | * 941 | * @return string formatted GMT date 'dddd-mm-dd' 942 | */ 943 | public function utc_date() 944 | { 945 | return gmdate('Y-m-d', time()); 946 | } 947 | 948 | /** 949 | * Method to return GMT time in the string format. 950 | * 951 | * @param none 952 | * 953 | * @return string formatted GMT time '00:00:00' 954 | */ 955 | public function utc_time() 956 | { 957 | return gmdate('H:i:s', time()); 958 | } 959 | 960 | /** 961 | * Method to return GMT time stamp in the string format. 962 | * 963 | * @param none 964 | * 965 | * @return string formatted GMT timestamp 'yyyy-mm-dd 00:00:00' 966 | */ 967 | public function utc_timestamp() 968 | { 969 | return gmdate('Y-m-d H:i:s', time()); 970 | } 971 | 972 | /** 973 | * Method to return MySQL version. 974 | * 975 | * This function only returns the current newest version number of MySQL, 976 | * because it is meaningless for SQLite database. 977 | * 978 | * @param none 979 | * 980 | * @return string representing the version number: major_version.minor_version 981 | */ 982 | public function version() 983 | { 984 | //global $required_mysql_version; 985 | //return $required_mysql_version; 986 | return '5.5'; 987 | } 988 | } 989 | 990 | /** 991 | * This class extends PDO class and does the real work. 992 | * 993 | * It accepts a request from wpdb class, initialize PDO instance, 994 | * execute SQL statement, and returns the results to WordPress. 995 | */ 996 | class PDOEngine extends PDO 997 | { 998 | /** 999 | * Class variable to check if there is an error. 1000 | * 1001 | * @var boolean 1002 | */ 1003 | public $is_error = false; 1004 | /** 1005 | * Class variable which is used for CALC_FOUND_ROW query. 1006 | * 1007 | * @var unsigned integer 1008 | */ 1009 | public $found_rows_result = null; 1010 | /** 1011 | * Class variable used for query with ORDER BY FIELD() 1012 | * 1013 | * @var array of the object 1014 | */ 1015 | public $pre_ordered_results = null; 1016 | /** 1017 | * Class variable to store the rewritten queries. 1018 | * 1019 | * @var array 1020 | * @access private 1021 | */ 1022 | private $rewritten_query; 1023 | /** 1024 | * Class variable to have what kind of query to execute. 1025 | * 1026 | * @var string 1027 | * @access private 1028 | */ 1029 | private $query_type; 1030 | /** 1031 | * Class variable to store the result of the query. 1032 | * 1033 | * @var array reference to the PHP object 1034 | * @access private 1035 | */ 1036 | private $results = null; 1037 | /** 1038 | * Class variable to store the results of the query. 1039 | * 1040 | * This is for the backward compatibility. 1041 | * 1042 | * @var array reference to the PHP object 1043 | * @access private 1044 | */ 1045 | private $_results = null; 1046 | /** 1047 | * Class variable to reference to the PDO instance. 1048 | * 1049 | * @var PDO object 1050 | * @access private 1051 | */ 1052 | private $pdo; 1053 | /** 1054 | * Class variable to store the query string prepared to execute. 1055 | * 1056 | * @var string|array 1057 | */ 1058 | private $prepared_query; 1059 | /** 1060 | * Class variable to store the values in the query string. 1061 | * 1062 | * @var array 1063 | * @access private 1064 | */ 1065 | private $extracted_variables = []; 1066 | /** 1067 | * Class variable to store the error messages. 1068 | * 1069 | * @var array 1070 | * @access private 1071 | */ 1072 | private $error_messages = []; 1073 | /** 1074 | * Class variable to store the file name and function to cause error. 1075 | * 1076 | * @var array 1077 | * @access private 1078 | */ 1079 | private $errors; 1080 | /** 1081 | * Class variable to store the query strings. 1082 | * 1083 | * @var array 1084 | */ 1085 | public $queries = []; 1086 | /** 1087 | * Class variable to store the affected row id. 1088 | * 1089 | * @var unsigned integer 1090 | * @access private 1091 | */ 1092 | private $last_insert_id; 1093 | /** 1094 | * Class variable to store the number of rows affected. 1095 | * 1096 | * @var unsigned integer 1097 | */ 1098 | private $affected_rows; 1099 | /** 1100 | * Class variable to store the queried column info. 1101 | * 1102 | * @var array 1103 | */ 1104 | private $column_data; 1105 | /** 1106 | * Variable to emulate MySQL affected row. 1107 | * 1108 | * @var integer 1109 | */ 1110 | private $num_rows; 1111 | /** 1112 | * Return value from query(). 1113 | * 1114 | * Each query has its own return value. 1115 | * 1116 | * @var mixed 1117 | */ 1118 | private $return_value; 1119 | /** 1120 | * Variable to determine which insert query to use. 1121 | * 1122 | * Whether VALUES clause in the INSERT query can take multiple values or not 1123 | * depends on the version of SQLite library. We check the version and set 1124 | * this varable to true or false. 1125 | * 1126 | * @var boolean 1127 | */ 1128 | private $can_insert_multiple_rows = false; 1129 | /** 1130 | * 1131 | * @var integer 1132 | */ 1133 | private $param_num; 1134 | /** 1135 | * Varible to check if there is an active transaction. 1136 | * @var boolean 1137 | * @access protected 1138 | */ 1139 | protected $has_active_transaction = false; 1140 | 1141 | /** 1142 | * Constructor 1143 | * 1144 | * Create PDO object, set user defined functions and initialize other settings. 1145 | * Don't use parent::__construct() because this class does not only returns 1146 | * PDO instance but many others jobs. 1147 | * 1148 | * Constructor definition is changed since version 1.7.1. 1149 | * 1150 | * @param none 1151 | */ 1152 | function __construct() 1153 | { 1154 | register_shutdown_function([$this, '__destruct']); 1155 | if (! is_file(FQDB)) { 1156 | $this->prepare_directory(); 1157 | } 1158 | $dsn = 'sqlite:' . FQDB; 1159 | if (isset($GLOBALS['@pdo'])) { 1160 | $this->pdo = $GLOBALS['@pdo']; 1161 | } else { 1162 | $locked = false; 1163 | $status = 0; 1164 | do { 1165 | try { 1166 | $this->pdo = new PDO($dsn, null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); 1167 | new PDOSQLiteUDFS($this->pdo); 1168 | $GLOBALS['@pdo'] = $this->pdo; 1169 | } catch (PDOException $ex) { 1170 | $status = $ex->getCode(); 1171 | if ($status == 5 || $status == 6) { 1172 | $locked = true; 1173 | } else { 1174 | $err_message = $ex->getMessage(); 1175 | } 1176 | } 1177 | } while ($locked); 1178 | if ($status > 0) { 1179 | $message = 'Database initialization error!
' . 1180 | 'Code: ' . $status . 1181 | (isset($err_message) ? '
Error Message: ' . $err_message : ''); 1182 | $this->set_error(__LINE__, __FILE__, $message); 1183 | 1184 | return false; 1185 | } 1186 | } 1187 | $this->init(); 1188 | } 1189 | 1190 | /** 1191 | * Destructor 1192 | * 1193 | * If SQLITE_MEM_DEBUG constant is defined, append information about 1194 | * memory usage into database/mem_debug.txt. 1195 | * 1196 | * This definition is changed since version 1.7. 1197 | * 1198 | * @return boolean 1199 | */ 1200 | function __destruct() 1201 | { 1202 | if (defined('SQLITE_MEM_DEBUG') && SQLITE_MEM_DEBUG) { 1203 | $max = ini_get('memory_limit'); 1204 | if (is_null($max)) { 1205 | $message = sprintf("[%s] Memory_limit is not set in php.ini file.", 1206 | date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME'])); 1207 | file_put_contents(FQDBDIR . 'mem_debug.txt', $message, FILE_APPEND); 1208 | 1209 | return true; 1210 | } 1211 | if (stripos($max, 'M') !== false) { 1212 | $max = (int) $max * 1024 * 1024; 1213 | } 1214 | $peak = memory_get_peak_usage(true); 1215 | $used = round((int) $peak / (int) $max * 100, 2); 1216 | if ($used > 90) { 1217 | $message = sprintf("[%s] Memory peak usage warning: %s %% used. (max: %sM, now: %sM)\n", 1218 | date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']), $used, $max, $peak); 1219 | file_put_contents(FQDBDIR . 'mem_debug.txt', $message, FILE_APPEND); 1220 | } 1221 | } 1222 | 1223 | //$this->pdo = null; 1224 | return true; 1225 | } 1226 | 1227 | /** 1228 | * Method to initialize database, executed in the constructor. 1229 | * 1230 | * It checks if WordPress is in the installing process and does the required 1231 | * jobs. SQLite library version specific settings are also in this function. 1232 | * 1233 | * Some developers use WP_INSTALLING constant for other purposes, if so, this 1234 | * function will do no harms. 1235 | */ 1236 | private function init() 1237 | { 1238 | if (version_compare($this->get_sqlite_version(), '3.7.11', '>=')) { 1239 | $this->can_insert_multiple_rows = true; 1240 | } 1241 | $statement = $this->pdo->query('PRAGMA foreign_keys'); 1242 | if ($statement->fetchColumn(0) == '0') { 1243 | $this->pdo->query('PRAGMA foreign_keys = ON'); 1244 | } 1245 | } 1246 | 1247 | /** 1248 | * This method makes database direcotry and .htaccess file. 1249 | * 1250 | * It is executed only once when the installation begins. 1251 | */ 1252 | private function prepare_directory() 1253 | { 1254 | global $wpdb; 1255 | $u = umask(0000); 1256 | if (! is_dir(FQDBDIR)) { 1257 | if (! @mkdir(FQDBDIR, 0704, true)) { 1258 | umask($u); 1259 | $message = 'Unable to create the required directory! Please check your server settings.'; 1260 | wp_die($message, 'Error!'); 1261 | } 1262 | } 1263 | if (! is_writable(FQDBDIR)) { 1264 | umask($u); 1265 | $message = 'Unable to create a file in the directory! Please check your server settings.'; 1266 | wp_die($message, 'Error!'); 1267 | } 1268 | if (! is_file(FQDBDIR . '.htaccess')) { 1269 | $fh = fopen(FQDBDIR . '.htaccess', "w"); 1270 | if (! $fh) { 1271 | umask($u); 1272 | $message = 'Unable to create a file in the directory! Please check your server settings.'; 1273 | echo $message; 1274 | 1275 | return false; 1276 | } 1277 | fwrite($fh, 'DENY FROM ALL'); 1278 | fclose($fh); 1279 | } 1280 | if (! is_file(FQDBDIR . 'index.php')) { 1281 | $fh = fopen(FQDBDIR . 'index.php', "w"); 1282 | if (! $fh) { 1283 | umask($u); 1284 | $message = 'Unable to create a file in the directory! Please check your server settings.'; 1285 | echo $message; 1286 | 1287 | return false; 1288 | } 1289 | fwrite($fh, ''); 1290 | fclose($fh); 1291 | } 1292 | umask($u); 1293 | 1294 | return true; 1295 | } 1296 | 1297 | /** 1298 | * Method to execute query(). 1299 | * 1300 | * Divide the query types into seven different ones. That is to say: 1301 | * 1302 | * 1. SELECT SQL_CALC_FOUND_ROWS 1303 | * 2. INSERT 1304 | * 3. CREATE TABLE(INDEX) 1305 | * 4. ALTER TABLE 1306 | * 5. SHOW VARIABLES 1307 | * 6. DROP INDEX 1308 | * 7. THE OTHERS 1309 | * 1310 | * #1 is just a tricky play. See the private function handle_sql_count() in query.class.php. 1311 | * From #2 through #5 call different functions respectively. 1312 | * #6 call the ALTER TABLE query. 1313 | * #7 is a normal process: sequentially call prepare_query() and execute_query(). 1314 | * 1315 | * #1 process has been changed since version 1.5.1. 1316 | * 1317 | * @param string $statement full SQL statement string 1318 | * 1319 | * @param int $mode 1320 | * @param array $fetch_mode_args 1321 | * 1322 | * @return mixed according to the query type 1323 | * @see PDO::query() 1324 | */ 1325 | #[\ReturnTypeWillChange] 1326 | public function query($statement, $mode = PDO::ATTR_DEFAULT_FETCH_MODE, ...$fetch_mode_args) 1327 | { 1328 | $this->flush(); 1329 | 1330 | $this->queries[] = "Raw query:\n$statement"; 1331 | $res = $this->determine_query_type($statement); 1332 | if (! $res && defined('PDO_DEBUG') && PDO_DEBUG) { 1333 | $bailoutString = sprintf(__("

Unknown query type

Sorry, we cannot determine the type of query that is requested.

The query is %s

", 1334 | 'sqlite-integration'), $statement); 1335 | $this->set_error(__LINE__, __FUNCTION__, $bailoutString); 1336 | } 1337 | switch (strtolower($this->query_type)) { 1338 | case 'set': 1339 | $this->return_value = false; 1340 | break; 1341 | case 'foundrows': 1342 | $_column = ['FOUND_ROWS()' => '']; 1343 | $column = []; 1344 | if (! is_null($this->found_rows_result)) { 1345 | $this->num_rows = $this->found_rows_result; 1346 | $_column['FOUND_ROWS()'] = $this->num_rows; 1347 | //foreach ($this->found_rows_result[0] as $key => $value) { 1348 | //$_column['FOUND_ROWS()'] = $value; 1349 | //} 1350 | $column[] = new ObjectArray($_column); 1351 | $this->results = $column; 1352 | $this->found_rows_result = null; 1353 | } 1354 | break; 1355 | case 'insert': 1356 | if ($this->can_insert_multiple_rows) { 1357 | $this->execute_insert_query_new($statement); 1358 | } else { 1359 | $this->execute_insert_query($statement); 1360 | } 1361 | break; 1362 | case 'create': 1363 | $result = $this->execute_create_query($statement); 1364 | $this->return_value = $result; 1365 | break; 1366 | case 'alter': 1367 | $result = $this->execute_alter_query($statement); 1368 | $this->return_value = $result; 1369 | break; 1370 | case 'show_variables': 1371 | $this->return_value = $this->show_variables_workaround($statement); 1372 | break; 1373 | case 'showstatus': 1374 | $this->return_value = $this->show_status_workaround($statement); 1375 | break; 1376 | case 'drop_index': 1377 | $pattern = '/^\\s*(DROP\\s*INDEX\\s*.*?)\\s*ON\\s*(.*)/im'; 1378 | if (preg_match($pattern, $statement, $match)) { 1379 | $drop_query = 'ALTER TABLE ' . trim($match[2]) . ' ' . trim($match[1]); 1380 | $this->query_type = 'alter'; 1381 | $result = $this->execute_alter_query($drop_query); 1382 | $this->return_value = $result; 1383 | } else { 1384 | $this->return_value = false; 1385 | } 1386 | break; 1387 | default: 1388 | $engine = $this->prepare_engine($this->query_type); 1389 | $this->rewritten_query = $engine->rewrite_query($statement, $this->query_type); 1390 | if (! is_null($this->pre_ordered_results)) { 1391 | $this->results = $this->pre_ordered_results; 1392 | $this->num_rows = $this->return_value = count($this->results); 1393 | $this->pre_ordered_results = null; 1394 | break; 1395 | } 1396 | $this->queries[] = "Rewritten:\n$this->rewritten_query"; 1397 | $this->extract_variables(); 1398 | $prepared_query = $this->prepare_query(); 1399 | $this->execute_query($prepared_query); 1400 | if (! $this->is_error) { 1401 | $this->process_results($engine); 1402 | } else { 1403 | // Error 1404 | } 1405 | break; 1406 | } 1407 | if (defined('PDO_DEBUG') && PDO_DEBUG === true) { 1408 | file_put_contents(FQDBDIR . 'debug.txt', $this->get_debug_info(), FILE_APPEND); 1409 | } 1410 | 1411 | return $this->return_value; 1412 | } 1413 | 1414 | /** 1415 | * Method to return inserted row id. 1416 | */ 1417 | public function get_insert_id() 1418 | { 1419 | return $this->last_insert_id; 1420 | } 1421 | 1422 | /** 1423 | * Method to return the number of rows affected. 1424 | */ 1425 | public function get_affected_rows() 1426 | { 1427 | return $this->affected_rows; 1428 | } 1429 | 1430 | /** 1431 | * Method to return the queried column names. 1432 | * 1433 | * These data are meaningless for SQLite. So they are dummy emulating 1434 | * MySQL columns data. 1435 | * 1436 | * @return array of the object 1437 | */ 1438 | public function get_columns() 1439 | { 1440 | if (! empty($this->results)) { 1441 | $primary_key = [ 1442 | 'meta_id', 1443 | 'comment_ID', 1444 | 'link_ID', 1445 | 'option_id', 1446 | 'blog_id', 1447 | 'option_name', 1448 | 'ID', 1449 | 'term_id', 1450 | 'object_id', 1451 | 'term_taxonomy_id', 1452 | 'umeta_id', 1453 | 'id', 1454 | ]; 1455 | $unique_key = ['term_id', 'taxonomy', 'slug']; 1456 | $data = [ 1457 | 'name' => '', // column name 1458 | 'table' => '', // table name 1459 | 'max_length' => 0, // max length of the column 1460 | 'not_null' => 1, // 1 if not null 1461 | 'primary_key' => 0, // 1 if column has primary key 1462 | 'unique_key' => 0, // 1 if column has unique key 1463 | 'multiple_key' => 0, // 1 if column doesn't have unique key 1464 | 'numeric' => 0, // 1 if column has numeric value 1465 | 'blob' => 0, // 1 if column is blob 1466 | 'type' => '', // type of the column 1467 | 'unsigned' => 0, // 1 if column is unsigned integer 1468 | 'zerofill' => 0 // 1 if column is zero-filled 1469 | ]; 1470 | if (preg_match("/\s*FROM\s*(.*)?\s*/i", $this->rewritten_query, $match)) { 1471 | $table_name = trim($match[1]); 1472 | } else { 1473 | $table_name = ''; 1474 | } 1475 | foreach ($this->results[0] as $key => $value) { 1476 | $data['name'] = $key; 1477 | $data['table'] = $table_name; 1478 | if (in_array($key, $primary_key)) { 1479 | $data['primary_key'] = 1; 1480 | } elseif (in_array($key, $unique_key)) { 1481 | $data['unique_key'] = 1; 1482 | } else { 1483 | $data['multiple_key'] = 1; 1484 | } 1485 | $this->column_data[] = new ObjectArray($data); 1486 | $data['name'] = ''; 1487 | $data['table'] = ''; 1488 | $data['primary_key'] = 0; 1489 | $data['unique_key'] = 0; 1490 | $data['multiple_key'] = 0; 1491 | } 1492 | 1493 | return $this->column_data; 1494 | } else { 1495 | return null; 1496 | } 1497 | } 1498 | 1499 | /** 1500 | * Method to return the queried result data. 1501 | * 1502 | * @return mixed 1503 | */ 1504 | public function get_query_results() 1505 | { 1506 | return $this->results; 1507 | } 1508 | 1509 | /** 1510 | * Method to return the number of rows from the queried result. 1511 | */ 1512 | public function get_num_rows() 1513 | { 1514 | return $this->num_rows; 1515 | } 1516 | 1517 | /** 1518 | * Method to return the queried results according to the query types. 1519 | * 1520 | * @return mixed 1521 | */ 1522 | public function get_return_value() 1523 | { 1524 | return $this->return_value; 1525 | } 1526 | 1527 | /** 1528 | * Method to return error messages. 1529 | * 1530 | * @return string 1531 | */ 1532 | public function get_error_message() 1533 | { 1534 | if (count($this->error_messages) === 0) { 1535 | $this->is_error = false; 1536 | $this->error_messages = []; 1537 | 1538 | return ''; 1539 | } 1540 | $output = '
 
'; 1541 | if ($this->is_error === false) { 1542 | //return $output; 1543 | return ''; 1544 | } 1545 | $output .= "
Queries made or created this session were
\r\n\t
    \r\n"; 1546 | foreach ($this->queries as $q) { 1547 | $output .= "\t\t
  1. " . $q . "
  2. \r\n"; 1548 | } 1549 | $output .= "\t
\r\n
"; 1550 | foreach ($this->error_messages as $num => $m) { 1551 | $output .= "
Error occurred at line {$this->errors[$num]['line']} in Function {$this->errors[$num]['function']}.
Error message was: $m
"; 1552 | } 1553 | 1554 | ob_start(); 1555 | debug_print_backtrace(); 1556 | $output .= '
' . ob_get_contents() . '
'; 1557 | ob_end_clean(); 1558 | 1559 | return $output; 1560 | 1561 | } 1562 | 1563 | /** 1564 | * Method to return information about query string for debugging. 1565 | * 1566 | * @return string 1567 | */ 1568 | private function get_debug_info() 1569 | { 1570 | $output = ''; 1571 | foreach ($this->queries as $q) { 1572 | $output .= $q . "\n"; 1573 | } 1574 | 1575 | return $output; 1576 | } 1577 | 1578 | /** 1579 | * Method to clear previous data. 1580 | */ 1581 | private function flush() 1582 | { 1583 | $this->rewritten_query = ''; 1584 | $this->query_type = ''; 1585 | $this->results = null; 1586 | $this->_results = null; 1587 | $this->last_insert_id = null; 1588 | $this->affected_rows = null; 1589 | $this->column_data = []; 1590 | $this->num_rows = null; 1591 | $this->return_value = null; 1592 | $this->extracted_variables = []; 1593 | $this->error_messages = []; 1594 | $this->is_error = false; 1595 | $this->queries = []; 1596 | $this->param_num = 0; 1597 | } 1598 | 1599 | /** 1600 | * Method to include the apropreate class files. 1601 | * 1602 | * It is not a good habit to change the include files programatically. 1603 | * Needs to be fixed some other way. 1604 | * 1605 | * @param string $query_type 1606 | * 1607 | * @return object reference to apropreate driver 1608 | */ 1609 | private function prepare_engine($query_type = null) 1610 | { 1611 | if (stripos($query_type, 'create') !== false) { 1612 | $engine = new CreateQuery(); 1613 | } elseif (stripos($query_type, 'alter') !== false) { 1614 | $engine = new AlterQuery(); 1615 | } else { 1616 | $engine = new PDOSQLiteDriver(); 1617 | } 1618 | 1619 | return $engine; 1620 | } 1621 | 1622 | /** 1623 | * Method to create a PDO statement object from the query string. 1624 | * 1625 | * @return PDOStatement 1626 | */ 1627 | private function prepare_query() 1628 | { 1629 | $this->queries[] = "Prepare:\n" . $this->prepared_query; 1630 | $reason = 0; 1631 | $message = ''; 1632 | $statement = null; 1633 | do { 1634 | try { 1635 | $statement = $this->pdo->prepare($this->prepared_query); 1636 | } catch (PDOException $err) { 1637 | $reason = $err->getCode(); 1638 | $message = $err->getMessage(); 1639 | } 1640 | } while (5 == $reason || 6 == $reason); 1641 | 1642 | if ($reason > 0) { 1643 | $err_message = sprintf("Problem preparing the PDO SQL Statement. Error was: %s", $message); 1644 | $this->set_error(__LINE__, __FUNCTION__, $err_message); 1645 | } 1646 | 1647 | return $statement; 1648 | } 1649 | 1650 | /** 1651 | * Method to execute PDO statement object. 1652 | * 1653 | * This function executes query and sets the variables to give back to WordPress. 1654 | * The variables are class fields. So if success, no return value. If failure, it 1655 | * returns void and stops. 1656 | * 1657 | * @param object $statement of PDO statement 1658 | * 1659 | * @return boolean 1660 | */ 1661 | private function execute_query($statement) 1662 | { 1663 | $reason = 0; 1664 | $message = ''; 1665 | if (! is_object($statement)) { 1666 | return false; 1667 | } 1668 | if (count($this->extracted_variables) > 0) { 1669 | $this->queries[] = "Executing:\n" . var_export($this->extracted_variables, true); 1670 | do { 1671 | if ($this->query_type == 'update' || $this->query_type == 'replace') { 1672 | try { 1673 | $this->beginTransaction(); 1674 | $statement->execute($this->extracted_variables); 1675 | $this->commit(); 1676 | } catch (PDOException $err) { 1677 | $reason = $err->getCode(); 1678 | $message = $err->getMessage(); 1679 | $this->rollBack(); 1680 | } 1681 | } else { 1682 | try { 1683 | $statement->execute($this->extracted_variables); 1684 | } catch (PDOException $err) { 1685 | $reason = $err->getCode(); 1686 | $message = $err->getMessage(); 1687 | } 1688 | } 1689 | } while (5 == $reason || 6 == $reason); 1690 | } else { 1691 | $this->queries[] = 'Executing: (no parameters)'; 1692 | do { 1693 | if ($this->query_type == 'update' || $this->query_type == 'replace') { 1694 | try { 1695 | $this->beginTransaction(); 1696 | $statement->execute(); 1697 | $this->commit(); 1698 | } catch (PDOException $err) { 1699 | $reason = $err->getCode(); 1700 | $message = $err->getMessage(); 1701 | $this->rollBack(); 1702 | } 1703 | } else { 1704 | try { 1705 | $statement->execute(); 1706 | } catch (PDOException $err) { 1707 | $reason = $err->getCode(); 1708 | $message = $err->getMessage(); 1709 | } 1710 | } 1711 | } while (5 == $reason || 6 == $reason); 1712 | } 1713 | if ($reason > 0) { 1714 | $err_message = sprintf("Error while executing query! Error message was: %s", $message); 1715 | $this->set_error(__LINE__, __FUNCTION__, $err_message); 1716 | 1717 | return false; 1718 | } else { 1719 | $this->_results = $statement->fetchAll(PDO::FETCH_OBJ); 1720 | } 1721 | //generate the results that $wpdb will want to see 1722 | switch ($this->query_type) { 1723 | case 'insert': 1724 | case 'update': 1725 | case 'replace': 1726 | $this->last_insert_id = $this->pdo->lastInsertId(); 1727 | $this->affected_rows = $statement->rowCount(); 1728 | $this->return_value = $this->affected_rows; 1729 | break; 1730 | case 'select': 1731 | case 'show': 1732 | case 'showcolumns': 1733 | case 'showindex': 1734 | case 'describe': 1735 | case 'desc': 1736 | case 'check': 1737 | case 'analyze': 1738 | //case "foundrows": 1739 | $this->num_rows = count($this->_results); 1740 | $this->return_value = $this->num_rows; 1741 | break; 1742 | case 'delete': 1743 | $this->affected_rows = $statement->rowCount(); 1744 | $this->return_value = $this->affected_rows; 1745 | break; 1746 | case 'alter': 1747 | case 'drop': 1748 | case 'create': 1749 | case 'optimize': 1750 | case 'truncate': 1751 | if ($this->is_error) { 1752 | $this->return_value = false; 1753 | } else { 1754 | $this->return_value = true; 1755 | } 1756 | break; 1757 | } 1758 | } 1759 | 1760 | /** 1761 | * Method to extract field data to an array and prepare the query statement. 1762 | * 1763 | * If original SQL statement is CREATE query, this function does nothing. 1764 | */ 1765 | private function extract_variables() 1766 | { 1767 | if ($this->query_type == 'create') { 1768 | $this->prepared_query = $this->rewritten_query; 1769 | 1770 | return; 1771 | } 1772 | 1773 | //long queries can really kill this 1774 | $pattern = '/(? 10000000) { 1778 | $query = preg_replace_callback($pattern, [$this, 'replace_variables_with_placeholders'], 1779 | $this->rewritten_query); 1780 | } else { 1781 | do { 1782 | if ($limit > 10000000) { 1783 | $this->set_error(__LINE__, __FUNCTION__, 'The query is too big to parse properly'); 1784 | break; //no point in continuing execution, would get into a loop 1785 | } else { 1786 | ini_set('pcre.backtrack_limit', $limit); 1787 | $query = preg_replace_callback($pattern, [$this, 'replace_variables_with_placeholders'], 1788 | $this->rewritten_query); 1789 | } 1790 | $limit = $limit * 10; 1791 | } while (is_null($query)); 1792 | 1793 | //reset the pcre.backtrack_limit 1794 | ini_set('pcre.backtrack_limit', $_limit); 1795 | } 1796 | 1797 | if (isset($query)) { 1798 | $this->queries[] = "With Placeholders:\n" . $query; 1799 | $this->prepared_query = $query; 1800 | } 1801 | } 1802 | 1803 | /** 1804 | * Call back function to replace field data with PDO parameter. 1805 | * 1806 | * @param string $matches 1807 | * 1808 | * @return string 1809 | */ 1810 | private function replace_variables_with_placeholders($matches) 1811 | { 1812 | //remove the wordpress escaping mechanism 1813 | $param = stripslashes($matches[0]); 1814 | 1815 | //remove trailing spaces 1816 | $param = trim($param); 1817 | 1818 | //remove the quotes at the end and the beginning 1819 | if (in_array($param[strlen($param) - 1], ["'", '"'])) { 1820 | $param = substr($param, 0, -1);//end 1821 | } 1822 | if (in_array($param[0], ["'", '"'])) { 1823 | $param = substr($param, 1); //start 1824 | } 1825 | //$this->extracted_variables[] = $param; 1826 | $key = ':param_' . $this->param_num++; 1827 | $this->extracted_variables[] = $param; 1828 | //return the placeholder 1829 | //return ' ? '; 1830 | return ' ' . $key . ' '; 1831 | } 1832 | 1833 | /** 1834 | * Method to determine which query type the argument is. 1835 | * 1836 | * It takes the query string ,determines the type and returns the type string. 1837 | * If the query is the type that SQLite Integration can't executes, returns false. 1838 | * 1839 | * @param string $query 1840 | * 1841 | * @return boolean|string 1842 | */ 1843 | private function determine_query_type($query) 1844 | { 1845 | $result = preg_match('/^\\s*(SET|EXPLAIN|PRAGMA|SELECT\\s*FOUND_ROWS|SELECT|INSERT|UPDATE|REPLACE|DELETE|ALTER|CREATE|DROP\\s*INDEX|DROP|SHOW\\s*\\w+\\s*\\w+\\s*|DESCRIBE|DESC|TRUNCATE|OPTIMIZE|CHECK|ANALYZE)/i', 1846 | $query, $match); 1847 | 1848 | if (! $result) { 1849 | return false; 1850 | } 1851 | $this->query_type = strtolower($match[1]); 1852 | if (stripos($this->query_type, 'found') !== false) { 1853 | $this->query_type = 'foundrows'; 1854 | } 1855 | if (stripos($this->query_type, 'show') !== false) { 1856 | if (stripos($this->query_type, 'show table status') !== false) { 1857 | $this->query_type = 'showstatus'; 1858 | } elseif (stripos($this->query_type, 'show tables') !== false || stripos($this->query_type, 1859 | 'show full tables') !== false) { 1860 | $this->query_type = 'show'; 1861 | } elseif (stripos($this->query_type, 'show columns') !== false || stripos($this->query_type, 1862 | 'show fields') !== false || stripos($this->query_type, 'show full columns') !== false) { 1863 | $this->query_type = 'showcolumns'; 1864 | } elseif (stripos($this->query_type, 'show index') !== false || stripos($this->query_type, 1865 | 'show indexes') !== false || stripos($this->query_type, 'show keys') !== false) { 1866 | $this->query_type = 'showindex'; 1867 | } elseif (stripos($this->query_type, 'show variables') !== false || stripos($this->query_type, 1868 | 'show global variables') !== false || stripos($this->query_type, 1869 | 'show session variables') !== false) { 1870 | $this->query_type = 'show_variables'; 1871 | } else { 1872 | return false; 1873 | } 1874 | } 1875 | if (stripos($this->query_type, 'drop index') !== false) { 1876 | $this->query_type = 'drop_index'; 1877 | } 1878 | 1879 | return true; 1880 | } 1881 | 1882 | /** 1883 | * Method to execute INSERT query for SQLite version 3.7.11 or later. 1884 | * 1885 | * SQLite version 3.7.11 began to support multiple rows insert with values 1886 | * clause. This is for that version or later. 1887 | * 1888 | * @param string $query 1889 | */ 1890 | private function execute_insert_query_new($query) 1891 | { 1892 | $engine = $this->prepare_engine($this->query_type); 1893 | $this->rewritten_query = $engine->rewrite_query($query, $this->query_type); 1894 | $this->queries[] = "Rewritten:\n" . $this->rewritten_query; 1895 | $this->extract_variables(); 1896 | $statement = $this->prepare_query(); 1897 | $this->execute_query($statement); 1898 | } 1899 | 1900 | /** 1901 | * Method to execute INSERT query for SQLite version 3.7.10 or lesser. 1902 | * 1903 | * It executes the INSERT query for SQLite version 3.7.10 or lesser. It is 1904 | * necessary to rewrite multiple row values. 1905 | * 1906 | * @param string $query 1907 | */ 1908 | private function execute_insert_query($query) 1909 | { 1910 | global $wpdb; 1911 | $multi_insert = false; 1912 | $statement = null; 1913 | $engine = $this->prepare_engine($this->query_type); 1914 | if (preg_match('/(INSERT.*?VALUES\\s*)(\(.*\))/imsx', $query, $matched)) { 1915 | $query_prefix = $matched[1]; 1916 | $values_data = $matched[2]; 1917 | if (stripos($values_data, 'ON DUPLICATE KEY') !== false) { 1918 | $exploded_parts = $values_data; 1919 | } elseif (stripos($query_prefix, "INSERT INTO $wpdb->comments") !== false) { 1920 | $exploded_parts = $values_data; 1921 | } else { 1922 | $exploded_parts = $this->parse_multiple_inserts($values_data); 1923 | } 1924 | $count = count($exploded_parts); 1925 | if ($count > 1) { 1926 | $multi_insert = true; 1927 | } 1928 | } 1929 | if ($multi_insert) { 1930 | $first = true; 1931 | foreach ($exploded_parts as $value) { 1932 | if (substr($value, -1, 1) === ')') { 1933 | $suffix = ''; 1934 | } else { 1935 | $suffix = ')'; 1936 | } 1937 | $query_string = $query_prefix . ' ' . $value . $suffix; 1938 | $this->rewritten_query = $engine->rewrite_query($query_string, $this->query_type); 1939 | $this->queries[] = "Rewritten:\n" . $this->rewritten_query; 1940 | $this->extracted_variables = []; 1941 | $this->extract_variables(); 1942 | if ($first) { 1943 | $statement = $this->prepare_query(); 1944 | $this->execute_query($statement); 1945 | $first = false; 1946 | } else { 1947 | $this->execute_query($statement); 1948 | } 1949 | } 1950 | } else { 1951 | $this->rewritten_query = $engine->rewrite_query($query, $this->query_type); 1952 | $this->queries[] = "Rewritten:\n" . $this->rewritten_query; 1953 | $this->extract_variables(); 1954 | $statement = $this->prepare_query(); 1955 | $this->execute_query($statement); 1956 | } 1957 | } 1958 | 1959 | /** 1960 | * Method to help rewriting multiple row values insert query. 1961 | * 1962 | * It splits the values clause into an array to execute separately. 1963 | * 1964 | * @param string $values 1965 | * 1966 | * @return array 1967 | */ 1968 | private function parse_multiple_inserts($values) 1969 | { 1970 | $tokens = preg_split("/(''|(?prepare_engine($this->query_type); 2014 | $rewritten_query = $engine->rewrite_query($query); 2015 | $reason = 0; 2016 | $message = ''; 2017 | //$queries = explode(";", $this->rewritten_query); 2018 | try { 2019 | $this->beginTransaction(); 2020 | foreach ($rewritten_query as $single_query) { 2021 | $this->queries[] = "Executing:\n" . $single_query; 2022 | $single_query = trim($single_query); 2023 | if (empty($single_query)) { 2024 | continue; 2025 | } 2026 | $this->pdo->exec($single_query); 2027 | } 2028 | $this->commit(); 2029 | } catch (PDOException $err) { 2030 | $reason = $err->getCode(); 2031 | $message = $err->getMessage(); 2032 | if (5 == $reason || 6 == $reason) { 2033 | $this->commit(); 2034 | } else { 2035 | $this->rollBack(); 2036 | } 2037 | } 2038 | if ($reason > 0) { 2039 | $err_message = sprintf("Problem in creating table or index. Error was: %s", $message); 2040 | $this->set_error(__LINE__, __FUNCTION__, $err_message); 2041 | 2042 | return false; 2043 | } 2044 | 2045 | return true; 2046 | } 2047 | 2048 | /** 2049 | * Method to execute ALTER TABLE query. 2050 | * 2051 | * @param string 2052 | * 2053 | * @return boolean 2054 | */ 2055 | private function execute_alter_query($query) 2056 | { 2057 | $engine = $this->prepare_engine($this->query_type); 2058 | $reason = 0; 2059 | $message = ''; 2060 | $re_query = ''; 2061 | $rewritten_query = $engine->rewrite_query($query, $this->query_type); 2062 | if (is_array($rewritten_query) && array_key_exists('recursion', $rewritten_query)) { 2063 | $re_query = $rewritten_query['recursion']; 2064 | unset($rewritten_query['recursion']); 2065 | } 2066 | try { 2067 | $this->beginTransaction(); 2068 | if (is_array($rewritten_query)) { 2069 | foreach ($rewritten_query as $single_query) { 2070 | $this->queries[] = "Executing:\n" . $single_query; 2071 | $single_query = trim($single_query); 2072 | if (empty($single_query)) { 2073 | continue; 2074 | } 2075 | $this->pdo->exec($single_query); 2076 | } 2077 | } else { 2078 | $this->queries[] = "Executing:\n" . $rewritten_query; 2079 | $rewritten_query = trim($rewritten_query); 2080 | $this->pdo->exec($rewritten_query); 2081 | } 2082 | $this->commit(); 2083 | } catch (PDOException $err) { 2084 | $reason = $err->getCode(); 2085 | $message = $err->getMessage(); 2086 | if (5 == $reason || 6 == $reason) { 2087 | $this->commit(); 2088 | usleep(10000); 2089 | } else { 2090 | $this->rollBack(); 2091 | } 2092 | } 2093 | if ($re_query != '') { 2094 | $this->query($re_query); 2095 | } 2096 | if ($reason > 0) { 2097 | $err_message = sprintf("Problem in executing alter query. Error was: %s", $message); 2098 | $this->set_error(__LINE__, __FUNCTION__, $err_message); 2099 | 2100 | return false; 2101 | } 2102 | 2103 | return true; 2104 | } 2105 | 2106 | /** 2107 | * Method to execute SHOW VARIABLES query 2108 | * 2109 | * This query is meaningless for SQLite. This function returns null data with some 2110 | * exceptions and only avoids the error message. 2111 | * 2112 | * @param string 2113 | * 2114 | * @return bool 2115 | */ 2116 | private function show_variables_workaround($query) 2117 | { 2118 | $dummy_data = ['Variable_name' => '', 'Value' => null]; 2119 | $pattern = '/SHOW\\s*VARIABLES\\s*LIKE\\s*(.*)?$/im'; 2120 | if (preg_match($pattern, $query, $match)) { 2121 | $value = str_replace("'", '', $match[1]); 2122 | $dummy_data['Variable_name'] = trim($value); 2123 | // this is set for Wordfence Security Plugin 2124 | if ($value == 'max_allowed_packet') { 2125 | $dummy_data['Value'] = 1047552; 2126 | } else { 2127 | $dummy_data['Value'] = ''; 2128 | } 2129 | } 2130 | $_results[] = new ObjectArray($dummy_data); 2131 | $this->results = $_results; 2132 | $this->num_rows = count($this->results); 2133 | $this->return_value = $this->num_rows; 2134 | 2135 | return true; 2136 | } 2137 | 2138 | /** 2139 | * Method to execute SHOW TABLE STATUS query. 2140 | * 2141 | * This query is meaningless for SQLite. This function return dummy data. 2142 | * 2143 | * @param string 2144 | * 2145 | * @return bool 2146 | */ 2147 | private function show_status_workaround($query) 2148 | { 2149 | $pattern = '/^SHOW\\s*TABLE\\s*STATUS\\s*LIKE\\s*(.*?)$/im'; 2150 | if (preg_match($pattern, $query, $match)) { 2151 | $table_name = str_replace("'", '', $match[1]); 2152 | } else { 2153 | $table_name = ''; 2154 | } 2155 | $dummy_data = [ 2156 | 'Name' => $table_name, 2157 | 'Engine' => '', 2158 | 'Version' => '', 2159 | 'Row_format' => '', 2160 | 'Rows' => 0, 2161 | 'Avg_row_length' => 0, 2162 | 'Data_length' => 0, 2163 | 'Max_data_length' => 0, 2164 | 'Index_length' => 0, 2165 | 'Data_free' => 0, 2166 | 'Auto_increment' => 0, 2167 | 'Create_time' => '', 2168 | 'Update_time' => '', 2169 | 'Check_time' => '', 2170 | 'Collation' => '', 2171 | 'Checksum' => '', 2172 | 'Create_options' => '', 2173 | 'Comment' => '', 2174 | ]; 2175 | $_results[] = new ObjectArray($dummy_data); 2176 | $this->results = $_results; 2177 | $this->num_rows = count($this->results); 2178 | $this->return_value = $this->num_rows; 2179 | 2180 | return true; 2181 | } 2182 | 2183 | /** 2184 | * Method to format the queried data to that of MySQL. 2185 | * 2186 | * @param string $engine 2187 | */ 2188 | private function process_results($engine) 2189 | { 2190 | if (in_array($this->query_type, ['describe', 'desc', 'showcolumns'])) { 2191 | $this->convert_to_columns_object(); 2192 | } elseif ('showindex' === $this->query_type) { 2193 | $this->convert_to_index_object(); 2194 | } elseif (in_array($this->query_type, ['check', 'analyze'])) { 2195 | $this->convert_result_check_or_analyze(); 2196 | } else { 2197 | $this->results = $this->_results; 2198 | } 2199 | } 2200 | 2201 | /** 2202 | * Method to format the error messages and put out to the file. 2203 | * 2204 | * When $wpdb::suppress_errors is set to true or $wpdb::show_errors is set to false, 2205 | * the error messages are ignored. 2206 | * 2207 | * @param string $line where the error occurred. 2208 | * @param string $function to indicate the function name where the error occurred. 2209 | * @param string $message 2210 | * 2211 | * @return boolean 2212 | */ 2213 | private function set_error($line, $function, $message) 2214 | { 2215 | global $wpdb; 2216 | $this->errors[] = ["line" => $line, "function" => $function]; 2217 | $this->error_messages[] = $message; 2218 | $this->is_error = true; 2219 | if ($wpdb->suppress_errors) { 2220 | return false; 2221 | } 2222 | if (! $wpdb->show_errors) { 2223 | return false; 2224 | } 2225 | file_put_contents(FQDBDIR . 'debug.txt', "Line $line, Function: $function, Message: $message \n", FILE_APPEND); 2226 | } 2227 | 2228 | /** 2229 | * Method to change the queried data to PHP object format. 2230 | * 2231 | * It takes the associative array of query results and creates a numeric 2232 | * array of anonymous objects 2233 | * 2234 | * @access private 2235 | */ 2236 | private function convert_to_object() 2237 | { 2238 | $_results = []; 2239 | if (count($this->results) === 0) { 2240 | echo $this->get_error_message(); 2241 | } else { 2242 | foreach ($this->results as $row) { 2243 | $_results[] = new ObjectArray($row); 2244 | } 2245 | } 2246 | $this->results = $_results; 2247 | } 2248 | 2249 | /** 2250 | * Method to convert the SHOW COLUMNS query data to an object. 2251 | * 2252 | * It rewrites pragma results to mysql compatible array 2253 | * when query_type is describe, we use sqlite pragma function. 2254 | * 2255 | * @access private 2256 | */ 2257 | private function convert_to_columns_object() 2258 | { 2259 | $_results = []; 2260 | $_columns = [ //Field names MySQL SHOW COLUMNS returns 2261 | 'Field' => "", 2262 | 'Type' => "", 2263 | 'Null' => "", 2264 | 'Key' => "", 2265 | 'Default' => "", 2266 | 'Extra' => "", 2267 | ]; 2268 | if (empty($this->_results)) { 2269 | echo $this->get_error_message(); 2270 | } else { 2271 | foreach ($this->_results as $row) { 2272 | $_columns['Field'] = $row->name; 2273 | $_columns['Type'] = $row->type; 2274 | $_columns['Null'] = $row->notnull ? "NO" : "YES"; 2275 | $_columns['Key'] = $row->pk ? "PRI" : ""; 2276 | $_columns['Default'] = $row->dflt_value; 2277 | $_results[] = new ObjectArray($_columns); 2278 | } 2279 | } 2280 | $this->results = $_results; 2281 | } 2282 | 2283 | /** 2284 | * Method to convert SHOW INDEX query data to PHP object. 2285 | * 2286 | * It rewrites the result of SHOW INDEX to the Object compatible with MySQL 2287 | * added the WHERE clause manipulation (ver 1.3.1) 2288 | * 2289 | * @access private 2290 | */ 2291 | private function convert_to_index_object() 2292 | { 2293 | $_results = []; 2294 | $_columns = [ 2295 | 'Table' => "", 2296 | 'Non_unique' => "",// unique -> 0, not unique -> 1 2297 | 'Key_name' => "",// the name of the index 2298 | 'Seq_in_index' => "",// column sequence number in the index. begins at 1 2299 | 'Column_name' => "", 2300 | 'Collation' => "",//A(scend) or NULL 2301 | 'Cardinality' => "", 2302 | 'Sub_part' => "",// set to NULL 2303 | 'Packed' => "",// How to pack key or else NULL 2304 | 'Null' => "",// If column contains null, YES. If not, NO. 2305 | 'Index_type' => "",// BTREE, FULLTEXT, HASH, RTREE 2306 | 'Comment' => "", 2307 | ]; 2308 | if (count($this->_results) == 0) { 2309 | echo $this->get_error_message(); 2310 | } else { 2311 | foreach ($this->_results as $row) { 2312 | if ($row->type == 'table' && ! stripos($row->sql, 'primary')) { 2313 | continue; 2314 | } 2315 | if ($row->type == 'index' && stripos($row->name, 'sqlite_autoindex') !== false) { 2316 | continue; 2317 | } 2318 | switch ($row->type) { 2319 | case 'table': 2320 | $pattern1 = '/^\\s*PRIMARY.*\((.*)\)/im'; 2321 | $pattern2 = '/^\\s*(\\w+)?\\s*.*PRIMARY.*(?!\()/im'; 2322 | if (preg_match($pattern1, $row->sql, $match)) { 2323 | $col_name = trim($match[1]); 2324 | $_columns['Key_name'] = 'PRIMARY'; 2325 | $_columns['Non_unique'] = 0; 2326 | $_columns['Column_name'] = $col_name; 2327 | } elseif (preg_match($pattern2, $row->sql, $match)) { 2328 | $col_name = trim($match[1]); 2329 | $_columns['Key_name'] = 'PRIMARY'; 2330 | $_columns['Non_unique'] = 0; 2331 | $_columns['Column_name'] = $col_name; 2332 | } 2333 | break; 2334 | case 'index': 2335 | if (stripos($row->sql, 'unique') !== false) { 2336 | $_columns['Non_unique'] = 0; 2337 | } else { 2338 | $_columns['Non_unique'] = 1; 2339 | } 2340 | if (preg_match('/^.*\((.*)\)/i', $row->sql, $match)) { 2341 | $col_name = str_replace("'", '', $match[1]); 2342 | $_columns['Column_name'] = trim($col_name); 2343 | } 2344 | $_columns['Key_name'] = $row->name; 2345 | break; 2346 | default: 2347 | break; 2348 | } 2349 | $_columns['Table'] = $row->tbl_name; 2350 | $_columns['Collation'] = null; 2351 | $_columns['Cardinality'] = 0; 2352 | $_columns['Sub_part'] = null; 2353 | $_columns['Packed'] = null; 2354 | $_columns['Null'] = 'NO'; 2355 | $_columns['Index_type'] = 'BTREE'; 2356 | $_columns['Comment'] = ''; 2357 | $_results[] = new ObjectArray($_columns); 2358 | } 2359 | if (stripos($this->queries[0], 'WHERE') !== false) { 2360 | preg_match('/WHERE\\s*(.*)$/im', $this->queries[0], $match); 2361 | list($key, $value) = explode('=', $match[1]); 2362 | $key = trim($key); 2363 | $value = preg_replace("/[\';]/", '', $value); 2364 | $value = trim($value); 2365 | foreach ($_results as $result) { 2366 | if (! empty($result->$key) && is_scalar($result->$key) && stripos($value, $result->$key) !== false) { 2367 | unset($_results); 2368 | $_results[] = $result; 2369 | break; 2370 | } 2371 | } 2372 | } 2373 | } 2374 | $this->results = $_results; 2375 | } 2376 | 2377 | /** 2378 | * Method to the CHECK query data to an object. 2379 | * 2380 | * @access private 2381 | */ 2382 | private function convert_result_check_or_analyze() 2383 | { 2384 | $results = []; 2385 | if ($this->query_type == 'check') { 2386 | $_columns = [ 2387 | 'Table' => '', 2388 | 'Op' => 'check', 2389 | 'Msg_type' => 'status', 2390 | 'Msg_text' => 'OK', 2391 | ]; 2392 | } else { 2393 | $_columns = [ 2394 | 'Table' => '', 2395 | 'Op' => 'analyze', 2396 | 'Msg_type' => 'status', 2397 | 'Msg_text' => 'Table is already up to date', 2398 | ]; 2399 | } 2400 | $_results[] = new ObjectArray($_columns); 2401 | $this->results = $_results; 2402 | } 2403 | 2404 | /** 2405 | * Method to check SQLite library version. 2406 | * 2407 | * This is used for checking if SQLite can execute multiple rows insert. 2408 | * 2409 | * @return version number string or 0 2410 | * @access private 2411 | */ 2412 | private function get_sqlite_version() 2413 | { 2414 | try { 2415 | $statement = $this->pdo->prepare('SELECT sqlite_version()'); 2416 | $statement->execute(); 2417 | $result = $statement->fetch(PDO::FETCH_NUM); 2418 | 2419 | return $result[0]; 2420 | } catch (PDOException $err) { 2421 | return '0'; 2422 | } 2423 | } 2424 | 2425 | /** 2426 | * Method to call PDO::beginTransaction(). 2427 | * 2428 | * @see PDO::beginTransaction() 2429 | * @return boolean 2430 | */ 2431 | #[\ReturnTypeWillChange] 2432 | public function beginTransaction() 2433 | { 2434 | if ($this->has_active_transaction) { 2435 | return false; 2436 | } 2437 | 2438 | $this->has_active_transaction = $this->pdo->beginTransaction(); 2439 | 2440 | return $this->has_active_transaction; 2441 | } 2442 | 2443 | /** 2444 | * Method to call PDO::commit(). 2445 | * 2446 | * @see PDO::commit() 2447 | */ 2448 | #[\ReturnTypeWillChange] 2449 | public function commit() 2450 | { 2451 | $isSuccess = $this->pdo->commit(); 2452 | $this->has_active_transaction = false; 2453 | 2454 | return $isSuccess; 2455 | } 2456 | 2457 | /** 2458 | * Method to call PDO::rollBack(). 2459 | * 2460 | * @see PDO::rollBack() 2461 | */ 2462 | #[\ReturnTypeWillChange] 2463 | public function rollBack() 2464 | { 2465 | $isSuccess = $this->pdo->rollBack(); 2466 | $this->has_active_transaction = false; 2467 | 2468 | return $isSuccess; 2469 | } 2470 | } 2471 | 2472 | /** 2473 | * Class to change queried data to PHP object. 2474 | * 2475 | * @author kjm 2476 | */ 2477 | #[\AllowDynamicProperties] 2478 | class ObjectArray 2479 | { 2480 | function __construct($data = null, &$node = null) 2481 | { 2482 | foreach ($data as $key => $value) { 2483 | if (is_array($value)) { 2484 | if (! $node) { 2485 | $node =& $this; 2486 | } 2487 | $node->$key = new \stdClass(); 2488 | self::__construct($value, $node->$key); 2489 | } else { 2490 | if (! $node) { 2491 | $node =& $this; 2492 | } 2493 | $node->$key = $value; 2494 | } 2495 | } 2496 | } 2497 | } 2498 | 2499 | /** 2500 | * This class extends wpdb and replaces it. 2501 | * 2502 | * It also rewrites some methods that use mysql specific functions. 2503 | */ 2504 | class wpsqlitedb extends \wpdb 2505 | { 2506 | /** 2507 | * Database Handle 2508 | * @var PDOEngine 2509 | */ 2510 | protected $dbh; 2511 | 2512 | /** 2513 | * Constructor 2514 | * 2515 | * Unlike wpdb, no credentials are needed. 2516 | */ 2517 | public function __construct() 2518 | { 2519 | parent::__construct('', '', '', ''); 2520 | } 2521 | 2522 | /** 2523 | * Method to set character set for the database. 2524 | * 2525 | * This overrides wpdb::set_charset(), only to dummy out the MySQL function. 2526 | * 2527 | * @see wpdb::set_charset() 2528 | * 2529 | * @param resource $dbh The resource given by mysql_connect 2530 | * @param string $charset Optional. The character set. Default null. 2531 | * @param string $collate Optional. The collation. Default null. 2532 | */ 2533 | public function set_charset($dbh, $charset = null, $collate = null) 2534 | { 2535 | } 2536 | 2537 | /** 2538 | * Method to dummy out wpdb::set_sql_mode() 2539 | * 2540 | * @see wpdb::set_sql_mode() 2541 | * 2542 | * @param array $modes Optional. A list of SQL modes to set. 2543 | */ 2544 | public function set_sql_mode($modes = []) 2545 | { 2546 | } 2547 | 2548 | /** 2549 | * Method to select the database connection. 2550 | * 2551 | * This overrides wpdb::select(), only to dummy out the MySQL function. 2552 | * 2553 | * @see wpdb::select() 2554 | * 2555 | * @param string $db MySQL database name 2556 | * @param resource|null $dbh Optional link identifier. 2557 | */ 2558 | public function select($db, $dbh = null) 2559 | { 2560 | $this->ready = true; 2561 | } 2562 | 2563 | /** 2564 | * Method to escape characters. 2565 | * 2566 | * This overrides wpdb::_real_escape() to avoid using mysql_real_escape_string(). 2567 | * 2568 | * @see wpdb::_real_escape() 2569 | * 2570 | * @param string $string to escape 2571 | * 2572 | * @return string escaped 2573 | */ 2574 | function _real_escape($string) 2575 | { 2576 | return addslashes($string); 2577 | } 2578 | 2579 | /** 2580 | * Method to dummy out wpdb::esc_like() function. 2581 | * 2582 | * WordPress 4.0.0 introduced esc_like() function that adds backslashes to %, 2583 | * underscore and backslash, which is not interpreted as escape character 2584 | * by SQLite. So we override it and dummy out this function. 2585 | * 2586 | * @param string $text The raw text to be escaped. The input typed by the user should have no 2587 | * extra or deleted slashes. 2588 | * 2589 | * @return string Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare() 2590 | * or real_escape next. 2591 | */ 2592 | public function esc_like($text) 2593 | { 2594 | return $text; 2595 | } 2596 | 2597 | /** 2598 | * Method to put out the error message. 2599 | * 2600 | * This overrides wpdb::print_error(), for we can't use the parent class method. 2601 | * 2602 | * @see wpdb::print_error() 2603 | * 2604 | * @global array $EZSQL_ERROR Stores error information of query and error string 2605 | * 2606 | * @param string $str The error to display 2607 | * 2608 | * @return bool False if the showing of errors is disabled. 2609 | */ 2610 | public function print_error($str = '') 2611 | { 2612 | global $EZSQL_ERROR; 2613 | 2614 | if (! $str) { 2615 | $err = $this->dbh->get_error_message() ? $this->dbh->get_error_message() : ''; 2616 | if (! empty($err)) { 2617 | $str = $err[2]; 2618 | } else { 2619 | $str = ''; 2620 | } 2621 | } 2622 | $EZSQL_ERROR[] = ['query' => $this->last_query, 'error_str' => $str]; 2623 | 2624 | if ($this->suppress_errors) { 2625 | return false; 2626 | } 2627 | 2628 | wp_load_translations_early(); 2629 | 2630 | if ($caller = $this->get_caller()) { 2631 | $error_str = sprintf(__('WordPress database error %1$s for query %2$s made by %3$s'), $str, 2632 | $this->last_query, $caller); 2633 | } else { 2634 | $error_str = sprintf(__('WordPress database error %1$s for query %2$s'), $str, $this->last_query); 2635 | } 2636 | 2637 | error_log($error_str); 2638 | 2639 | if (! $this->show_errors) { 2640 | return false; 2641 | } 2642 | 2643 | if (is_multisite()) { 2644 | $msg = "WordPress database error: [$str]\n{$this->last_query}\n"; 2645 | if (defined('ERRORLOGFILE')) { 2646 | error_log($msg, 3, ERRORLOGFILE); 2647 | } 2648 | if (defined('DIEONDBERROR')) { 2649 | wp_die($msg); 2650 | } 2651 | } else { 2652 | $str = htmlspecialchars($str, ENT_QUOTES); 2653 | $query = htmlspecialchars($this->last_query, ENT_QUOTES); 2654 | 2655 | print "
2656 |

WordPress database error: [$str]
2657 | $query

2658 |
"; 2659 | } 2660 | } 2661 | 2662 | /** 2663 | * Method to flush cached data. 2664 | * 2665 | * This overrides wpdb::flush(). This is not necessarily overridden, because 2666 | * $result will never be resource. 2667 | * 2668 | * @see wpdb::flush 2669 | */ 2670 | public function flush() 2671 | { 2672 | $this->last_result = []; 2673 | $this->col_info = null; 2674 | $this->last_query = null; 2675 | $this->rows_affected = $this->num_rows = 0; 2676 | $this->last_error = ''; 2677 | $this->result = null; 2678 | } 2679 | 2680 | /** 2681 | * Method to do the database connection. 2682 | * 2683 | * This overrides wpdb::db_connect() to avoid using MySQL function. 2684 | * 2685 | * @see wpdb::db_connect() 2686 | * 2687 | * @param bool $allow_bail 2688 | */ 2689 | public function db_connect($allow_bail = true) 2690 | { 2691 | $this->init_charset(); 2692 | $this->dbh = new PDOEngine(); 2693 | $this->ready = true; 2694 | } 2695 | 2696 | /** 2697 | * Method to dummy out wpdb::check_connection() 2698 | * 2699 | * @param bool $allow_bail 2700 | * 2701 | * @return bool 2702 | */ 2703 | public function check_connection($allow_bail = true) 2704 | { 2705 | return true; 2706 | } 2707 | 2708 | /** 2709 | * Method to execute the query. 2710 | * 2711 | * This overrides wpdb::query(). In fact, this method does all the database 2712 | * access jobs. 2713 | * 2714 | * @see wpdb::query() 2715 | * 2716 | * @param string $query Database query 2717 | * 2718 | * @return int|false Number of rows affected/selected or false on error 2719 | */ 2720 | public function query($query) 2721 | { 2722 | if (! $this->ready) { 2723 | return false; 2724 | } 2725 | 2726 | $query = apply_filters('query', $query); 2727 | 2728 | $return_val = 0; 2729 | $this->flush(); 2730 | 2731 | $this->func_call = "\$db->query(\"$query\")"; 2732 | 2733 | $this->last_query = $query; 2734 | 2735 | if (defined('SAVEQUERIES') && SAVEQUERIES) { 2736 | $this->timer_start(); 2737 | } 2738 | 2739 | $this->result = $this->dbh->query($query); 2740 | $this->num_queries++; 2741 | 2742 | if (defined('SAVEQUERIES') && SAVEQUERIES) { 2743 | $this->queries[] = [$query, $this->timer_stop(), $this->get_caller()]; 2744 | } 2745 | 2746 | if ($this->last_error = $this->dbh->get_error_message()) { 2747 | if (defined('WP_INSTALLING') && WP_INSTALLING) { 2748 | //$this->suppress_errors(); 2749 | } else { 2750 | $this->print_error($this->last_error); 2751 | 2752 | return false; 2753 | } 2754 | } 2755 | 2756 | if (preg_match('/^\\s*(create|alter|truncate|drop|optimize)\\s*/i', $query)) { 2757 | //$return_val = $this->result; 2758 | $return_val = $this->dbh->get_return_value(); 2759 | } elseif (preg_match('/^\\s*(insert|delete|update|replace)\s/i', $query)) { 2760 | $this->rows_affected = $this->dbh->get_affected_rows(); 2761 | if (preg_match('/^\s*(insert|replace)\s/i', $query)) { 2762 | $this->insert_id = $this->dbh->get_insert_id(); 2763 | } 2764 | $return_val = $this->rows_affected; 2765 | } else { 2766 | $this->last_result = $this->dbh->get_query_results(); 2767 | $this->num_rows = $this->dbh->get_num_rows(); 2768 | $return_val = $this->num_rows; 2769 | } 2770 | 2771 | return $return_val; 2772 | } 2773 | 2774 | /** 2775 | * Method to set the class variable $col_info. 2776 | * 2777 | * This overrides wpdb::load_col_info(), which uses a mysql function. 2778 | * 2779 | * @see wpdb::load_col_info() 2780 | * @access protected 2781 | */ 2782 | protected function load_col_info() 2783 | { 2784 | if ($this->col_info) { 2785 | return; 2786 | } 2787 | $this->col_info = $this->dbh->get_columns(); 2788 | } 2789 | 2790 | /** 2791 | * Method to return what the database can do. 2792 | * 2793 | * This overrides wpdb::has_cap() to avoid using MySQL functions. 2794 | * SQLite supports subqueries, but not support collation, group_concat and set_charset. 2795 | * 2796 | * @see wpdb::has_cap() 2797 | * 2798 | * @param string $db_cap The feature to check for. Accepts 'collation', 2799 | * 'group_concat', 'subqueries', 'set_charset', 2800 | * 'utf8mb4', or 'utf8mb4_520'. 2801 | * 2802 | * @return int|false Whether the database feature is supported, false otherwise. 2803 | */ 2804 | public function has_cap($db_cap) 2805 | { 2806 | switch (strtolower($db_cap)) { 2807 | case 'collation': 2808 | case 'group_concat': 2809 | case 'set_charset': 2810 | return false; 2811 | case 'subqueries': 2812 | return true; 2813 | default: 2814 | return false; 2815 | } 2816 | } 2817 | 2818 | /** 2819 | * Method to return database version number. 2820 | * 2821 | * This overrides wpdb::db_version() to avoid using MySQL function. 2822 | * It returns mysql version number, but it means nothing for SQLite. 2823 | * So it return the newest mysql version. 2824 | * 2825 | * @see wpdb::db_version() 2826 | */ 2827 | public function db_version() 2828 | { 2829 | // WordPress currently requires this to be 5.5.5 or greater. 2830 | // See https://github.com/WordPress/wordpress-develop/blob/308271cd35c3c71548f6bece6746e67fc4aa6d89/src/wp-includes/version.php#L47 2831 | // See https://github.com/WordPress/sqlite-database-integration/commit/768f8cc6d2a2707f744bba31cb0e5421a68c0f4f 2832 | return '8.0'; 2833 | } 2834 | 2835 | /** 2836 | * Retrieves full database server information. 2837 | * 2838 | * @return string|false Server info on success, false on failure. 2839 | */ 2840 | public function db_server_info() 2841 | { 2842 | return SQLite3::version()['versionString'] . '-SQLite3'; 2843 | } 2844 | } 2845 | 2846 | 2847 | /** 2848 | * This class is for rewriting various query string except CREATE and ALTER. 2849 | * 2850 | */ 2851 | class PDOSQLiteDriver 2852 | { 2853 | 2854 | /** 2855 | * Variable to indicate the query types. 2856 | * 2857 | * @var string $query_type 2858 | */ 2859 | public $query_type = ''; 2860 | /** 2861 | * Variable to store query string. 2862 | * 2863 | * @var string 2864 | */ 2865 | public $_query = ''; 2866 | /** 2867 | * Variable to check if rewriting CALC_FOUND_ROWS is needed. 2868 | * 2869 | * @var boolean 2870 | */ 2871 | private $rewrite_calc_found = false; 2872 | /** 2873 | * Variable to check if rewriting ON DUPLICATE KEY UPDATE is needed. 2874 | * 2875 | * @var boolean 2876 | */ 2877 | private $rewrite_duplicate_key = false; 2878 | /** 2879 | * Variable to check if rewriting index hints is needed. 2880 | * 2881 | * @var boolean 2882 | */ 2883 | private $rewrite_index_hint = false; 2884 | /** 2885 | * Variable to check if rewriting BETWEEN is needed. 2886 | * 2887 | * @var boolean 2888 | */ 2889 | private $rewrite_between = false; 2890 | /** 2891 | * Variable to check how many times rewriting BETWEEN is needed. 2892 | * 2893 | * @var integer 2894 | */ 2895 | private $num_of_rewrite_between = 0; 2896 | /** 2897 | * Variable to check order by field() with column data. 2898 | * 2899 | * @var boolean 2900 | */ 2901 | private $orderby_field = false; 2902 | 2903 | /** 2904 | * Method to rewrite a query string for SQLite to execute. 2905 | * 2906 | * @param strin $query 2907 | * @param string $query_type 2908 | * 2909 | * @return string 2910 | */ 2911 | public function rewrite_query($query, $query_type) 2912 | { 2913 | $this->query_type = $query_type; 2914 | $this->_query = $query; 2915 | $this->parse_query(); 2916 | switch ($this->query_type) { 2917 | case 'truncate': 2918 | $this->handle_truncate_query(); 2919 | break; 2920 | case 'alter': 2921 | $this->handle_alter_query(); 2922 | break; 2923 | case 'create': 2924 | $this->handle_create_query(); 2925 | break; 2926 | case 'describe': 2927 | case 'desc': 2928 | $this->handle_describe_query(); 2929 | break; 2930 | case 'show': 2931 | $this->handle_show_query(); 2932 | break; 2933 | case 'showcolumns': 2934 | $this->handle_show_columns_query(); 2935 | break; 2936 | case 'showindex': 2937 | $this->handle_show_index(); 2938 | break; 2939 | case 'select': 2940 | //$this->strip_backticks(); 2941 | $this->handle_sql_count(); 2942 | $this->rewrite_date_sub(); 2943 | $this->delete_index_hints(); 2944 | $this->rewrite_regexp(); 2945 | //$this->rewrite_boolean(); 2946 | $this->fix_date_quoting(); 2947 | $this->rewrite_between(); 2948 | $this->handle_orderby_field(); 2949 | break; 2950 | case 'insert': 2951 | //$this->safe_strip_backticks(); 2952 | $this->execute_duplicate_key_update(); 2953 | $this->rewrite_insert_ignore(); 2954 | $this->rewrite_regexp(); 2955 | $this->fix_date_quoting(); 2956 | break; 2957 | case 'update': 2958 | //$this->safe_strip_backticks(); 2959 | $this->rewrite_update_ignore(); 2960 | //$this->_rewrite_date_sub(); 2961 | $this->rewrite_limit_usage(); 2962 | $this->rewrite_order_by_usage(); 2963 | $this->rewrite_regexp(); 2964 | $this->rewrite_between(); 2965 | break; 2966 | case 'delete': 2967 | //$this->strip_backticks(); 2968 | $this->rewrite_limit_usage(); 2969 | $this->rewrite_order_by_usage(); 2970 | $this->rewrite_date_sub(); 2971 | $this->rewrite_regexp(); 2972 | $this->delete_workaround(); 2973 | break; 2974 | case 'replace': 2975 | //$this->safe_strip_backticks(); 2976 | $this->rewrite_date_sub(); 2977 | $this->rewrite_regexp(); 2978 | break; 2979 | case 'optimize': 2980 | $this->rewrite_optimize(); 2981 | break; 2982 | case 'pragma': 2983 | break; 2984 | default: 2985 | if (defined(WP_DEBUG) && WP_DEBUG) { 2986 | break; 2987 | } else { 2988 | $this->return_true(); 2989 | break; 2990 | } 2991 | } 2992 | 2993 | return $this->_query; 2994 | } 2995 | 2996 | /** 2997 | * Method to parse query string and determine which operation is needed. 2998 | * 2999 | * Remove backticks and change true/false values into 1/0. And determines 3000 | * if rewriting CALC_FOUND_ROWS or ON DUPLICATE KEY UPDATE etc is needed. 3001 | * 3002 | * @access private 3003 | */ 3004 | private function parse_query() 3005 | { 3006 | $tokens = preg_split("/(\\\'|''|')/s", $this->_query, -1, PREG_SPLIT_DELIM_CAPTURE); 3007 | $literal = false; 3008 | $query_string = ''; 3009 | foreach ($tokens as $token) { 3010 | if ($token == "'") { 3011 | if ($literal) { 3012 | $literal = false; 3013 | } else { 3014 | $literal = true; 3015 | } 3016 | } else { 3017 | if ($literal === false) { 3018 | if (strpos($token, '`') !== false) { 3019 | $token = str_replace('`', '', $token); 3020 | } 3021 | if (preg_match('/\\bTRUE\\b/i', $token)) { 3022 | $token = str_ireplace('TRUE', '1', $token); 3023 | } 3024 | if (preg_match('/\\bFALSE\\b/i', $token)) { 3025 | $token = str_ireplace('FALSE', '0', $token); 3026 | } 3027 | if (stripos($token, 'SQL_CALC_FOUND_ROWS') !== false) { 3028 | $this->rewrite_calc_found = true; 3029 | } 3030 | if (stripos($token, 'ON DUPLICATE KEY UPDATE') !== false) { 3031 | $this->rewrite_duplicate_key = true; 3032 | } 3033 | if (stripos($token, 'USE INDEX') !== false) { 3034 | $this->rewrite_index_hint = true; 3035 | } 3036 | if (stripos($token, 'IGNORE INDEX') !== false) { 3037 | $this->rewrite_index_hint = true; 3038 | } 3039 | if (stripos($token, 'FORCE INDEX') !== false) { 3040 | $this->rewrite_index_hint = true; 3041 | } 3042 | if (stripos($token, 'BETWEEN') !== false) { 3043 | $this->rewrite_between = true; 3044 | $this->num_of_rewrite_between++; 3045 | } 3046 | if (stripos($token, 'ORDER BY FIELD') !== false) { 3047 | $this->orderby_field = true; 3048 | } 3049 | } 3050 | } 3051 | $query_string .= $token; 3052 | } 3053 | $this->_query = $query_string; 3054 | } 3055 | 3056 | /** 3057 | * method to handle SHOW TABLES query. 3058 | * 3059 | * @access private 3060 | */ 3061 | private function handle_show_query() 3062 | { 3063 | $this->_query = str_ireplace(' FULL', '', $this->_query); 3064 | $table_name = ''; 3065 | $pattern = '/^\\s*SHOW\\s*TABLES\\s*.*?(LIKE\\s*(.*))$/im'; 3066 | if (preg_match($pattern, $this->_query, $matches)) { 3067 | $table_name = str_replace(["'", ';'], '', $matches[2]); 3068 | } 3069 | if (! empty($table_name)) { 3070 | $suffix = ' AND name LIKE ' . "'" . $table_name . "'"; 3071 | } else { 3072 | $suffix = ''; 3073 | } 3074 | $this->_query = "SELECT name FROM sqlite_master WHERE type='table'" . $suffix . ' ORDER BY name DESC'; 3075 | } 3076 | 3077 | /** 3078 | * Method to emulate the SQL_CALC_FOUND_ROWS placeholder for MySQL. 3079 | * 3080 | * This is a kind of tricky play. 3081 | * 1. remove SQL_CALC_FOUND_ROWS option, and give it to the pdo engine 3082 | * 2. make another $wpdb instance, and execute the rewritten query 3083 | * 3. give the returned value (integer: number of the rows) to the original instance variable without LIMIT 3084 | * 3085 | * We no longer use SELECT COUNT query, because it returns the inexact values when used with WP_Meta_Query(). 3086 | * 3087 | * This kind of statement is required for WordPress to calculate the paging information. 3088 | * see also WP_Query class in wp-includes/query.php 3089 | */ 3090 | private function handle_sql_count() 3091 | { 3092 | if (! $this->rewrite_calc_found) { 3093 | return; 3094 | } 3095 | global $wpdb; 3096 | // first strip the code. this is the end of rewriting process 3097 | $this->_query = str_ireplace('SQL_CALC_FOUND_ROWS', '', $this->_query); 3098 | // we make the data for next SELECE FOUND_ROWS() statement 3099 | $unlimited_query = preg_replace('/\\bLIMIT\\s*.*/imsx', '', $this->_query); 3100 | //$unlimited_query = preg_replace('/\\bGROUP\\s*BY\\s*.*/imsx', '', $unlimited_query); 3101 | // we no longer use SELECT COUNT query 3102 | //$unlimited_query = $this->_transform_to_count($unlimited_query); 3103 | $_wpdb = new wpsqlitedb(); 3104 | $result = $_wpdb->query($unlimited_query); 3105 | $wpdb->dbh->found_rows_result = $result; 3106 | $_wpdb = null; 3107 | } 3108 | 3109 | /** 3110 | * Method to rewrite INSERT IGNORE to INSERT OR IGNORE. 3111 | * 3112 | * @access private 3113 | */ 3114 | private function rewrite_insert_ignore() 3115 | { 3116 | $this->_query = str_ireplace('INSERT IGNORE', 'INSERT OR IGNORE ', $this->_query); 3117 | } 3118 | 3119 | /** 3120 | * Method to rewrite UPDATE IGNORE to UPDATE OR IGNORE. 3121 | * 3122 | * @access private 3123 | */ 3124 | private function rewrite_update_ignore() 3125 | { 3126 | $this->_query = str_ireplace('UPDATE IGNORE', 'UPDATE OR IGNORE ', $this->_query); 3127 | } 3128 | 3129 | /** 3130 | * Method to rewrite DATE_ADD() function. 3131 | * 3132 | * DATE_ADD has a parameter PHP function can't parse, so we quote the list and 3133 | * pass it to the user defined function. 3134 | * 3135 | * @access private 3136 | */ 3137 | private function rewrite_date_add() 3138 | { 3139 | //(date,interval expression unit) 3140 | $pattern = '/\\s*date_add\\s*\(([^,]*),([^\)]*)\)/imsx'; 3141 | if (preg_match($pattern, $this->_query, $matches)) { 3142 | $expression = "'" . trim($matches[2]) . "'"; 3143 | $this->_query = preg_replace($pattern, " date_add($matches[1], $expression) ", $this->_query); 3144 | } 3145 | } 3146 | 3147 | /** 3148 | * Method to rewrite DATE_SUB() function. 3149 | * 3150 | * DATE_SUB has a parameter PHP function can't parse, so we quote the list and 3151 | * pass it to the user defined function. 3152 | * 3153 | * @access private 3154 | */ 3155 | private function rewrite_date_sub() 3156 | { 3157 | //(date,interval expression unit) 3158 | $pattern = '/\\s*date_sub\\s*\(([^,]*),([^\)]*)\)/imsx'; 3159 | if (preg_match($pattern, $this->_query, $matches)) { 3160 | $expression = "'" . trim($matches[2]) . "'"; 3161 | $this->_query = preg_replace($pattern, " date_sub($matches[1], $expression) ", $this->_query); 3162 | } 3163 | } 3164 | 3165 | /** 3166 | * Method to handle CREATE query. 3167 | * 3168 | * If the query is CREATE query, it will be passed to the query_create.class.php. 3169 | * So this method can't be used. It's here for safety. 3170 | * 3171 | * @access private 3172 | */ 3173 | private function handle_create_query() 3174 | { 3175 | $engine = new CreateQuery(); 3176 | $this->_query = $engine->rewrite_query($this->_query); 3177 | $engine = null; 3178 | } 3179 | 3180 | /** 3181 | * Method to handle ALTER query. 3182 | * 3183 | * If the query is ALTER query, it will be passed ot the query_alter.class.php. 3184 | * So this method can't be used. It is here for safety. 3185 | * 3186 | * @access private 3187 | */ 3188 | private function handle_alter_query() 3189 | { 3190 | $engine = new AlterQuery(); 3191 | $this->_query = $engine->rewrite_query($this->_query, 'alter'); 3192 | $engine = null; 3193 | } 3194 | 3195 | /** 3196 | * Method to handle DESCRIBE or DESC query. 3197 | * 3198 | * DESCRIBE is required for WordPress installation process. DESC is 3199 | * an alias for DESCRIBE, but it is not used in core WordPress. 3200 | * 3201 | * @access private 3202 | */ 3203 | private function handle_describe_query() 3204 | { 3205 | $pattern = '/^\\s*(DESCRIBE|DESC)\\s*(.*)/i'; 3206 | if (preg_match($pattern, $this->_query, $match)) { 3207 | $tablename = preg_replace('/[\';]/', '', $match[2]); 3208 | $this->_query = "PRAGMA table_info($tablename)"; 3209 | } 3210 | } 3211 | 3212 | /** 3213 | * Method to remove LIMIT clause from DELETE or UPDATE query. 3214 | * 3215 | * The author of the original 'PDO for WordPress' says update method of wpdb 3216 | * insists on adding LIMIT. But the newest version of WordPress doesn't do that. 3217 | * Nevertheless some plugins use DELETE with LIMIT, UPDATE with LIMIT. 3218 | * We need to exclude sub query's LIMIT. And if SQLite is compiled with 3219 | * ENABLE_UPDATE_DELETE_LIMIT option, we don't remove it. 3220 | * 3221 | * @access private 3222 | */ 3223 | private function rewrite_limit_usage() 3224 | { 3225 | $_wpdb = new wpsqlitedb(); 3226 | $options = $_wpdb->get_results('PRAGMA compile_options'); 3227 | foreach ($options as $opt) { 3228 | if (isset($opt->compile_option) && stripos($opt->compile_option, 'ENABLE_UPDATE_DELETE_LIMIT') !== false) { 3229 | return; 3230 | } 3231 | } 3232 | if (stripos($this->_query, '(select') === false) { 3233 | $this->_query = preg_replace('/\\s*LIMIT\\s*[0-9]$/i', '', $this->_query); 3234 | } 3235 | } 3236 | 3237 | /** 3238 | * Method to remove ORDER BY clause from DELETE or UPDATE query. 3239 | * 3240 | * SQLite compiled without SQLITE_ENABLE_UPDATE_DELETE_LIMIT option can't 3241 | * execute UPDATE with ORDER BY, DELETE with GROUP BY. 3242 | * We need to exclude sub query's GROUP BY. 3243 | * 3244 | * @access private 3245 | */ 3246 | private function rewrite_order_by_usage() 3247 | { 3248 | $_wpdb = new wpsqlitedb(); 3249 | $options = $_wpdb->get_results('PRAGMA compile_options'); 3250 | foreach ($options as $opt) { 3251 | if (isset($opt->compile_option) && stripos($opt->compile_option, 'ENABLE_UPDATE_DELETE_LIMIT') !== false) { 3252 | return; 3253 | } 3254 | } 3255 | if (stripos($this->_query, '(select') === false) { 3256 | $this->_query = preg_replace('/\\s+ORDER\\s+BY\\s*.*$/i', '', $this->_query); 3257 | } 3258 | } 3259 | 3260 | /** 3261 | * Method to handle TRUNCATE query. 3262 | * 3263 | * @access private 3264 | */ 3265 | private function handle_truncate_query() 3266 | { 3267 | $pattern = '/TRUNCATE TABLE (.*)/im'; 3268 | $this->_query = preg_replace($pattern, 'DELETE FROM $1', $this->_query); 3269 | } 3270 | 3271 | /** 3272 | * Method to handle OPTIMIZE query. 3273 | * 3274 | * Original query has the table names, but they are simply ignored. 3275 | * Table names are meaningless in SQLite. 3276 | * 3277 | * @access private 3278 | */ 3279 | private function rewrite_optimize() 3280 | { 3281 | $this->_query = "VACUUM"; 3282 | } 3283 | 3284 | /** 3285 | * Method to rewrite day. 3286 | * 3287 | * Jusitn Adie says: some wp UI interfaces (notably the post interface) 3288 | * badly composes the day part of the date leading to problems in sqlite 3289 | * sort ordering etc. 3290 | * 3291 | * I don't understand that... 3292 | * 3293 | * @return void 3294 | * @access private 3295 | */ 3296 | private function rewrite_badly_formed_dates() 3297 | { 3298 | $pattern = '/([12]\d{3,}-\d{2}-)(\d )/ims'; 3299 | $this->_query = preg_replace($pattern, '${1}0$2', $this->_query); 3300 | } 3301 | 3302 | /** 3303 | * Method to remove INDEX HINT. 3304 | * 3305 | * @return void 3306 | * @access private 3307 | */ 3308 | private function delete_index_hints() 3309 | { 3310 | $pattern = '/\\s*(use|ignore|force)\\s+index\\s*\(.*?\)/i'; 3311 | $this->_query = preg_replace($pattern, '', $this->_query); 3312 | } 3313 | 3314 | /** 3315 | * Method to fix the date string and quoting. 3316 | * 3317 | * This is required for the calendar widget. 3318 | * 3319 | * WHERE month(fieldname)=08 is converted to month(fieldname)='8' 3320 | * WHERE month(fieldname)='08' is converted to month(fieldname)='8' 3321 | * 3322 | * I use preg_replace_callback instead of 'e' option because of security reason. 3323 | * cf. PHP manual (regular expression) 3324 | * 3325 | * @return void 3326 | * @access private 3327 | */ 3328 | private function fix_date_quoting() 3329 | { 3330 | $pattern = '/(month|year|second|day|minute|hour|dayofmonth)\\s*\((.*?)\)\\s*=\\s*["\']?(\d{1,4})[\'"]?\\s*/im'; 3331 | $this->_query = preg_replace_callback($pattern, [$this, '_fix_date_quoting'], $this->_query); 3332 | } 3333 | 3334 | /** 3335 | * Call back method to rewrite date string. 3336 | * 3337 | * @param string $match 3338 | * 3339 | * @return string 3340 | * @access private 3341 | */ 3342 | private function _fix_date_quoting($match) 3343 | { 3344 | $fixed_val = "{$match[1]}({$match[2]})='" . intval($match[3]) . "' "; 3345 | 3346 | return $fixed_val; 3347 | } 3348 | 3349 | /** 3350 | * Method to rewrite REGEXP() function. 3351 | * 3352 | * This method changes function name to regexpp() and pass it to the user defined 3353 | * function. 3354 | * 3355 | * @access private 3356 | */ 3357 | private function rewrite_regexp() 3358 | { 3359 | $pattern = '/\s([^\s]*)\s*regexp\s*(\'.*?\')/im'; 3360 | $this->_query = preg_replace($pattern, ' regexpp(\1, \2)', $this->_query); 3361 | } 3362 | 3363 | /** 3364 | * Method to handl SHOW COLUMN query. 3365 | * 3366 | * @access private 3367 | */ 3368 | private function handle_show_columns_query() 3369 | { 3370 | $this->_query = str_ireplace(' FULL', '', $this->_query); 3371 | $pattern_like = '/^\\s*SHOW\\s*(COLUMNS|FIELDS)\\s*FROM\\s*(.*)?\\s*LIKE\\s*(.*)?/i'; 3372 | $pattern = '/^\\s*SHOW\\s*(COLUMNS|FIELDS)\\s*FROM\\s*(.*)?/i'; 3373 | if (preg_match($pattern_like, $this->_query, $matches)) { 3374 | $table_name = str_replace("'", "", trim($matches[2])); 3375 | $column_name = str_replace("'", "", trim($matches[3])); 3376 | $query_string = "SELECT sql FROM sqlite_master WHERE tbl_name='$table_name' AND sql LIKE '%$column_name%'"; 3377 | $this->_query = $query_string; 3378 | } elseif (preg_match($pattern, $this->_query, $matches)) { 3379 | $table_name = $matches[2]; 3380 | $query_string = preg_replace($pattern, "PRAGMA table_info($table_name)", $this->_query); 3381 | $this->_query = $query_string; 3382 | } 3383 | } 3384 | 3385 | /** 3386 | * Method to handle SHOW INDEX query. 3387 | * 3388 | * Moved the WHERE clause manipulation to pdoengin.class.php (ver 1.3.1) 3389 | * 3390 | * @access private 3391 | */ 3392 | private function handle_show_index() 3393 | { 3394 | $pattern = '/^\\s*SHOW\\s*(?:INDEX|INDEXES|KEYS)\\s*FROM\\s*(\\w+)?/im'; 3395 | if (preg_match($pattern, $this->_query, $match)) { 3396 | $table_name = preg_replace("/[\';]/", '', $match[1]); 3397 | $table_name = trim($table_name); 3398 | $this->_query = "SELECT * FROM sqlite_master WHERE tbl_name='$table_name'"; 3399 | } 3400 | } 3401 | 3402 | /** 3403 | * Method to handle ON DUPLICATE KEY UPDATE statement. 3404 | * 3405 | * First we use SELECT query and check if INSERT is allowed or not. 3406 | * Rewriting procedure looks like a detour, but I've got no other ways. 3407 | * 3408 | * Added the literal check since the version 1.5.1. 3409 | * 3410 | * @return void 3411 | * @access private 3412 | */ 3413 | private function execute_duplicate_key_update() 3414 | { 3415 | if (! $this->rewrite_duplicate_key) { 3416 | return; 3417 | } 3418 | $unique_keys_for_cond = []; 3419 | $unique_keys_for_check = []; 3420 | $pattern = '/^\\s*INSERT\\s*INTO\\s*(\\w+)?\\s*(.*)\\s*ON\\s*DUPLICATE\\s*KEY\\s*UPDATE\\s*(.*)$/ims'; 3421 | if (preg_match($pattern, $this->_query, $match_0)) { 3422 | $table_name = trim($match_0[1]); 3423 | $insert_data = trim($match_0[2]); 3424 | $update_data = trim($match_0[3]); 3425 | // prepare two unique key data for the table 3426 | // 1. array('col1', 'col2, col3', etc) 2. array('col1', 'col2', 'col3', etc) 3427 | $_wpdb = new wpsqlitedb(); 3428 | $indexes = $_wpdb->get_results("SHOW INDEX FROM {$table_name}"); 3429 | if (! empty($indexes)) { 3430 | foreach ($indexes as $index) { 3431 | if ($index->Non_unique == 0) { 3432 | $unique_keys_for_cond[] = $index->Column_name; 3433 | if (strpos($index->Column_name, ',') !== false) { 3434 | $unique_keys_for_check = array_merge($unique_keys_for_check, 3435 | explode(',', $index->Column_name)); 3436 | } else { 3437 | $unique_keys_for_check[] = $index->Column_name; 3438 | } 3439 | } 3440 | } 3441 | $unique_keys_for_check = array_map('trim', $unique_keys_for_check); 3442 | } else { 3443 | // Without unique key or primary key, UPDATE statement will affect all the rows! 3444 | $query = "INSERT INTO $table_name $insert_data"; 3445 | $this->_query = $query; 3446 | $_wpdb = null; 3447 | 3448 | return; 3449 | } 3450 | // data check 3451 | if (preg_match('/^\((.*)\)\\s*VALUES\\s*\((.*)\)$/ims', $insert_data, $match_1)) { 3452 | $col_array = explode(',', $match_1[1]); 3453 | $ins_data_array = explode(',', $match_1[2]); 3454 | foreach ($col_array as $col) { 3455 | $val = trim(array_shift($ins_data_array)); 3456 | $ins_data_assoc[trim($col)] = $val; 3457 | } 3458 | $condition = ''; 3459 | foreach ($unique_keys_for_cond as $unique_key) { 3460 | if (strpos($unique_key, ',') !== false) { 3461 | $unique_key_array = explode(',', $unique_key); 3462 | $counter = count($unique_key_array); 3463 | for ($i = 0; $i < $counter; ++$i) { 3464 | $col = trim($unique_key_array[$i]); 3465 | if (isset($ins_data_assoc[$col]) && $i == $counter - 1) { 3466 | $condition .= $col . '=' . $ins_data_assoc[$col] . ' OR '; 3467 | } elseif (isset($ins_data_assoc[$col])) { 3468 | $condition .= $col . '=' . $ins_data_assoc[$col] . ' AND '; 3469 | } else { 3470 | continue; 3471 | } 3472 | } 3473 | } else { 3474 | $col = trim($unique_key); 3475 | if (isset($ins_data_assoc[$col])) { 3476 | $condition .= $col . '=' . $ins_data_assoc[$col] . ' OR '; 3477 | } else { 3478 | continue; 3479 | } 3480 | } 3481 | } 3482 | $condition = rtrim($condition, ' OR '); 3483 | $test_query = "SELECT * FROM {$table_name} WHERE {$condition}"; 3484 | $results = $_wpdb->query($test_query); 3485 | $_wpdb = null; 3486 | if ($results == 0) { 3487 | $this->_query = "INSERT INTO $table_name $insert_data"; 3488 | 3489 | return; 3490 | } else { 3491 | $ins_array_assoc = []; 3492 | 3493 | if (preg_match('/^\((.*)\)\\s*VALUES\\s*\((.*)\)$/im', $insert_data, $match_2)) { 3494 | $col_array = explode(',', $match_2[1]); 3495 | $ins_array = explode(',', $match_2[2]); 3496 | $count = count($col_array); 3497 | for ($i = 0; $i < $count; $i++) { 3498 | $col = trim($col_array[$i]); 3499 | $val = trim($ins_array[$i]); 3500 | $ins_array_assoc[$col] = $val; 3501 | } 3502 | } 3503 | $update_data = rtrim($update_data, ';'); 3504 | $tmp_array = explode(',', $update_data); 3505 | foreach ($tmp_array as $pair) { 3506 | list($col, $value) = explode('=', $pair); 3507 | $col = trim($col); 3508 | $value = trim($value); 3509 | $update_array_assoc[$col] = $value; 3510 | } 3511 | foreach ($update_array_assoc as $key => &$value) { 3512 | if (preg_match('/^VALUES\\s*\((.*)\)$/im', $value, $match_3)) { 3513 | $col = trim($match_3[1]); 3514 | $value = $ins_array_assoc[$col]; 3515 | } 3516 | } 3517 | foreach ($ins_array_assoc as $key => $val) { 3518 | if (in_array($key, $unique_keys_for_check)) { 3519 | $where_array[] = $key . '=' . $val; 3520 | } 3521 | } 3522 | $update_strings = ''; 3523 | foreach ($update_array_assoc as $key => $val) { 3524 | if (in_array($key, $unique_keys_for_check)) { 3525 | $where_array[] = $key . '=' . $val; 3526 | } else { 3527 | $update_strings .= $key . '=' . $val . ','; 3528 | } 3529 | } 3530 | $update_strings = rtrim($update_strings, ','); 3531 | $unique_where = array_unique($where_array, SORT_REGULAR); 3532 | $where_string = ' WHERE ' . implode(' AND ', $unique_where); 3533 | $update_query = 'UPDATE ' . $table_name . ' SET ' . $update_strings . $where_string; 3534 | $this->_query = $update_query; 3535 | } 3536 | } 3537 | } 3538 | } 3539 | 3540 | /** 3541 | * Method to rewrite BETWEEN A AND B clause. 3542 | * 3543 | * This clause is the same form as natural language, so we have to check if it is 3544 | * in the data or SQL statement. 3545 | * 3546 | * @access private 3547 | */ 3548 | private function rewrite_between() 3549 | { 3550 | if (! $this->rewrite_between) { 3551 | return; 3552 | } 3553 | $pattern = '/\\s*(CAST\([^\)]+?\)|[^\\s\(]*)?\\s*BETWEEN\\s*([^\\s]*)?\\s*AND\\s*([^\\s\)]*)?\\s*/ims'; 3554 | do { 3555 | if (preg_match($pattern, $this->_query, $match)) { 3556 | $column_name = trim($match[1]); 3557 | $min_value = trim($match[2]); 3558 | $max_value = trim($match[3]); 3559 | $max_value = rtrim($max_value); 3560 | $replacement = " ($column_name >= $min_value AND $column_name <= $max_value)"; 3561 | $this->_query = str_ireplace($match[0], $replacement, $this->_query); 3562 | } 3563 | $this->num_of_rewrite_between--; 3564 | } while ($this->num_of_rewrite_between > 0); 3565 | } 3566 | 3567 | /** 3568 | * Method to handle ORDER BY FIELD() clause. 3569 | * 3570 | * When FIELD() function has column name to compare, we can't rewrite it with 3571 | * use defined functions. When this function detect column name in the argument, 3572 | * it creates another instance, does the query withuot ORDER BY clause and gives 3573 | * the result array sorted to the main instance. 3574 | * 3575 | * If FIELD() function doesn't have column name, it will use the user defined 3576 | * function. usort() function closure function to compare the items. 3577 | * 3578 | * @access private 3579 | */ 3580 | private function handle_orderby_field() 3581 | { 3582 | if (! $this->orderby_field) { 3583 | return; 3584 | } 3585 | global $wpdb; 3586 | $pattern = '/\\s+ORDER\\s+BY\\s+FIELD\\s*\(\\s*([^\)]+?)\\s*\)/i'; 3587 | if (preg_match($pattern, $this->_query, $match)) { 3588 | $params = explode(',', $match[1]); 3589 | $params = array_map('trim', $params); 3590 | $tbl_col = array_shift($params); 3591 | $flipped = array_flip($params); 3592 | $tbl_name = substr($tbl_col, 0, strpos($tbl_col, '.')); 3593 | $tbl_name = str_replace($wpdb->prefix, '', $tbl_name); 3594 | 3595 | if ($tbl_name && in_array($tbl_name, $wpdb->tables)) { 3596 | $query = str_replace($match[0], '', $this->_query); 3597 | $_wpdb = new wpsqlitedb(); 3598 | $results = $_wpdb->get_results($query); 3599 | $_wpdb = null; 3600 | usort($results, function ($a, $b) use ($flipped) { 3601 | return $flipped[$a->ID] - $flipped[$b->ID]; 3602 | }); 3603 | } 3604 | $wpdb->dbh->pre_ordered_results = $results; 3605 | } 3606 | } 3607 | 3608 | /** 3609 | * Method to avoid DELETE with JOIN statement. 3610 | * 3611 | * wp-admin/includes/upgrade.php contains 'DELETE ... JOIN' statement. 3612 | * This query can't be replaced with regular expression or udf, so we 3613 | * replace all the statement with another. But this query was used in 3614 | * the very old version of WordPress when it was upgraded. So we won't 3615 | * have no chance that this method should be used. 3616 | * 3617 | * @access private 3618 | */ 3619 | private function delete_workaround() 3620 | { 3621 | global $wpdb; 3622 | $pattern = "DELETE o1 FROM $wpdb->options AS o1 JOIN $wpdb->options AS o2"; 3623 | $pattern2 = "DELETE a, b FROM $wpdb->sitemeta AS a, $wpdb->sitemeta AS b"; 3624 | $rewritten = "DELETE FROM $wpdb->options WHERE option_id IN (SELECT MIN(option_id) FROM $wpdb->options GROUP BY option_name HAVING COUNT(*) > 1)"; 3625 | if (stripos($this->_query, $pattern) !== false) { 3626 | $this->_query = $rewritten; 3627 | } elseif (stripos($this->_query, $pattern2) !== false) { 3628 | $time = time(); 3629 | $prep_query = "SELECT a.meta_id AS aid, b.meta_id AS bid FROM $wpdb->sitemeta AS a INNER JOIN $wpdb->sitemeta AS b ON a.meta_key='_site_transient_timeout_'||substr(b.meta_key, 17) WHERE b.meta_key='_site_transient_'||substr(a.meta_key, 25) AND a.meta_value < $time"; 3630 | $_wpdb = new wpsqlitedb(); 3631 | $ids = $_wpdb->get_results($prep_query); 3632 | foreach ($ids as $id) { 3633 | $ids_to_delete[] = $id->aid; 3634 | $ids_to_delete[] = $id->bid; 3635 | } 3636 | $rewritten = "DELETE FROM $wpdb->sitemeta WHERE meta_id IN (" . implode(',', $ids_to_delete) . ")"; 3637 | $this->_query = $rewritten; 3638 | } 3639 | } 3640 | 3641 | /** 3642 | * Method to suppress errors. 3643 | * 3644 | * When the query string is the one that this class can't manipulate, 3645 | * the query string is replaced with the one that always returns true 3646 | * and does nothing. 3647 | * 3648 | * @access private 3649 | */ 3650 | private function return_true() 3651 | { 3652 | $this->_query = 'SELECT 1=1'; 3653 | } 3654 | } 3655 | 3656 | /** 3657 | * This class provides a function to rewrite CREATE query. 3658 | * 3659 | */ 3660 | class CreateQuery 3661 | { 3662 | 3663 | /** 3664 | * The query string to be rewritten in this class. 3665 | * 3666 | * @var string 3667 | * @access private 3668 | */ 3669 | private $_query = ''; 3670 | /** 3671 | * The array to contain CREATE INDEX queries. 3672 | * 3673 | * @var array of strings 3674 | * @access private 3675 | */ 3676 | private $index_queries = []; 3677 | /** 3678 | * The array to contain error messages. 3679 | * 3680 | * @var array of string 3681 | * @access private 3682 | */ 3683 | private $_errors = []; 3684 | /** 3685 | * Variable to have the table name to be executed. 3686 | * 3687 | * @var string 3688 | * @access private 3689 | */ 3690 | private $table_name = ''; 3691 | /** 3692 | * Variable to check if the query has the primary key. 3693 | * 3694 | * @var boolean 3695 | * @access private 3696 | */ 3697 | private $has_primary_key = false; 3698 | 3699 | /** 3700 | * Function to rewrite query. 3701 | * 3702 | * @param string $query the query being processed 3703 | * 3704 | * @return string|array the processed (rewritten) query 3705 | */ 3706 | public function rewrite_query($query) 3707 | { 3708 | $this->_query = $query; 3709 | $this->_errors [] = ''; 3710 | if (preg_match('/^CREATE\\s*(UNIQUE|FULLTEXT|)\\s*INDEX/ims', $this->_query, $match)) { 3711 | // we manipulate CREATE INDEX query in PDOEngine.class.php 3712 | // FULLTEXT index creation is simply ignored. 3713 | if (isset($match[1]) && stripos($match[1], 'fulltext') !== false) { 3714 | return 'SELECT 1=1'; 3715 | } else { 3716 | return $this->_query; 3717 | } 3718 | } elseif (preg_match('/^CREATE\\s*(TEMP|TEMPORARY|)\\s*TRIGGER\\s*/im', $this->_query)) { 3719 | // if WordPress comes to use foreign key constraint, trigger will be needed. 3720 | // we don't use it for now. 3721 | return $this->_query; 3722 | } 3723 | $this->strip_backticks(); 3724 | $this->quote_illegal_field(); 3725 | $this->get_table_name(); 3726 | $this->rewrite_comments(); 3727 | $this->rewrite_field_types(); 3728 | $this->rewrite_character_set(); 3729 | $this->rewrite_engine_info(); 3730 | $this->rewrite_unsigned(); 3731 | $this->rewrite_autoincrement(); 3732 | $this->rewrite_primary_key(); 3733 | $this->rewrite_foreign_key(); 3734 | $this->rewrite_unique_key(); 3735 | $this->rewrite_enum(); 3736 | $this->rewrite_set(); 3737 | $this->rewrite_key(); 3738 | $this->add_if_not_exists(); 3739 | 3740 | return $this->post_process(); 3741 | } 3742 | 3743 | /** 3744 | * Method to get table name from the query string. 3745 | * 3746 | * 'IF NOT EXISTS' clause is removed for the easy regular expression usage. 3747 | * It will be added at the end of the process. 3748 | * 3749 | * @access private 3750 | */ 3751 | private function get_table_name() 3752 | { 3753 | // $pattern = '/^\\s*CREATE\\s*(TEMP|TEMPORARY)?\\s*TABLE\\s*(IF NOT EXISTS)?\\s*([^\(]*)/imsx'; 3754 | $pattern = '/^\\s*CREATE\\s*(?:TEMP|TEMPORARY)?\\s*TABLE\\s*(?:IF\\s*NOT\\s*EXISTS)?\\s*([^\(]*)/imsx'; 3755 | if (preg_match($pattern, $this->_query, $matches)) { 3756 | $this->table_name = trim($matches[1]); 3757 | } 3758 | } 3759 | 3760 | /** 3761 | * Method to change the MySQL field types to SQLite compatible types. 3762 | * 3763 | * If column name is the same as the key value, e.g. "date" or "timestamp", 3764 | * and the column is on the top of the line, we add a single quote and avoid 3765 | * to be replaced. But this doesn't work if that column name is in the middle 3766 | * of the line. 3767 | * Order of the key value is important. Don't change it. 3768 | * 3769 | * @access private 3770 | */ 3771 | private function rewrite_field_types() 3772 | { 3773 | $array_types = [ 3774 | 'bit' => 'integer', 3775 | 'bool' => 'integer', 3776 | 'boolean' => 'integer', 3777 | 'tinyint' => 'integer', 3778 | 'smallint' => 'integer', 3779 | 'mediumint' => 'integer', 3780 | 'int' => 'integer', 3781 | 'integer' => 'integer', 3782 | 'bigint' => 'integer', 3783 | 'float' => 'real', 3784 | 'double' => 'real', 3785 | 'decimal' => 'real', 3786 | 'dec' => 'real', 3787 | 'numeric' => 'real', 3788 | 'fixed' => 'real', 3789 | 'date' => 'text', 3790 | 'datetime' => 'text', 3791 | 'timestamp' => 'text', 3792 | 'time' => 'text', 3793 | 'year' => 'text', 3794 | 'char' => 'text', 3795 | 'varchar' => 'text', 3796 | 'binary' => 'integer', 3797 | 'varbinary' => 'blob', 3798 | 'tinyblob' => 'blob', 3799 | 'tinytext' => 'text', 3800 | 'blob' => 'blob', 3801 | 'text' => 'text', 3802 | 'mediumblob' => 'blob', 3803 | 'mediumtext' => 'text', 3804 | 'longblob' => 'blob', 3805 | 'longtext' => 'text', 3806 | ]; 3807 | foreach ($array_types as $o => $r) { 3808 | if (preg_match("/^\\s*(?_query, $match)) { 3809 | $ptrn = "/$match[1]/im"; 3810 | $replaced = str_ireplace($ptrn, '#placeholder#', $this->_query); 3811 | $replaced = str_ireplace($o, "'{$o}'", $replaced); 3812 | $this->_query = str_replace('#placeholder#', $ptrn, $replaced); 3813 | } 3814 | $pattern = "/\\b(?_query)) { 3816 | ; 3817 | } else { 3818 | $this->_query = preg_replace($pattern, " $r ", $this->_query); 3819 | } 3820 | } 3821 | } 3822 | 3823 | /** 3824 | * Method for stripping the comments from the SQL statement. 3825 | * 3826 | * @access private 3827 | */ 3828 | private function rewrite_comments() 3829 | { 3830 | $this->_query = preg_replace("/# --------------------------------------------------------/", 3831 | "-- ******************************************************", $this->_query); 3832 | $this->_query = preg_replace("/#/", "--", $this->_query); 3833 | } 3834 | 3835 | /** 3836 | * Method for stripping the engine and other stuffs. 3837 | * 3838 | * TYPE, ENGINE and AUTO_INCREMENT are removed here. 3839 | * @access private 3840 | */ 3841 | private function rewrite_engine_info() 3842 | { 3843 | $this->_query = preg_replace("/\\s*(TYPE|ENGINE)\\s*=\\s*.*(?_query); 3844 | $this->_query = preg_replace("/ AUTO_INCREMENT\\s*=\\s*[0-9]*/ims", '', $this->_query); 3845 | } 3846 | 3847 | /** 3848 | * Method for stripping unsigned. 3849 | * 3850 | * SQLite doesn't have unsigned int data type. So UNSIGNED INT(EGER) is converted 3851 | * to INTEGER here. 3852 | * 3853 | * @access private 3854 | */ 3855 | private function rewrite_unsigned() 3856 | { 3857 | $this->_query = preg_replace('/\\bunsigned\\b/ims', ' ', $this->_query); 3858 | } 3859 | 3860 | /** 3861 | * Method for rewriting primary key auto_increment. 3862 | * 3863 | * If the field type is 'INTEGER PRIMARY KEY', it is automatically autoincremented 3864 | * by SQLite. There's a little difference between PRIMARY KEY and AUTOINCREMENT, so 3865 | * we may well convert to PRIMARY KEY only. 3866 | * 3867 | * @access private 3868 | */ 3869 | private function rewrite_autoincrement() 3870 | { 3871 | $this->_query = preg_replace('/\\bauto_increment\\s*primary\\s*key\\s*(,)?/ims', 3872 | ' PRIMARY KEY AUTOINCREMENT \\1', $this->_query, -1, $count); 3873 | $this->_query = preg_replace('/\\bauto_increment\\b\\s*(,)?/ims', ' PRIMARY KEY AUTOINCREMENT $1', 3874 | $this->_query, -1, $count); 3875 | if ($count > 0) { 3876 | $this->has_primary_key = true; 3877 | } 3878 | } 3879 | 3880 | /** 3881 | * Method for rewriting primary key. 3882 | * 3883 | * @access private 3884 | */ 3885 | private function rewrite_primary_key() 3886 | { 3887 | if ($this->has_primary_key) { 3888 | $this->_query = preg_replace('/\\s*primary key\\s*.*?\([^\)]*\)\\s*(,|)/i', ' ', $this->_query); 3889 | } else { 3890 | // If primary key has an index name, we remove that name. 3891 | $this->_query = preg_replace('/\\bprimary\\s*key\\s*.*?\\s*(\(.*?\))/im', 'PRIMARY KEY \\1', $this->_query); 3892 | } 3893 | } 3894 | 3895 | /** 3896 | * Method for rewriting foreign key. 3897 | * 3898 | * @access private 3899 | */ 3900 | private function rewrite_foreign_key() 3901 | { 3902 | $pattern = '/\\s*foreign\\s*key\\s*(|.*?)\([^\)]+?\)\\s*references\\s*.*/i'; 3903 | if (preg_match_all($pattern, $this->_query, $match)) { 3904 | if (isset($match[1])) { 3905 | $this->_query = str_ireplace($match[1], '', $this->_query); 3906 | } 3907 | } 3908 | } 3909 | 3910 | /** 3911 | * Method for rewriting unique key. 3912 | * 3913 | * @access private 3914 | */ 3915 | private function rewrite_unique_key() 3916 | { 3917 | $this->_query = preg_replace_callback('/\\bunique key\\b([^\(]*)(\(.*\))/im', [$this, '_rewrite_unique_key'], 3918 | $this->_query); 3919 | } 3920 | 3921 | /** 3922 | * Callback method for rewrite_unique_key. 3923 | * 3924 | * @param array $matches an array of matches from the Regex 3925 | * 3926 | * @access private 3927 | * @return string 3928 | */ 3929 | private function _rewrite_unique_key($matches) 3930 | { 3931 | $index_name = trim($matches[1]); 3932 | $col_name = trim($matches[2]); 3933 | $tbl_name = $this->table_name; 3934 | if (preg_match('/\(\\d+?\)/', $col_name)) { 3935 | $col_name = preg_replace('/\(\\d+?\)/', '', $col_name); 3936 | } 3937 | $_wpdb = new wpsqlitedb(); 3938 | $results = $_wpdb->get_results("SELECT name FROM sqlite_master WHERE type='index'"); 3939 | $_wpdb = null; 3940 | if ($results) { 3941 | foreach ($results as $result) { 3942 | if ($result->name == $index_name) { 3943 | $r = rand(0, 50); 3944 | $index_name = $index_name . "_$r"; 3945 | break; 3946 | } 3947 | } 3948 | } 3949 | $index_name = str_replace(' ', '', $index_name); 3950 | $this->index_queries[] = "CREATE UNIQUE INDEX $index_name ON " . $tbl_name . $col_name; 3951 | 3952 | return ''; 3953 | } 3954 | 3955 | /** 3956 | * Method for handling ENUM fields. 3957 | * 3958 | * SQLite doesn't support enum, so we change it to check constraint. 3959 | * 3960 | * @access private 3961 | */ 3962 | private function rewrite_enum() 3963 | { 3964 | $pattern = '/(,|\))([^,]*)enum\((.*?)\)([^,\)]*)/ims'; 3965 | $this->_query = preg_replace_callback($pattern, [$this, '_rewrite_enum'], $this->_query); 3966 | } 3967 | 3968 | /** 3969 | * Call back method for rewrite_enum() and rewrite_set(). 3970 | * 3971 | * @access private 3972 | * 3973 | * @param $matches 3974 | * 3975 | * @return string 3976 | */ 3977 | private function _rewrite_enum($matches) 3978 | { 3979 | $output = $matches[1] . ' ' . $matches[2] . ' TEXT ' . $matches[4] . ' CHECK (' . $matches[2] . ' IN (' . $matches[3] . ')) '; 3980 | 3981 | return $output; 3982 | } 3983 | 3984 | /** 3985 | * Method for rewriting usage of set. 3986 | * 3987 | * It is similar but not identical to enum. SQLite does not support either. 3988 | * 3989 | * @access private 3990 | */ 3991 | private function rewrite_set() 3992 | { 3993 | $pattern = '/\b(\w)*\bset\\s*\((.*?)\)\\s*(.*?)(,)*/ims'; 3994 | $this->_query = preg_replace_callback($pattern, [$this, '_rewrite_enum'], $this->_query); 3995 | } 3996 | 3997 | /** 3998 | * Method for rewriting usage of key to create an index. 3999 | * 4000 | * SQLite cannot create non-unique indices as part of the create query, 4001 | * so we need to create an index by hand and append it to the create query. 4002 | * 4003 | * @access private 4004 | */ 4005 | private function rewrite_key() 4006 | { 4007 | $this->_query = preg_replace_callback('/,\\s*(KEY|INDEX)\\s*(\\w+)?\\s*(\(.+\))/im', [$this, '_rewrite_key'], 4008 | $this->_query); 4009 | } 4010 | 4011 | /** 4012 | * Callback method for rewrite_key. 4013 | * 4014 | * @param array $matches an array of matches from the Regex 4015 | * 4016 | * @access private 4017 | * @return string 4018 | */ 4019 | private function _rewrite_key($matches) 4020 | { 4021 | $index_name = trim($matches[2]); 4022 | $col_name = trim($matches[3]); 4023 | if (preg_match('/\([0-9]+?\)/', $col_name, $match)) { 4024 | $col_name = preg_replace_callback('/\([0-9]+?\)/', [$this, '_remove_length'], $col_name); 4025 | } 4026 | $tbl_name = $this->table_name; 4027 | $_wpdb = new wpsqlitedb(); 4028 | $results = $_wpdb->get_results("SELECT name FROM sqlite_master WHERE type='index'"); 4029 | $_wpdb = null; 4030 | if ($results) { 4031 | foreach ($results as $result) { 4032 | if ($result->name == $index_name) { 4033 | $r = rand(0, 50); 4034 | $index_name = $index_name . "_$r"; 4035 | break; 4036 | } 4037 | } 4038 | } 4039 | $this->index_queries[] = 'CREATE INDEX ' . $index_name . ' ON ' . $tbl_name . $col_name; 4040 | 4041 | return ''; 4042 | } 4043 | 4044 | /** 4045 | * Call back method to remove unnecessary string. 4046 | * 4047 | * This method is deprecated. 4048 | * 4049 | * @param string $match 4050 | * 4051 | * @return string whose length is zero 4052 | * @access private 4053 | */ 4054 | private function _remove_length($match) 4055 | { 4056 | return ''; 4057 | } 4058 | 4059 | /** 4060 | * Method to assemble the main query and index queries into an array. 4061 | * 4062 | * It return the array of the queries to be executed separately. 4063 | * 4064 | * @return array 4065 | * @access private 4066 | */ 4067 | private function post_process() 4068 | { 4069 | $mainquery = $this->_query; 4070 | do { 4071 | $count = 0; 4072 | $mainquery = preg_replace('/,\\s*\)/imsx', ')', $mainquery, -1, $count); 4073 | } while ($count > 0); 4074 | do { 4075 | $count = 0; 4076 | $mainquery = preg_replace('/\(\\s*?,/imsx', '(', $mainquery, -1, $count); 4077 | } while ($count > 0); 4078 | $return_val[] = $mainquery; 4079 | $return_val = array_merge($return_val, $this->index_queries); 4080 | 4081 | return $return_val; 4082 | } 4083 | 4084 | /** 4085 | * Method to add IF NOT EXISTS to query string. 4086 | * 4087 | * This adds IF NOT EXISTS to every query string, which prevent the exception 4088 | * from being thrown. 4089 | * 4090 | * @access private 4091 | */ 4092 | private function add_if_not_exists() 4093 | { 4094 | $pattern_table = '/^\\s*CREATE\\s*(TEMP|TEMPORARY)?\\s*TABLE\\s*(IF NOT EXISTS)?\\s*/ims'; 4095 | $this->_query = preg_replace($pattern_table, 'CREATE $1 TABLE IF NOT EXISTS ', $this->_query); 4096 | $pattern_index = '/^\\s*CREATE\\s*(UNIQUE)?\\s*INDEX\\s*(IF NOT EXISTS)?\\s*/ims'; 4097 | for ($i = 0; $i < count($this->index_queries); $i++) { 4098 | $this->index_queries[$i] = preg_replace($pattern_index, 'CREATE $1 INDEX IF NOT EXISTS ', 4099 | $this->index_queries[$i]); 4100 | } 4101 | } 4102 | 4103 | /** 4104 | * Method to strip back quotes. 4105 | * 4106 | * @access private 4107 | */ 4108 | private function strip_backticks() 4109 | { 4110 | $this->_query = str_replace('`', '', $this->_query); 4111 | foreach ($this->index_queries as &$query) { 4112 | $query = str_replace('`', '', $query); 4113 | } 4114 | } 4115 | 4116 | /** 4117 | * Method to remove the character set information from within mysql queries. 4118 | * 4119 | * This removes DEFAULT CHAR(ACTER) SET and COLLATE, which is meaningless for 4120 | * SQLite. 4121 | * 4122 | * @access private 4123 | */ 4124 | private function rewrite_character_set() 4125 | { 4126 | $pattern_charset = '/\\b(default\\s*character\\s*set|default\\s*charset|character\\s*set)\\s*(?_query = preg_replace($patterns, '', $this->_query); 4131 | } 4132 | 4133 | /** 4134 | * Method to quote illegal field name for SQLite 4135 | * 4136 | * @access private 4137 | */ 4138 | private function quote_illegal_field() 4139 | { 4140 | $this->_query = preg_replace("/^\\s*(?_query); 4141 | } 4142 | } 4143 | 4144 | class AlterQuery 4145 | { 4146 | /** 4147 | * Variable to store the rewritten query string. 4148 | * @var string 4149 | */ 4150 | public $_query = null; 4151 | 4152 | /** 4153 | * Function to split the query string to the tokens and call appropriate functions. 4154 | * 4155 | * @param $query 4156 | * @param string $query_type 4157 | * 4158 | * @return boolean | string 4159 | */ 4160 | public function rewrite_query($query, $query_type) 4161 | { 4162 | if (stripos($query, $query_type) === false) { 4163 | return false; 4164 | } 4165 | $query = str_replace('`', '', $query); 4166 | if (preg_match('/^\\s*(ALTER\\s*TABLE)\\s*(\\w+)?\\s*/ims', $query, $match)) { 4167 | $tmp_query = []; 4168 | $re_command = ''; 4169 | $command = str_ireplace($match[0], '', $query); 4170 | $tmp_tokens['query_type'] = trim($match[1]); 4171 | $tmp_tokens['table_name'] = trim($match[2]); 4172 | $command_array = explode(',', $command); 4173 | 4174 | $single_command = array_shift($command_array); 4175 | if (! empty($command_array)) { 4176 | $re_command = "ALTER TABLE {$tmp_tokens['table_name']} "; 4177 | $re_command .= implode(',', $command_array); 4178 | } 4179 | $command_tokens = $this->command_tokenizer($single_command); 4180 | if (! empty($command_tokens)) { 4181 | $tokens = array_merge($tmp_tokens, $command_tokens); 4182 | } else { 4183 | $this->_query = 'SELECT 1=1'; 4184 | 4185 | return $this->_query; 4186 | } 4187 | $command_name = strtolower($tokens['command']); 4188 | switch ($command_name) { 4189 | case 'add column': 4190 | case 'rename to': 4191 | case 'add index': 4192 | case 'drop index': 4193 | $tmp_query = $this->handle_single_command($tokens); 4194 | break; 4195 | case 'add primary key': 4196 | $tmp_query = $this->handle_add_primary_key($tokens); 4197 | break; 4198 | case 'drop primary key': 4199 | $tmp_query = $this->handle_drop_primary_key($tokens); 4200 | break; 4201 | case 'modify column': 4202 | $tmp_query = $this->handle_modify_command($tokens); 4203 | break; 4204 | case 'change column': 4205 | $tmp_query = $this->handle_change_command($tokens); 4206 | break; 4207 | case 'alter column': 4208 | $tmp_query = $this->handle_alter_command($tokens); 4209 | break; 4210 | default: 4211 | break; 4212 | } 4213 | if (! is_array($tmp_query)) { 4214 | $this->_query[] = $tmp_query; 4215 | } else { 4216 | $this->_query = $tmp_query; 4217 | } 4218 | if ($re_command != '') { 4219 | $this->_query = array_merge($this->_query, ['recursion' => $re_command]); 4220 | } 4221 | } else { 4222 | $this->_query = 'SELECT 1=1'; 4223 | } 4224 | 4225 | return $this->_query; 4226 | } 4227 | 4228 | /** 4229 | * Function to analyze ALTER TABLE command and sets the data to an array. 4230 | * 4231 | * @param string $command 4232 | * 4233 | * @return boolean|array 4234 | * @access private 4235 | */ 4236 | private function command_tokenizer($command) 4237 | { 4238 | $tokens = []; 4239 | if (preg_match('/^(ADD|DROP|RENAME|MODIFY|CHANGE|ALTER)\\s*(\\w+)?\\s*(\\w+(\(.+\)|))?\\s*/ims', $command, 4240 | $match)) { 4241 | $the_rest = str_ireplace($match[0], '', $command); 4242 | $match_1 = trim($match[1]); 4243 | $match_2 = trim($match[2]); 4244 | $match_3 = isset($match[3]) ? trim($match[3]) : ''; 4245 | switch (strtolower($match_1)) { 4246 | case 'add': 4247 | if (in_array(strtolower($match_2), ['fulltext', 'constraint', 'foreign'])) { 4248 | break; 4249 | } elseif (stripos('column', $match_2) !== false) { 4250 | $tokens['command'] = $match_1 . ' ' . $match_2; 4251 | $tokens['column_name'] = $match_3; 4252 | $tokens['column_def'] = trim($the_rest); 4253 | } elseif (stripos('primary', $match_2) !== false) { 4254 | $tokens['command'] = $match_1 . ' ' . $match_2 . ' ' . $match_3; 4255 | $tokens['column_name'] = $the_rest; 4256 | } elseif (stripos('unique', $match_2) !== false) { 4257 | list($index_name, $col_name) = preg_split('/[\(\)]/s', trim($the_rest), -1, 4258 | PREG_SPLIT_DELIM_CAPTURE); 4259 | $tokens['unique'] = true; 4260 | $tokens['command'] = $match_1 . ' ' . $match_3; 4261 | $tokens['index_name'] = trim($index_name); 4262 | $tokens['column_name'] = '(' . trim($col_name) . ')'; 4263 | } elseif (in_array(strtolower($match_2), ['index', 'key'])) { 4264 | $tokens['command'] = $match_1 . ' ' . $match_2; 4265 | if ($match_3 == '') { 4266 | $tokens['index_name'] = str_replace(['(', ')'], '', $the_rest); 4267 | } else { 4268 | $tokens['index_name'] = $match_3; 4269 | } 4270 | $tokens['column_name'] = trim($the_rest); 4271 | } else { 4272 | $tokens['command'] = $match_1 . ' COLUMN'; 4273 | $tokens['column_name'] = $match_2; 4274 | $tokens['column_def'] = $match_3 . ' ' . $the_rest; 4275 | } 4276 | break; 4277 | case 'drop': 4278 | if (stripos('column', $match_2) !== false) { 4279 | $tokens['command'] = $match_1 . ' ' . $match_2; 4280 | $tokens['column_name'] = trim($match_3); 4281 | } elseif (stripos('primary', $match_2) !== false) { 4282 | $tokens['command'] = $match_1 . ' ' . $match_2 . ' ' . $match_3; 4283 | } elseif (in_array(strtolower($match_2), ['index', 'key'])) { 4284 | $tokens['command'] = $match_1 . ' ' . $match_2; 4285 | $tokens['index_name'] = $match_3; 4286 | } elseif (stripos('primary', $match_2) !== false) { 4287 | $tokens['command'] = $match_1 . ' ' . $match_2 . ' ' . $match_3; 4288 | } else { 4289 | $tokens['command'] = $match_1 . ' COLUMN'; 4290 | $tokens['column_name'] = $match_2; 4291 | } 4292 | break; 4293 | case 'rename': 4294 | if (stripos('to', $match_2) !== false) { 4295 | $tokens['command'] = $match_1 . ' ' . $match_2; 4296 | $tokens['column_name'] = $match_3; 4297 | } else { 4298 | $tokens['command'] = $match_1 . ' TO'; 4299 | $tokens['column_name'] = $match_2; 4300 | } 4301 | break; 4302 | case 'modify': 4303 | if (stripos('column', $match_2) !== false) { 4304 | $tokens['command'] = $match_1 . ' ' . $match_2; 4305 | $tokens['column_name'] = $match_3; 4306 | $tokens['column_def'] = trim($the_rest); 4307 | } else { 4308 | $tokens['command'] = $match_1 . ' COLUMN'; 4309 | $tokens['column_name'] = $match_2; 4310 | $tokens['column_def'] = $match_3 . ' ' . trim($the_rest); 4311 | } 4312 | break; 4313 | case 'change': 4314 | $the_rest = trim($the_rest); 4315 | if (stripos('column', $match_2) !== false) { 4316 | $tokens['command'] = $match_1 . ' ' . $match_2; 4317 | $tokens['old_column'] = $match_3; 4318 | list($new_col) = explode(' ', $the_rest); 4319 | $tmp_col = preg_replace('/\(.+?\)/im', '', $new_col); 4320 | if (array_key_exists(strtolower($tmp_col), $this->array_types)) { 4321 | $tokens['column_def'] = $the_rest; 4322 | } else { 4323 | $tokens['new_column'] = $new_col; 4324 | $col_def = str_replace($new_col, '', $the_rest); 4325 | $tokens['column_def'] = trim($col_def); 4326 | } 4327 | } else { 4328 | $tokens['command'] = $match_1 . ' column'; 4329 | $tokens['old_column'] = $match_2; 4330 | $tmp_col = preg_replace('/\(.+?\)/im', '', $match_3); 4331 | if (array_key_exists(strtolower($tmp_col), $this->array_types)) { 4332 | $tokens['column_def'] = $match_3 . ' ' . $the_rest; 4333 | } else { 4334 | $tokens['new_column'] = $match_3; 4335 | $tokens['column_def'] = $the_rest; 4336 | } 4337 | } 4338 | break; 4339 | case 'alter': 4340 | if (stripos('column', $match_2) !== false) { 4341 | $tokens['command'] = $match_1 . ' ' . $match_2; 4342 | $tokens['column_name'] = $match_3; 4343 | list($set_or_drop) = explode(' ', $the_rest); 4344 | if (stripos('set', $set_or_drop) !== false) { 4345 | $tokens['default_command'] = 'SET DEFAULT'; 4346 | $default_value = str_ireplace('set default', '', $the_rest); 4347 | $tokens['default_value'] = trim($default_value); 4348 | } else { 4349 | $tokens['default_command'] = 'DROP DEFAULT'; 4350 | } 4351 | } else { 4352 | $tokens['command'] = $match_1 . ' COLUMN'; 4353 | $tokens['column_name'] = $match_2; 4354 | if (stripos('set', $match_3) !== false) { 4355 | $tokens['default_command'] = 'SET DEFAULT'; 4356 | $default_value = str_ireplace('default', '', $the_rest); 4357 | $tokens['default_value'] = trim($default_value); 4358 | } else { 4359 | $tokens['default_command'] = 'DROP DEFAULT'; 4360 | } 4361 | } 4362 | break; 4363 | default: 4364 | break; 4365 | } 4366 | 4367 | return $tokens; 4368 | } 4369 | } 4370 | 4371 | /** 4372 | * Function to handle single command. 4373 | * 4374 | * @access private 4375 | * 4376 | * @param array of string $queries 4377 | * 4378 | * @return string 4379 | */ 4380 | private function handle_single_command($queries) 4381 | { 4382 | $tokenized_query = $queries; 4383 | if (stripos($tokenized_query['command'], 'add column') !== false) { 4384 | $column_def = $this->convert_field_types($tokenized_query['column_name'], $tokenized_query['column_def']); 4385 | $query = "ALTER TABLE {$tokenized_query['table_name']} ADD COLUMN {$tokenized_query['column_name']} $column_def"; 4386 | } elseif (stripos($tokenized_query['command'], 'rename') !== false) { 4387 | $query = "ALTER TABLE {$tokenized_query['table_name']} RENAME TO {$tokenized_query['column_name']}"; 4388 | } elseif (stripos($tokenized_query['command'], 'add index') !== false) { 4389 | $unique = isset($tokenized_query['unique']) ? 'UNIQUE' : ''; 4390 | $query = "CREATE $unique INDEX IF NOT EXISTS {$tokenized_query['index_name']} ON {$tokenized_query['table_name']} {$tokenized_query['column_name']}"; 4391 | } elseif (stripos($tokenized_query['command'], 'drop index') !== false) { 4392 | $query = "DROP INDEX IF EXISTS {$tokenized_query['index_name']}"; 4393 | } else { 4394 | $query = 'SELECT 1=1'; 4395 | } 4396 | 4397 | return $query; 4398 | } 4399 | 4400 | /** 4401 | * Function to handle ADD PRIMARY KEY. 4402 | * 4403 | * @access private 4404 | * 4405 | * @param array of string $queries 4406 | * 4407 | * @return array of string 4408 | */ 4409 | private function handle_add_primary_key($queries) 4410 | { 4411 | $tokenized_query = $queries; 4412 | $tbl_name = $tokenized_query['table_name']; 4413 | $temp_table = 'temp_' . $tokenized_query['table_name']; 4414 | $_wpdb = new wpsqlitedb(); 4415 | $query_obj = $_wpdb->get_results("SELECT sql FROM sqlite_master WHERE tbl_name='$tbl_name'"); 4416 | $_wpdb = null; 4417 | for ($i = 0; $i < count($query_obj); $i++) { 4418 | $index_queries[$i] = $query_obj[$i]->sql; 4419 | } 4420 | $table_query = array_shift($index_queries); 4421 | $table_query = str_replace($tokenized_query['table_name'], $temp_table, $table_query); 4422 | $table_query = rtrim($table_query, ')'); 4423 | $table_query = ", PRIMARY KEY {$tokenized_query['column_name']}"; 4424 | $query[] = $table_query; 4425 | $query[] = "INSERT INTO $temp_table SELECT * FROM {$tokenized_query['table_name']}"; 4426 | $query[] = "DROP TABLE IF EXISTS {$tokenized_query['table_name']}"; 4427 | $query[] = "ALTER TABLE $temp_table RENAME TO {$tokenized_query['table_name']}"; 4428 | foreach ($index_queries as $index) { 4429 | $query[] = $index; 4430 | } 4431 | 4432 | return $query; 4433 | } 4434 | 4435 | /** 4436 | * Function to handle DROP PRIMARY KEY. 4437 | * 4438 | * @access private 4439 | * 4440 | * @param array of string $queries 4441 | * 4442 | * @return array of string 4443 | */ 4444 | private function handle_drop_primary_key($queries) 4445 | { 4446 | $tokenized_query = $queries; 4447 | $temp_table = 'temp_' . $tokenized_query['table_name']; 4448 | $_wpdb = new wpsqlitedb(); 4449 | $query_obj = $_wpdb->get_results("SELECT sql FROM sqlite_master WHERE tbl_name='{$tokenized_query['table_name']}'"); 4450 | $_wpdb = null; 4451 | for ($i = 0; $i < count($query_obj); $i++) { 4452 | $index_queries[$i] = $query_obj[$i]->sql; 4453 | } 4454 | $table_query = array_shift($index_queries); 4455 | $pattern1 = '/^\\s*PRIMARY\\s*KEY\\s*\(.*\)/im'; 4456 | $pattern2 = '/^\\s*.*(PRIMARY\\s*KEY\\s*(:?AUTOINCREMENT|))\\s*(?!\()/im'; 4457 | if (preg_match($pattern1, $table_query, $match)) { 4458 | $table_query = str_replace($match[0], '', $table_query); 4459 | } elseif (preg_match($pattern2, $table_query, $match)) { 4460 | $table_query = str_replace($match[1], '', $table_query); 4461 | } 4462 | $table_query = str_replace($tokenized_query['table_name'], $temp_table, $table_query); 4463 | $query[] = $table_query; 4464 | $query[] = "INSERT INTO $temp_table SELECT * FROM {$tokenized_query['table_name']}"; 4465 | $query[] = "DROP TABLE IF EXISTS {$tokenized_query['table_name']}"; 4466 | $query[] = "ALTER TABLE $temp_table RENAME TO {$tokenized_query['table_name']}"; 4467 | foreach ($index_queries as $index) { 4468 | $query[] = $index; 4469 | } 4470 | 4471 | return $query; 4472 | } 4473 | 4474 | /** 4475 | * Function to handle MODIFY COLUMN. 4476 | * 4477 | * @access private 4478 | * 4479 | * @param array of string $queries 4480 | * 4481 | * @return string|array of string 4482 | */ 4483 | private function handle_modify_command($queries) 4484 | { 4485 | $tokenized_query = $queries; 4486 | $temp_table = 'temp_' . $tokenized_query['table_name']; 4487 | $column_def = $this->convert_field_types($tokenized_query['column_name'], $tokenized_query['column_def']); 4488 | $_wpdb = new wpsqlitedb(); 4489 | $query_obj = $_wpdb->get_results("SELECT sql FROM sqlite_master WHERE tbl_name='{$tokenized_query['table_name']}'"); 4490 | $_wpdb = null; 4491 | for ($i = 0; $i < count($query_obj); $i++) { 4492 | $index_queries[$i] = $query_obj[$i]->sql; 4493 | } 4494 | $create_query = array_shift($index_queries); 4495 | if (stripos($create_query, $tokenized_query['column_name']) === false) { 4496 | return 'SELECT 1=1'; 4497 | } elseif (preg_match("/{$tokenized_query['column_name']}\\s*{$column_def}\\s*[,)]/i", $create_query)) { 4498 | return 'SELECT 1=1'; 4499 | } 4500 | $create_query = preg_replace("/{$tokenized_query['table_name']}/i", $temp_table, $create_query); 4501 | if (preg_match("/\\b{$tokenized_query['column_name']}\\s*.*(?=,)/ims", $create_query)) { 4502 | $create_query = preg_replace("/\\b{$tokenized_query['column_name']}\\s*.*(?=,)/ims", 4503 | "{$tokenized_query['column_name']} {$column_def}", $create_query); 4504 | } elseif (preg_match("/\\b{$tokenized_query['column_name']}\\s*.*(?=\))/ims", $create_query)) { 4505 | $create_query = preg_replace("/\\b{$tokenized_query['column_name']}\\s*.*(?=\))/ims", 4506 | "{$tokenized_query['column_name']} {$column_def}", $create_query); 4507 | } 4508 | $query[] = $create_query; 4509 | $query[] = "INSERT INTO $temp_table SELECT * FROM {$tokenized_query['table_name']}"; 4510 | $query[] = "DROP TABLE IF EXISTS {$tokenized_query['table_name']}"; 4511 | $query[] = "ALTER TABLE $temp_table RENAME TO {$tokenized_query['table_name']}"; 4512 | foreach ($index_queries as $index) { 4513 | $query[] = $index; 4514 | } 4515 | 4516 | return $query; 4517 | } 4518 | 4519 | /** 4520 | * Function to handle CHANGE COLUMN. 4521 | * 4522 | * @access private 4523 | * 4524 | * @param array of string $queries 4525 | * 4526 | * @return string|array of string 4527 | */ 4528 | private function handle_change_command($queries) 4529 | { 4530 | $col_check = false; 4531 | $old_fields = ''; 4532 | $tokenized_query = $queries; 4533 | $temp_table = 'temp_' . $tokenized_query['table_name']; 4534 | if (isset($tokenized_query['new_column'])) { 4535 | $column_name = $tokenized_query['new_column']; 4536 | } else { 4537 | $column_name = $tokenized_query['old_column']; 4538 | } 4539 | $column_def = $this->convert_field_types($column_name, $tokenized_query['column_def']); 4540 | $_wpdb = new wpsqlitedb(); 4541 | $col_obj = $_wpdb->get_results("SHOW COLUMNS FROM {$tokenized_query['table_name']}"); 4542 | foreach ($col_obj as $col) { 4543 | if (stripos($col->Field, $tokenized_query['old_column']) !== false) { 4544 | $col_check = true; 4545 | } 4546 | $old_fields .= $col->Field . ','; 4547 | } 4548 | if ($col_check == false) { 4549 | $_wpdb = null; 4550 | 4551 | return 'SELECT 1=1'; 4552 | } 4553 | $old_fields = rtrim($old_fields, ','); 4554 | $new_fields = str_ireplace($tokenized_query['old_column'], $column_name, $old_fields); 4555 | $query_obj = $_wpdb->get_results("SELECT sql FROM sqlite_master WHERE tbl_name='{$tokenized_query['table_name']}'"); 4556 | $_wpdb = null; 4557 | for ($i = 0; $i < count($query_obj); $i++) { 4558 | $index_queries[$i] = $query_obj[$i]->sql; 4559 | } 4560 | $create_query = array_shift($index_queries); 4561 | $create_query = preg_replace("/{$tokenized_query['table_name']}/i", $temp_table, $create_query); 4562 | if (preg_match("/\\b{$tokenized_query['old_column']}\\s*(.+?)(?=,)/ims", $create_query, $match)) { 4563 | if (stripos(trim($match[1]), $column_def) !== false) { 4564 | return 'SELECT 1=1'; 4565 | } else { 4566 | $create_query = preg_replace("/\\b{$tokenized_query['old_column']}\\s*.+?(?=,)/ims", 4567 | "{$column_name} {$column_def}", $create_query, 1); 4568 | } 4569 | } elseif (preg_match("/\\b{$tokenized_query['old_column']}\\s*(.+?)(?=\))/ims", $create_query, $match)) { 4570 | if (stripos(trim($match[1]), $column_def) !== false) { 4571 | return 'SELECT 1=1'; 4572 | } else { 4573 | $create_query = preg_replace("/\\b{$tokenized_query['old_column']}\\s*.*(?=\))/ims", 4574 | "{$column_name} {$column_def}", $create_query, 1); 4575 | } 4576 | } 4577 | $query[] = $create_query; 4578 | $query[] = "INSERT INTO $temp_table ($new_fields) SELECT $old_fields FROM {$tokenized_query['table_name']}"; 4579 | $query[] = "DROP TABLE IF EXISTS {$tokenized_query['table_name']}"; 4580 | $query[] = "ALTER TABLE $temp_table RENAME TO {$tokenized_query['table_name']}"; 4581 | foreach ($index_queries as $index) { 4582 | $query[] = $index; 4583 | } 4584 | 4585 | return $query; 4586 | } 4587 | 4588 | /** 4589 | * Function to handle ALTER COLUMN. 4590 | * 4591 | * @access private 4592 | * 4593 | * @param array of string $queries 4594 | * 4595 | * @return string|array of string 4596 | */ 4597 | private function handle_alter_command($queries) 4598 | { 4599 | $tokenized_query = $queries; 4600 | $temp_table = 'temp_' . $tokenized_query['table_name']; 4601 | if (isset($tokenized_query['default_value'])) { 4602 | $def_value = $this->convert_field_types($tokenized_query['column_name'], $tokenized_query['default_value']); 4603 | $def_value = 'DEFAULT ' . $def_value; 4604 | } else { 4605 | $def_value = null; 4606 | } 4607 | $_wpdb = new wpsqlitedb(); 4608 | $query_obj = $_wpdb->get_results("SELECT sql FROM sqlite_master WHERE tbl_name='{$tokenized_query['table_name']}'"); 4609 | $_wpdb = null; 4610 | for ($i = 0; $i < count($query_obj); $i++) { 4611 | $index_queries[$i] = $query_obj[$i]->sql; 4612 | } 4613 | $create_query = array_shift($index_queries); 4614 | if (stripos($create_query, $tokenized_query['column_name']) === false) { 4615 | return 'SELECT 1=1'; 4616 | } 4617 | if (preg_match("/\\s*({$tokenized_query['column_name']})\\s*(.*)?(DEFAULT\\s*.*)[,)]/im", $create_query, 4618 | $match)) { 4619 | $col_name = trim($match[1]); 4620 | $col_def = trim($match[2]); 4621 | $col_def_esc = str_replace(['(', ')'], ['\(', '\)'], $col_def); 4622 | $checked_col_def = $this->convert_field_types($col_name, $col_def); 4623 | $old_default = trim($match[3]); 4624 | $pattern = "/$col_name\\s*$col_def_esc\\s*$old_default/im"; 4625 | if (is_null($def_value)) { 4626 | $replacement = $col_name . ' ' . $checked_col_def; 4627 | } else { 4628 | $replacement = $col_name . ' ' . $checked_col_def . ' ' . $def_value; 4629 | } 4630 | $create_query = preg_replace($pattern, $replacement, $create_query); 4631 | $create_query = str_ireplace($tokenized_query['table_name'], $temp_table, $create_query); 4632 | } elseif (preg_match("/\\s*({$tokenized_query['column_name']})\\s*(.*)?[,)]/im", $create_query, $match)) { 4633 | $col_name = trim($match[1]); 4634 | $col_def = trim($match[2]); 4635 | $col_def_esc = str_replace(['(', ')'], ['\(', '\)'], $col_def); 4636 | $checked_col_def = $this->convert_field_types($col_name, $col_def); 4637 | $pattern = "/$col_name\\s*$col_def_esc/im"; 4638 | if (is_null($def_value)) { 4639 | $replacement = $col_name . ' ' . $checked_col_def; 4640 | } else { 4641 | $replacement = $col_name . ' ' . $checked_col_def . ' ' . $def_value; 4642 | } 4643 | $create_query = preg_replace($pattern, $replacement, $create_query); 4644 | $create_query = str_ireplace($tokenized_query['table_name'], $temp_table, $create_query); 4645 | } else { 4646 | return 'SELECT 1=1'; 4647 | } 4648 | $query[] = $create_query; 4649 | $query[] = "INSERT INTO $temp_table SELECT * FROM {$tokenized_query['table_name']}"; 4650 | $query[] = "DROP TABLE IF EXISTS {$tokenized_query['table_name']}"; 4651 | $query[] = "ALTER TABLE $temp_table RENAME TO {$tokenized_query['table_name']}"; 4652 | foreach ($index_queries as $index) { 4653 | $query[] = $index; 4654 | } 4655 | 4656 | return $query; 4657 | } 4658 | 4659 | /** 4660 | * Function to change the field definition to SQLite compatible data type. 4661 | * 4662 | * @access private 4663 | * 4664 | * @param string $col_name 4665 | * @param string $col_def 4666 | * 4667 | * @return string 4668 | */ 4669 | private function convert_field_types($col_name, $col_def) 4670 | { 4671 | $array_curtime = ['current_timestamp', 'current_time', 'current_date']; 4672 | $array_reptime = ["'0000-00-00 00:00:00'", "'0000-00-00 00:00:00'", "'0000-00-00'"]; 4673 | $def_string = str_replace('`', '', $col_def); 4674 | foreach ($this->array_types as $o => $r) { 4675 | $pattern = "/\\b$o\\s*(\([^\)]*\)*)?\\s*/ims"; 4676 | if (preg_match($pattern, $def_string)) { 4677 | $def_string = preg_replace($pattern, "$r ", $def_string); 4678 | break; 4679 | } 4680 | } 4681 | $def_string = preg_replace('/unsigned/im', '', $def_string); 4682 | $def_string = preg_replace('/auto_increment/im', 'PRIMARY KEY AUTOINCREMENT', $def_string); 4683 | // when you use ALTER TABLE ADD, you can't use current_*. so we replace 4684 | $def_string = str_ireplace($array_curtime, $array_reptime, $def_string); 4685 | // colDef is enum 4686 | $pattern_enum = '/enum\((.*?)\)([^,\)]*)/ims'; 4687 | if (preg_match($pattern_enum, $col_def, $matches)) { 4688 | $def_string = 'TEXT' . $matches[2] . ' CHECK (' . $col_name . ' IN (' . $matches[1] . '))'; 4689 | } 4690 | 4691 | return $def_string; 4692 | } 4693 | 4694 | /** 4695 | * Variable to store the data definition table. 4696 | * 4697 | * @access private 4698 | * @var associative array 4699 | */ 4700 | private $array_types = [ 4701 | 'bit' => 'INTEGER', 4702 | 'bool' => 'INTEGER', 4703 | 'boolean' => 'INTEGER', 4704 | 'tinyint' => 'INTEGER', 4705 | 'smallint' => 'INTEGER', 4706 | 'mediumint' => 'INTEGER', 4707 | 'bigint' => 'INTEGER', 4708 | 'integer' => 'INTEGER', 4709 | 'int' => 'INTEGER', 4710 | 'float' => 'REAL', 4711 | 'double' => 'REAL', 4712 | 'decimal' => 'REAL', 4713 | 'dec' => 'REAL', 4714 | 'numeric' => 'REAL', 4715 | 'fixed' => 'REAL', 4716 | 'datetime' => 'TEXT', 4717 | 'date' => 'TEXT', 4718 | 'timestamp' => 'TEXT', 4719 | 'time' => 'TEXT', 4720 | 'year' => 'TEXT', 4721 | 'varchar' => 'TEXT', 4722 | 'char' => 'TEXT', 4723 | 'varbinary' => 'BLOB', 4724 | 'binary' => 'BLOB', 4725 | 'tinyblob' => 'BLOB', 4726 | 'mediumblob' => 'BLOB', 4727 | 'longblob' => 'BLOB', 4728 | 'blob' => 'BLOB', 4729 | 'tinytext' => 'TEXT', 4730 | 'mediumtext' => 'TEXT', 4731 | 'longtext' => 'TEXT', 4732 | 'text' => 'TEXT', 4733 | ]; 4734 | } 4735 | 4736 | /** 4737 | * Function to create tables according to the schemas of WordPress. 4738 | * 4739 | * This is executed only once while installation. 4740 | * 4741 | * @return boolean 4742 | */ 4743 | function make_db_sqlite() 4744 | { 4745 | include_once ABSPATH . 'wp-admin/includes/schema.php'; 4746 | $index_array = []; 4747 | 4748 | $table_schemas = wp_get_db_schema(); 4749 | $queries = explode(";", $table_schemas); 4750 | $query_parser = new CreateQuery(); 4751 | try { 4752 | $pdo = new PDO('sqlite:' . FQDB, null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); 4753 | } catch (PDOException $err) { 4754 | $err_data = $err->errorInfo; 4755 | $message = 'Database connection error!
'; 4756 | $message .= sprintf("Error message is: %s", $err_data[2]); 4757 | wp_die($message, 'Database Error!'); 4758 | } 4759 | 4760 | try { 4761 | $pdo->beginTransaction(); 4762 | foreach ($queries as $query) { 4763 | $query = trim($query); 4764 | if (empty($query)) { 4765 | continue; 4766 | } 4767 | $rewritten_query = $query_parser->rewrite_query($query); 4768 | if (is_array($rewritten_query)) { 4769 | $table_query = array_shift($rewritten_query); 4770 | $index_queries = $rewritten_query; 4771 | $table_query = trim($table_query); 4772 | $pdo->exec($table_query); 4773 | //foreach($rewritten_query as $single_query) { 4774 | // $single_query = trim($single_query); 4775 | // $pdo->exec($single_query); 4776 | //} 4777 | } else { 4778 | $rewritten_query = trim($rewritten_query); 4779 | $pdo->exec($rewritten_query); 4780 | } 4781 | } 4782 | $pdo->commit(); 4783 | if ($index_queries) { 4784 | // $query_parser rewrites KEY to INDEX, so we don't need KEY pattern 4785 | $pattern = '/CREATE\\s*(UNIQUE\\s*INDEX|INDEX)\\s*IF\\s*NOT\\s*EXISTS\\s*(\\w+)?\\s*.*/im'; 4786 | $pdo->beginTransaction(); 4787 | foreach ($index_queries as $index_query) { 4788 | preg_match($pattern, $index_query, $match); 4789 | $index_name = trim($match[2]); 4790 | if (in_array($index_name, $index_array)) { 4791 | $r = rand(0, 50); 4792 | $replacement = $index_name . "_$r"; 4793 | $index_query = str_ireplace('EXISTS ' . $index_name, 'EXISTS ' . $replacement, 4794 | $index_query); 4795 | } else { 4796 | $index_array[] = $index_name; 4797 | } 4798 | $pdo->exec($index_query); 4799 | } 4800 | $pdo->commit(); 4801 | } 4802 | } catch (PDOException $err) { 4803 | $err_data = $err->errorInfo; 4804 | $err_code = $err_data[1]; 4805 | if (5 == $err_code || 6 == $err_code) { 4806 | // if the database is locked, commit again 4807 | $pdo->commit(); 4808 | } else { 4809 | $pdo->rollBack(); 4810 | $message = sprintf("Error occured while creating tables or indexes...
Query was: %s
", 4811 | var_export($rewritten_query, true)); 4812 | $message .= sprintf("Error message is: %s", $err_data[2]); 4813 | wp_die($message, 'Database Error!'); 4814 | } 4815 | } 4816 | 4817 | $query_parser = null; 4818 | $pdo = null; 4819 | 4820 | return true; 4821 | } 4822 | } // WP_SQLite_DB namespace 4823 | 4824 | namespace { 4825 | 4826 | /** 4827 | * Installs the site. 4828 | * 4829 | * Runs the required functions to set up and populate the database, 4830 | * including primary admin user and initial options. 4831 | * 4832 | * @since 2.1.0 4833 | * 4834 | * @param string $blog_title Site title. 4835 | * @param string $user_name User's username. 4836 | * @param string $user_email User's email. 4837 | * @param bool $public Whether site is public. 4838 | * @param string $deprecated Optional. Not used. 4839 | * @param string $user_password Optional. User's chosen password. Default empty (random password). 4840 | * @param string $language Optional. Language chosen. Default empty. 4841 | * 4842 | * @return array Array keys 'url', 'user_id', 'password', and 'password_message'. 4843 | */ 4844 | function wp_install($blog_title, $user_name, $user_email, $public, $deprecated = '', $user_password = '', $language = '') 4845 | { 4846 | if (! empty($deprecated)) { 4847 | _deprecated_argument(__FUNCTION__, '2.6.0'); 4848 | } 4849 | 4850 | wp_check_mysql_version(); 4851 | wp_cache_flush(); 4852 | /* begin wp-sqlite-db changes */ 4853 | // make_db_current_silent(); 4854 | WP_SQLite_DB\make_db_sqlite(); 4855 | /* end wp-sqlite-db changes */ 4856 | populate_options(); 4857 | populate_roles(); 4858 | 4859 | update_option('blogname', $blog_title); 4860 | update_option('admin_email', $user_email); 4861 | update_option('blog_public', $public); 4862 | 4863 | // Freshness of site - in the future, this could get more specific about actions taken, perhaps. 4864 | update_option('fresh_site', 1); 4865 | 4866 | if ($language) { 4867 | update_option('WPLANG', $language); 4868 | } 4869 | 4870 | $guessurl = wp_guess_url(); 4871 | 4872 | update_option('siteurl', $guessurl); 4873 | 4874 | // If not a public blog, don't ping. 4875 | if (! $public) { 4876 | update_option('default_pingback_flag', 0); 4877 | } 4878 | 4879 | /* 4880 | * Create default user. If the user already exists, the user tables are 4881 | * being shared among sites. Just set the role in that case. 4882 | */ 4883 | $user_id = username_exists($user_name); 4884 | $user_password = trim($user_password); 4885 | $email_password = false; 4886 | if (! $user_id && empty($user_password)) { 4887 | $user_password = wp_generate_password(12, false); 4888 | $message = __('Note that password carefully! It is a random password that was generated just for you.'); 4889 | $user_id = wp_create_user($user_name, $user_password, $user_email); 4890 | update_user_option($user_id, 'default_password_nag', true, true); 4891 | $email_password = true; 4892 | } elseif (! $user_id) { 4893 | // Password has been provided 4894 | $message = '' . __('Your chosen password.') . ''; 4895 | $user_id = wp_create_user($user_name, $user_password, $user_email); 4896 | } else { 4897 | $message = __('User already exists. Password inherited.'); 4898 | } 4899 | 4900 | $user = new WP_User($user_id); 4901 | $user->set_role('administrator'); 4902 | 4903 | wp_install_defaults($user_id); 4904 | 4905 | wp_install_maybe_enable_pretty_permalinks(); 4906 | 4907 | flush_rewrite_rules(); 4908 | 4909 | wp_new_blog_notification($blog_title, $guessurl, $user_id, ($email_password ? $user_password : __('The password you chose during installation.'))); 4910 | 4911 | wp_cache_flush(); 4912 | 4913 | /** 4914 | * Fires after a site is fully installed. 4915 | * 4916 | * @since 3.9.0 4917 | * 4918 | * @param WP_User $user The site owner. 4919 | */ 4920 | do_action('wp_install', $user); 4921 | 4922 | return ['url' => $guessurl, 'user_id' => $user_id, 'password' => $user_password, 'password_message' => $message]; 4923 | } 4924 | 4925 | $GLOBALS['wpdb'] = new WP_SQLite_DB\wpsqlitedb(); 4926 | } 4927 | 4928 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | assertCount(0, get_posts()); 13 | 14 | $this->factory()->post->create(); 15 | 16 | $this->assertCount(1, get_posts()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/src/TestCase.php: -------------------------------------------------------------------------------- 1 |