PluginProbe
SQLite Database Integration / 3.0.2
SQLite Database Integration v3.0.2
3.0.2 3.0.1 trunk 2.1.13 2.1.14 2.1.15 2.1.16 2.2.0 2.2.1 2.2.10 2.2.11 2.2.12 2.2.13 2.2.14 2.2.15 2.2.16 2.2.17 2.2.18 2.2.19 2.2.2 2.2.20 2.2.21 2.2.22 2.2.23 2.2.3 All 32 releases
sqlite-database-integration / wp-includes / database / sqlite / class-wp-sqlite-pdo-user-defined-functions.php

class-wp-sqlite-pdo-user-defined-functions.php in SQLite Database Integration 3.0.2, at wp-includes/database/sqlite/class-wp-sqlite-pdo-user-defined-functions.php

1,001 lines 27.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Custom functions for the SQLite implementation.
4 */
5
6 /**
7 * Registers MySQL-compatible functions with PDO SQLite.
8 *
9 * Each callback implements a MySQL SQL function that SQLite does not provide.
10 *
11 * @access private
12 */
13 class WP_SQLite_PDO_User_Defined_Functions {
14
15 /**
16 * Register the user-defined SQLite functions on a PDO connection.
17 *
18 * The functions are registered using PDO::sqliteCreateFunction().
19 *
20 * @param PDO|Pdo\Sqlite $pdo The PDO object.
21 */
22 public static function register_for( $pdo ): self {
23 $instance = new self();
24 foreach ( $instance->functions as $f => $t ) {
25 if ( $pdo instanceof Pdo\Sqlite ) {
26 $pdo->createFunction( $f, array( $instance, $t ) );
27 } else {
28 $pdo->sqliteCreateFunction( $f, array( $instance, $t ) );
29 }
30 }
31 return $instance;
32 }
33
34 /**
35 * Array to define MySQL function => function defined with PHP.
36 *
37 * Replaced functions must be public.
38 *
39 * @var array
40 */
41 private $functions = array(
42 'throw' => 'throw',
43 'month' => 'month',
44 'monthnum' => 'month',
45 'year' => 'year',
46 'day' => 'day',
47 'hour' => 'hour',
48 'minute' => 'minute',
49 'second' => 'second',
50 'week' => 'week',
51 'weekday' => 'weekday',
52 'dayofweek' => 'dayofweek',
53 'dayofmonth' => 'dayofmonth',
54 'unix_timestamp' => 'unix_timestamp',
55 'now' => 'now',
56 'md5' => 'md5',
57 'curdate' => 'curdate',
58 'rand' => 'rand',
59 'from_unixtime' => 'from_unixtime',
60 'localtime' => 'now',
61 'localtimestamp' => 'now',
62 'isnull' => 'isnull',
63 'if' => '_if',
64 'regexp' => 'regexp',
65 'field' => 'field',
66 'log' => 'log',
67 'least' => 'least',
68 'greatest' => 'greatest',
69 'get_lock' => 'get_lock',
70 'release_lock' => 'release_lock',
71 'ucase' => 'ucase',
72 'lcase' => 'lcase',
73 'unhex' => 'unhex',
74 'from_base64' => 'from_base64',
75 'to_base64' => 'to_base64',
76 'inet_ntoa' => 'inet_ntoa',
77 'inet_aton' => 'inet_aton',
78 'datediff' => 'datediff',
79 'locate' => 'locate',
80 'utc_date' => 'utc_date',
81 'utc_time' => 'utc_time',
82 'utc_timestamp' => 'utc_timestamp',
83 'version' => 'version',
84 'reverse' => 'reverse',
85
86 // Internal helper functions.
87 '_helper_like_to_glob_pattern' => '_helper_like_to_glob_pattern',
88 );
89
90 /**
91 * First element of the RAND(N) LCG state (the value the output is derived from).
92 *
93 * @var int|null
94 */
95 private $rand_seed1 = null;
96
97 /**
98 * Second element of the RAND(N) LCG state (the paired value used in the recurrence).
99 *
100 * @var int|null
101 */
102 private $rand_seed2 = null;
103
104 /**
105 * Last seed value passed to RAND(N) in the current statement.
106 *
107 * Used to detect whether the rand sequence is advancing with the same seed
108 * (e.g. "SELECT RAND(3) FROM t"), or reseeding (starting a new sequence).
109 *
110 * @var int|null
111 */
112 private $rand_last_seed = null;
113
114 /**
115 * Clear any per-statement state held by the UDFs.
116 */
117 public function flush(): void {
118 $this->rand_seed1 = null;
119 $this->rand_seed2 = null;
120 $this->rand_last_seed = null;
121 }
122
123 /**
124 * A helper function to throw an error from SQLite expressions.
125 *
126 * @param string $message The error message.
127 *
128 * @throws Exception The error message.
129 * @return void
130 */
131 public function throw( $message ): void {
132 throw new Exception( $message );
133 }
134
135 /**
136 * Method to return the unix timestamp.
137 *
138 * Used without an argument, it returns PHP time() function (total seconds passed
139 * from '1970-01-01 00:00:00' GMT). Used with the argument, it changes the value
140 * to the timestamp.
141 *
142 * @param string $field Representing the date formatted as '0000-00-00 00:00:00'.
143 *
144 * @return number of unsigned integer
145 */
146 public function unix_timestamp( $field = null ) {
147 return is_null( $field ) ? time() : strtotime( $field );
148 }
149
150 /**
151 * Method to emulate MySQL FROM_UNIXTIME() function.
152 *
153 * @param int $field The unix timestamp.
154 * @param string $format Indicate the way of formatting(optional).
155 *
156 * @return string
157 */
158 public function from_unixtime( $field, $format = null ) {
159 // Convert to ISO time.
160 $date = gmdate( 'Y-m-d H:i:s', $field );
161
162 return is_null( $format ) ? $date : $this->dateformat( $date, $format );
163 }
164
165 /**
166 * Method to emulate MySQL NOW() function.
167 *
168 * @return string representing current time formatted as '0000-00-00 00:00:00'.
169 */
170 public function now() {
171 return gmdate( 'Y-m-d H:i:s' );
172 }
173
174 /**
175 * Method to emulate MySQL CURDATE() function.
176 *
177 * @return string representing current time formatted as '0000-00-00'.
178 */
179 public function curdate() {
180 return gmdate( 'Y-m-d' );
181 }
182
183 /**
184 * Method to emulate MySQL MD5() function.
185 *
186 * @param string $field The string to be hashed.
187 *
188 * @return string of the md5 hash value of the argument.
189 */
190 public function md5( $field ) {
191 return md5( $field );
192 }
193
194 /**
195 * Method to emulate MySQL's seeded RAND(N) function.
196 *
197 * Implements MySQL's deterministic LCG (Linear Congruential Generator),
198 * producing bit-exact output for a given seed.
199 *
200 * Known divergences from MySQL:
201 *
202 * 1. In MySQL, RAND(N) behaves differently depending on whether the seed
203 * is constant expression or varies per invocation:
204 * - Constant seed (e.g. "SELECT RAND(3) FROM t"):
205 * LCG is initialized once per statement and advanced for each row.
206 * - Non-constant seed (e.g. "SELECT RAND(col) FROM t"):
207 * LCG is initialized for every row with its seed value.
208 *
209 * A SQLite UDF cannot tell whether the seed expression is constant, so
210 * we just compare the seed against its last value. This diverges from
211 * MySQL in rare cases, and we can consider improving it in the future.
212 *
213 * 2. The LCG state is shared across call sites in the same query, so
214 * "SELECT RAND(1), RAND(1)" yields different results here than in MySQL.
215 * This is a rare edge case that we can consider improving in the future.
216 *
217 * Unseeded RAND() never reaches this function. The AST driver translates it
218 * directly to a more efficient SQLite-native expression.
219 *
220 * @param int|float|string|null $seed Seed value.
221 *
222 * @return float A value in [0, 1).
223 */
224 public function rand( $seed ) {
225 // Requires 64-bit PHP. Seed * 0x10000001 can exceed PHP_INT_MAX on 32-bit.
226 $max_value = 0x3FFFFFFF;
227
228 if ( null === $seed ) {
229 // MySQL treats NULL seed as 0.
230 $seed = 0;
231 } elseif ( ! is_int( $seed ) ) {
232 /*
233 * MySQL rounds float values and numeric strings take the same path.
234 * Reduce the value to a 32-bit range using "fmod" to avoid firing
235 * the "out-of-range float to int" cast deprecation on PHP 8.1+.
236 */
237 $seed = (int) fmod( round( (float) $seed, 0, PHP_ROUND_HALF_EVEN ), 0x100000000 );
238 }
239
240 // Initialize MySQL's internal 30-bit seeds.
241 if ( $seed !== $this->rand_last_seed ) {
242 /*
243 * MySQL casts to uint32, and the intermediate results wrap at 32-bit
244 * unsigned boundaries. We emulate this with & 0xFFFFFFFF masks.
245 */
246 $seed_u32 = $seed & 0xFFFFFFFF;
247 $this->rand_seed1 = ( ( $seed_u32 * 0x10001 + 55555555 ) & 0xFFFFFFFF ) % $max_value;
248 $this->rand_seed2 = ( ( $seed_u32 * 0x10000001 ) & 0xFFFFFFFF ) % $max_value;
249 $this->rand_last_seed = $seed;
250 }
251
252 /*
253 * MySQL's LCG recurrence:
254 * seed1 = (seed1 * 3 + seed2) % max_value
255 * seed2 = (seed1 + seed2 + 33) % max_value
256 *
257 * Note that seed1 is updated first and the new value is used for seed2.
258 */
259 $this->rand_seed1 = ( $this->rand_seed1 * 3 + $this->rand_seed2 ) % $max_value;
260 $this->rand_seed2 = ( $this->rand_seed1 + $this->rand_seed2 + 33 ) % $max_value;
261
262 return (float) $this->rand_seed1 / (float) $max_value;
263 }
264
265 /**
266 * Method to emulate MySQL DATEFORMAT() function.
267 *
268 * @param string $date Formatted as '0000-00-00' or datetime as '0000-00-00 00:00:00'.
269 * @param string $format The string format.
270 *
271 * @return string formatted according to $format
272 */
273 public function dateformat( $date, $format ) {
274 $mysql_php_date_formats = array(
275 '%a' => 'D',
276 '%b' => 'M',
277 '%c' => 'n',
278 '%D' => 'jS',
279 '%d' => 'd',
280 '%e' => 'j',
281 '%H' => 'H',
282 '%h' => 'h',
283 '%I' => 'h',
284 '%i' => 'i',
285 '%j' => 'z',
286 '%k' => 'G',
287 '%l' => 'g',
288 '%M' => 'F',
289 '%m' => 'm',
290 '%p' => 'A',
291 '%r' => 'h:i:s A',
292 '%S' => 's',
293 '%s' => 's',
294 '%T' => 'H:i:s',
295 '%U' => 'W',
296 '%u' => 'W',
297 '%V' => 'W',
298 '%v' => 'W',
299 '%W' => 'l',
300 '%w' => 'w',
301 '%X' => 'Y',
302 '%x' => 'o',
303 '%Y' => 'Y',
304 '%y' => 'y',
305 );
306
307 $time = strtotime( $date );
308 $format = strtr( $format, $mysql_php_date_formats );
309
310 return gmdate( $format, $time );
311 }
312
313 /**
314 * Method to extract the month value from the date.
315 *
316 * @param string $field Representing the date formatted as 0000-00-00.
317 *
318 * @return string Representing the number of the month between 1 and 12.
319 */
320 public function month( $field ) {
321 /*
322 * MySQL returns 0 for MONTH('0000-00-00') and for dates with
323 * zero month parts like '2020-00-15'. PHP's strtotime() can't
324 * parse these, so we extract the month directly from the string.
325 */
326 if ( preg_match( '/^\d{4}-(\d{2})/', $field, $matches ) ) {
327 return intval( $matches[1] );
328 }
329 /*
330 * From https://www.php.net/manual/en/datetime.format.php:
331 *
332 * n - Numeric representation of a month, without leading zeros.
333 * 1 through 12
334 */
335 return intval( gmdate( 'n', strtotime( $field ) ) );
336 }
337
338 /**
339 * Method to extract the year value from the date.
340 *
341 * @param string $field Representing the date formatted as 0000-00-00.
342 *
343 * @return string Representing the number of the year.
344 */
345 public function year( $field ) {
346 /*
347 * MySQL returns 0 for YEAR('0000-00-00'). PHP's strtotime()
348 * can't parse zero dates, so we extract the year directly.
349 */
350 if ( preg_match( '/^(\d{4})-\d{2}/', $field, $matches ) ) {
351 return intval( $matches[1] );
352 }
353 /*
354 * From https://www.php.net/manual/en/datetime.format.php:
355 *
356 * Y - A full numeric representation of a year, 4 digits.
357 */
358 return intval( gmdate( 'Y', strtotime( $field ) ) );
359 }
360
361 /**
362 * Method to extract the day value from the date.
363 *
364 * @param string $field Representing the date formatted as 0000-00-00.
365 *
366 * @return string Representing the number of the day of the month from 1 and 31.
367 */
368 public function day( $field ) {
369 /*
370 * MySQL returns 0 for DAY('0000-00-00') and for dates with
371 * zero day parts like '2020-01-00'. PHP's strtotime() can't
372 * parse these, so we extract the day directly from the string.
373 */
374 if ( preg_match( '/^\d{4}-\d{2}-(\d{2})/', $field, $matches ) ) {
375 return intval( $matches[1] );
376 }
377 /*
378 * From https://www.php.net/manual/en/datetime.format.php:
379 *
380 * j - Day of the month without leading zeros.
381 * 1 to 31.
382 */
383 return intval( gmdate( 'j', strtotime( $field ) ) );
384 }
385
386 /**
387 * Method to emulate MySQL SECOND() function.
388 *
389 * @see https://www.php.net/manual/en/datetime.format.php
390 *
391 * @param string $field Representing the time formatted as '00:00:00'.
392 *
393 * @return number Unsigned integer
394 */
395 public function second( $field ) {
396 /*
397 * From https://www.php.net/manual/en/datetime.format.php:
398 *
399 * s - Seconds, with leading zeros (00 to 59)
400 */
401 return intval( gmdate( 's', strtotime( $field ) ) );
402 }
403
404 /**
405 * Method to emulate MySQL MINUTE() function.
406 *
407 * @param string $field Representing the time formatted as '00:00:00'.
408 *
409 * @return int
410 */
411 public function minute( $field ) {
412 /*
413 * From https://www.php.net/manual/en/datetime.format.php:
414 *
415 * i - Minutes with leading zeros.
416 * 00 to 59.
417 */
418 return intval( gmdate( 'i', strtotime( $field ) ) );
419 }
420
421 /**
422 * Method to emulate MySQL HOUR() function.
423 *
424 * Returns the hour for time, in 24-hour format, from 0 to 23.
425 * Importantly, midnight is 0, not 24.
426 *
427 * @param string $time Representing the time formatted, like '14:08:12'.
428 *
429 * @return int
430 */
431 public function hour( $time ) {
432 /*
433 * From https://www.php.net/manual/en/datetime.format.php:
434 *
435 * H 24-hour format of an hour with leading zeros.
436 * 00 through 23.
437 */
438 return intval( gmdate( 'H', strtotime( $time ) ) );
439 }
440
441 /**
442 * Covers MySQL WEEK() function.
443 *
444 * Always assumes $mode = 1.
445 *
446 * @TODO: Support other modes.
447 *
448 * From https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_week:
449 *
450 * > Returns the week number for date. The two-argument form of WEEK()
451 * > enables you to specify whether the week starts on Sunday or Monday
452 * > and whether the return value should be in the range from 0 to 53
453 * > or from 1 to 53. If the mode argument is omitted, the value of the
454 * > default_week_format system variable is used.
455 * >
456 * > The following table describes how the mode argument works:
457 * >
458 * > Mode First day of week Range Week 1 is the first week …
459 * > 0 Sunday 0-53 with a Sunday in this year
460 * > 1 Monday 0-53 with 4 or more days this year
461 * > 2 Sunday 1-53 with a Sunday in this year
462 * > 3 Monday 1-53 with 4 or more days this year
463 * > 4 Sunday 0-53 with 4 or more days this year
464 * > 5 Monday 0-53 with a Monday in this year
465 * > 6 Sunday 1-53 with 4 or more days this year
466 * > 7 Monday 1-53 with a Monday in this year
467 *
468 * @param string $field Representing the date.
469 * @param int $mode The mode argument.
470 */
471 public function week( $field, $mode ) {
472 /*
473 * From https://www.php.net/manual/en/datetime.format.php:
474 *
475 * W - ISO-8601 week number of year, weeks starting on Monday.
476 * Example: 42 (the 42nd week in the year)
477 *
478 * Week 1 is the first week with a Thursday in it.
479 */
480 return intval( gmdate( 'W', strtotime( $field ) ) );
481 }
482
483 /**
484 * Simulates WEEKDAY() function in MySQL.
485 *
486 * Returns the day of the week as an integer.
487 * The days of the week are numbered 0 to 6:
488 * * 0 for Monday
489 * * 1 for Tuesday
490 * * 2 for Wednesday
491 * * 3 for Thursday
492 * * 4 for Friday
493 * * 5 for Saturday
494 * * 6 for Sunday
495 *
496 * @param string $field Representing the date.
497 *
498 * @return int
499 */
500 public function weekday( $field ) {
501 /*
502 * date('N') returns 1 (for Monday) through 7 (for Sunday)
503 * That's one more than MySQL.
504 * Let's subtract one to make it compatible.
505 */
506 return intval( gmdate( 'N', strtotime( $field ) ) ) - 1;
507 }
508
509 /**
510 * Method to emulate MySQL DAYOFMONTH() function.
511 *
512 * @see https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_dayofmonth
513 *
514 * @param string $field Representing the date.
515 *
516 * @return int Returns the day of the month for date as a number in the range 1 to 31.
517 */
518 public function dayofmonth( $field ) {
519 return intval( gmdate( 'j', strtotime( $field ) ) );
520 }
521
522 /**
523 * Method to emulate MySQL DAYOFWEEK() function.
524 *
525 * > Returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday).
526 * > These index values correspond to the ODBC standard. Returns NULL if date is NULL.
527 *
528 * @param string $field Representing the date.
529 *
530 * @return int Returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday).
531 */
532 public function dayofweek( $field ) {
533 /**
534 * From https://www.php.net/manual/en/datetime.format.php:
535 *
536 * `w` – Numeric representation of the day of the week
537 * 0 (for Sunday) through 6 (for Saturday)
538 */
539 return intval( gmdate( 'w', strtotime( $field ) ) ) + 1;
540 }
541
542 /**
543 * Method to emulate MySQL DATE() function.
544 *
545 * @see https://www.php.net/manual/en/datetime.format.php
546 *
547 * @param string $date formatted as unix time.
548 *
549 * @return string formatted as '0000-00-00'.
550 */
551 public function date( $date ) {
552 return gmdate( 'Y-m-d', strtotime( $date ) );
553 }
554
555 /**
556 * Method to emulate MySQL ISNULL() function.
557 *
558 * This function returns true if the argument is null, and true if not.
559 *
560 * @param mixed $field The field to be tested.
561 *
562 * @return boolean
563 */
564 public function isnull( $field ) {
565 return is_null( $field );
566 }
567
568 /**
569 * Method to emulate MySQL IF() function.
570 *
571 * As 'IF' is a reserved word for PHP, function name must be changed.
572 *
573 * @param mixed $expression The statement to be evaluated as true or false.
574 * @param mixed $truthy Statement or value returned if $expression is true.
575 * @param mixed $falsy Statement or value returned if $expression is false.
576 *
577 * @return mixed
578 */
579 public function _if( $expression, $truthy, $falsy ) {
580 return ( true === $expression ) ? $truthy : $falsy;
581 }
582
583 /**
584 * Method to emulate MySQL REGEXP() function.
585 *
586 * @param string $pattern Regular expression to match.
587 * @param string $field Haystack.
588 *
589 * @return integer 1 if matched, 0 if not matched.
590 */
591 public function regexp( $pattern, $field ) {
592 /*
593 * If the original query says REGEXP BINARY
594 * the comparison is byte-by-byte and letter casing now
595 * matters since lower- and upper-case letters have different
596 * byte codes.
597 *
598 * The REGEXP function can't be easily made to accept two
599 * parameters, so we'll have to use a hack to get around this.
600 *
601 * If the first character of the pattern is a null byte, we'll
602 * remove it and make the comparison case-sensitive. This should
603 * be reasonably safe since PHP does not allow null bytes in
604 * regular expressions anyway.
605 */
606 if ( "\x00" === $pattern[0] ) {
607 $pattern = substr( $pattern, 1 );
608 $flags = '';
609 } else {
610 // Otherwise, the search is case-insensitive.
611 $flags = 'i';
612 }
613 $pattern = str_replace( '/', '\/', $pattern );
614 $pattern = '/' . $pattern . '/' . $flags;
615
616 return preg_match( $pattern, $field );
617 }
618
619 /**
620 * Method to emulate MySQL FIELD() function.
621 *
622 * This function gets the list argument and compares the first item to all the others.
623 * If the same value is found, it returns the position of that value. If not, it
624 * returns 0.
625 *
626 * @return int
627 */
628 public function field() {
629 $num_args = func_num_args();
630 if ( $num_args < 2 || is_null( func_get_arg( 0 ) ) ) {
631 return 0;
632 }
633 $arg_list = func_get_args();
634 $search_string = strtolower( array_shift( $arg_list ) );
635
636 for ( $i = 0; $i < $num_args - 1; $i++ ) {
637 if ( strtolower( $arg_list[ $i ] ) === $search_string ) {
638 return $i + 1;
639 }
640 }
641
642 return 0;
643 }
644
645 /**
646 * Method to emulate MySQL LOG() function.
647 *
648 * Used with one argument, it returns the natural logarithm of X.
649 * <code>
650 * LOG(X)
651 * </code>
652 * Used with two arguments, it returns the natural logarithm of X base B.
653 * <code>
654 * LOG(B, X)
655 * </code>
656 * In this case, it returns the value of log(X) / log(B).
657 *
658 * Used without an argument, it returns false. This returned value will be
659 * rewritten to 0, because SQLite doesn't understand true/false value.
660 *
661 * @return double|null
662 */
663 public function log() {
664 $num_args = func_num_args();
665 if ( 1 === $num_args ) {
666 $arg1 = func_get_arg( 0 );
667
668 return log( $arg1 );
669 }
670 if ( 2 === $num_args ) {
671 $arg1 = func_get_arg( 0 );
672 $arg2 = func_get_arg( 1 );
673
674 return log( $arg1 ) / log( $arg2 );
675 }
676 return null;
677 }
678
679 /**
680 * Method to emulate MySQL LEAST() function.
681 *
682 * This function rewrites the function name to SQLite compatible function name.
683 *
684 * @return mixed
685 */
686 public function least() {
687 $arg_list = func_get_args();
688
689 return min( $arg_list );
690 }
691
692 /**
693 * Method to emulate MySQL GREATEST() function.
694 *
695 * This function rewrites the function name to SQLite compatible function name.
696 *
697 * @return mixed
698 */
699 public function greatest() {
700 $arg_list = func_get_args();
701
702 return max( $arg_list );
703 }
704
705 /**
706 * Method to dummy out MySQL GET_LOCK() function.
707 *
708 * This function is meaningless in SQLite, so we do nothing.
709 *
710 * @param string $name Not used.
711 * @param integer $timeout Not used.
712 *
713 * @return string
714 */
715 public function get_lock( $name, $timeout ) {
716 return '1=1';
717 }
718
719 /**
720 * Method to dummy out MySQL RELEASE_LOCK() function.
721 *
722 * This function is meaningless in SQLite, so we do nothing.
723 *
724 * @param string $name Not used.
725 *
726 * @return string
727 */
728 public function release_lock( $name ) {
729 return '1=1';
730 }
731
732 /**
733 * Method to emulate MySQL UCASE() function.
734 *
735 * This is MySQL alias for upper() function. This function rewrites it
736 * to SQLite compatible name upper().
737 *
738 * @param string $content String to be converted to uppercase.
739 *
740 * @return string SQLite compatible function name.
741 */
742 public function ucase( $content ) {
743 return "upper($content)";
744 }
745
746 /**
747 * Method to emulate MySQL LCASE() function.
748 *
749 * This is MySQL alias for lower() function. This function rewrites it
750 * to SQLite compatible name lower().
751 *
752 * @param string $content String to be converted to lowercase.
753 *
754 * @return string SQLite compatible function name.
755 */
756 public function lcase( $content ) {
757 return "lower($content)";
758 }
759
760 /**
761 * Method to emulate MySQL UNHEX() function.
762 *
763 * For a string argument str, UNHEX(str) interprets each pair of characters
764 * in the argument as a hexadecimal number and converts it to the byte represented
765 * by the number. The return value is a binary string.
766 *
767 * @param string $number Number to be unhexed.
768 *
769 * @return string Binary string
770 */
771 public function unhex( $number ) {
772 return pack( 'H*', $number );
773 }
774
775 /**
776 * Method to emulate MySQL FROM_BASE64() function.
777 *
778 * Takes a base64-encoded string and returns the decoded result as a binary
779 * string. Returns NULL if the argument is NULL or is not a valid base64 string.
780 *
781 * @param string|null $str The base64-encoded string.
782 *
783 * @return string|null Decoded binary string, or NULL.
784 */
785 public function from_base64( $str ) {
786 if ( null === $str ) {
787 return null;
788 }
789 $decoded = base64_decode( $str, true );
790 if ( false === $decoded ) {
791 return null;
792 }
793 return $decoded;
794 }
795
796 /**
797 * Method to emulate MySQL TO_BASE64() function.
798 *
799 * Takes a string and returns a base64-encoded result.
800 * Returns NULL if the argument is NULL.
801 *
802 * @param string|null $str The string to encode.
803 *
804 * @return string|null Base64-encoded string, or NULL.
805 */
806 public function to_base64( $str ) {
807 if ( null === $str ) {
808 return null;
809 }
810 return base64_encode( $str );
811 }
812
813 /**
814 * Method to emulate MySQL INET_NTOA() function.
815 *
816 * This function gets 4 or 8 bytes integer and turn it into the network address.
817 *
818 * @param integer $num Long integer.
819 *
820 * @return string
821 */
822 public function inet_ntoa( $num ) {
823 return long2ip( $num );
824 }
825
826 /**
827 * Method to emulate MySQL INET_ATON() function.
828 *
829 * This function gets the network address and turns it into integer.
830 *
831 * @param string $addr Network address.
832 *
833 * @return int long integer
834 */
835 public function inet_aton( $addr ) {
836 return abs( (int) ip2long( $addr ) );
837 }
838
839 /**
840 * Method to emulate MySQL DATEDIFF() function.
841 *
842 * This function compares two dates value and returns the difference.
843 *
844 * @param string $start Start date.
845 * @param string $end End date.
846 *
847 * @return string
848 */
849 public function datediff( $start, $end ) {
850 $start_date = new DateTime( $start );
851 $end_date = new DateTime( $end );
852 $interval = $end_date->diff( $start_date, false );
853
854 return $interval->format( '%r%a' );
855 }
856
857 /**
858 * Method to emulate MySQL LOCATE() function.
859 *
860 * This function returns the position if $substr is found in $str. If not,
861 * it returns 0. If mbstring extension is loaded, mb_strpos() function is
862 * used.
863 *
864 * @param string $substr Needle.
865 * @param string $str Haystack.
866 * @param integer $pos Position.
867 *
868 * @return integer
869 */
870 public function locate( $substr, $str, $pos = 0 ) {
871 if ( ! extension_loaded( 'mbstring' ) ) {
872 $val = strpos( $str, $substr, $pos );
873 if ( false !== $val ) {
874 return $val + 1;
875 }
876 return 0;
877 }
878 $val = mb_strpos( $str, $substr, $pos );
879 if ( false !== $val ) {
880 return $val + 1;
881 }
882 return 0;
883 }
884
885 /**
886 * Method to return GMT date in the string format.
887 *
888 * @return string formatted GMT date 'dddd-mm-dd'
889 */
890 public function utc_date() {
891 return gmdate( 'Y-m-d', time() );
892 }
893
894 /**
895 * Method to return GMT time in the string format.
896 *
897 * @return string formatted GMT time '00:00:00'
898 */
899 public function utc_time() {
900 return gmdate( 'H:i:s', time() );
901 }
902
903 /**
904 * Method to return GMT time stamp in the string format.
905 *
906 * @return string formatted GMT timestamp 'yyyy-mm-dd 00:00:00'
907 */
908 public function utc_timestamp() {
909 return gmdate( 'Y-m-d H:i:s', time() );
910 }
911
912 /**
913 * Method to return MySQL version.
914 *
915 * This function only returns the current newest version number of MySQL,
916 * because it is meaningless for SQLite database.
917 *
918 * @return string representing the version number: major_version.minor_version
919 */
920 public function version() {
921 return '5.5';
922 }
923
924 /**
925 * Method to emulate MySQL REVERSE() function.
926 *
927 * Reverse UTF-8 text by code point, matching MySQL behavior.
928 *
929 * @param string|null $str The string to reverse.
930 *
931 * @return string|null reversed string, or NULL.
932 */
933 public function reverse( $str ) {
934 if ( null === $str ) {
935 return null;
936 }
937 if (
938 preg_match( '/[^\x00-\x7F]/', $str )
939 && preg_match_all( '/./us', $str, $matches )
940 ) {
941 return implode( '', array_reverse( $matches[0] ) );
942 }
943 return strrev( $str );
944 }
945
946 /**
947 * A helper to convert a LIKE pattern to a GLOB pattern for "LIKE BINARY" support.
948
949 * @TODO: Some of the MySQL string specifics described below are likely to
950 * affect also other patterns than just "LIKE BINARY". We should
951 * consider applying some of the conversions more broadly.
952 *
953 * @param string|null $pattern
954 * @return string|null
955 */
956 public function _helper_like_to_glob_pattern( $pattern ) {
957 if ( null === $pattern ) {
958 return null;
959 }
960
961 /*
962 * 1. Escape characters that have special meaning in GLOB patterns.
963 *
964 * We need to:
965 * 1. Escape "]" as "[]]" to avoid interpreting "[...]" as a character class.
966 * 2. Escape "*" as "[*]" (must be after 1 to avoid being escaped).
967 * 3. Escape "?" as "[?]" (must be after 1 to avoid being escaped).
968 */
969 $pattern = str_replace( ']', '[]]', $pattern );
970 $pattern = str_replace( '*', '[*]', $pattern );
971 $pattern = str_replace( '?', '[?]', $pattern );
972
973 /*
974 * 2. Convert LIKE wildcards to GLOB wildcards ("%" -> "*", "_" -> "?").
975 *
976 * We need to convert them only when they don't follow any backslashes,
977 * or when they follow an even number of backslashes (as "\\" is "\").
978 */
979 $pattern = preg_replace( '/(^|[^\\\\](?:\\\\{2})*)%/', '$1*', $pattern );
980 $pattern = preg_replace( '/(^|[^\\\\](?:\\\\{2})*)_/', '$1?', $pattern );
981
982 /*
983 * 3. Unescape LIKE escape sequences.
984 *
985 * While in MySQL LIKE patterns, a backslash is usually used to escape
986 * special characters ("%", "_", and "\"), it works with all characters.
987 *
988 * That is:
989 * SELECT '\\x' prints '\x', but LIKE '\\x' is equivalent to LIKE 'x'.
990 *
991 * This is true also for multi-byte characters:
992 * SELECT '\\©' prints '\©', but LIKE '\\©' is equivalent to LIKE '©'.
993 *
994 * Use "s" to include escaped newlines and omit "u" to preserve raw bytes.
995 */
996 $pattern = preg_replace( '/\\\\(.)/s', '$1', $pattern );
997
998 return $pattern;
999 }
1000 }
1001