├── .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 | [](https://github.com/aaemnnosttv/wp-sqlite-db/actions/workflows/test.yml)
4 | [](https://packagist.org/packages/aaemnnosttv/wp-sqlite-db)
5 | [](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 |
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 |
$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!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 = '' . 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 "
WordPress database error: [$str]
2657 | $query