PluginProbe
SQLite Database Integration / 3.1.0
SQLite Database Integration v3.1.0
3.1.0 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 All 33 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.1.0, at wp-includes/database/sqlite/class-wp-sqlite-pdo-user-defined-functions.php

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