PluginProbe
SQLite Database Integration / 2.2.18
SQLite Database Integration v2.2.18
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 / sqlite / class-wp-sqlite-pdo-user-defined-functions.php

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

837 lines 22.5 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 * @package wp-sqlite-integration
6 * @since 1.0.0
7 */
8
9 /**
10 * This class defines user defined functions(UDFs) for PDO library.
11 *
12 * These functions replace those used in the SQL statement with the PHP functions.
13 *
14 * Usage:
15 *
16 * <code>
17 * new WP_SQLite_PDO_User_Defined_Functions(ref_to_pdo_obj);
18 * </code>
19 *
20 * This automatically enables ref_to_pdo_obj to replace the function in the SQL statement
21 * to the ones defined here.
22 */
23 class WP_SQLite_PDO_User_Defined_Functions {
24
25 /**
26 * Registers the user defined functions for SQLite to a PDO instance.
27 * The functions are registered using PDO::sqliteCreateFunction().
28 *
29 * @param PDO|PDO\SQLite $pdo The PDO object.
30 */
31 public static function register_for( $pdo ): self {
32 $instance = new self();
33 foreach ( $instance->functions as $f => $t ) {
34 if ( $pdo instanceof PDO\SQLite ) {
35 $pdo->createFunction( $f, array( $instance, $t ) );
36 } else {
37 $pdo->sqliteCreateFunction( $f, array( $instance, $t ) );
38 }
39 }
40 return $instance;
41 }
42
43 /**
44 * Array to define MySQL function => function defined with PHP.
45 *
46 * Replaced functions must be public.
47 *
48 * @var array
49 */
50 private $functions = array(
51 'throw' => 'throw',
52 'month' => 'month',
53 'monthnum' => 'month',
54 'year' => 'year',
55 'day' => 'day',
56 'hour' => 'hour',
57 'minute' => 'minute',
58 'second' => 'second',
59 'week' => 'week',
60 'weekday' => 'weekday',
61 'dayofweek' => 'dayofweek',
62 'dayofmonth' => 'dayofmonth',
63 'unix_timestamp' => 'unix_timestamp',
64 'now' => 'now',
65 'md5' => 'md5',
66 'curdate' => 'curdate',
67 'rand' => 'rand',
68 'from_unixtime' => 'from_unixtime',
69 'localtime' => 'now',
70 'localtimestamp' => 'now',
71 'isnull' => 'isnull',
72 'if' => '_if',
73 'regexp' => 'regexp',
74 'field' => 'field',
75 'log' => 'log',
76 'least' => 'least',
77 'greatest' => 'greatest',
78 'get_lock' => 'get_lock',
79 'release_lock' => 'release_lock',
80 'ucase' => 'ucase',
81 'lcase' => 'lcase',
82 'unhex' => 'unhex',
83 'inet_ntoa' => 'inet_ntoa',
84 'inet_aton' => 'inet_aton',
85 'datediff' => 'datediff',
86 'locate' => 'locate',
87 'utc_date' => 'utc_date',
88 'utc_time' => 'utc_time',
89 'utc_timestamp' => 'utc_timestamp',
90 'version' => 'version',
91
92 // Internal helper functions.
93 '_helper_like_to_glob_pattern' => '_helper_like_to_glob_pattern',
94 );
95
96 /**
97 * A helper function to throw an error from SQLite expressions.
98 *
99 * @param string $message The error message.
100 *
101 * @throws Exception The error message.
102 * @return void
103 */
104 public function throw( $message ): void {
105 throw new Exception( $message );
106 }
107
108 /**
109 * Method to return the unix timestamp.
110 *
111 * Used without an argument, it returns PHP time() function (total seconds passed
112 * from '1970-01-01 00:00:00' GMT). Used with the argument, it changes the value
113 * to the timestamp.
114 *
115 * @param string $field Representing the date formatted as '0000-00-00 00:00:00'.
116 *
117 * @return number of unsigned integer
118 */
119 public function unix_timestamp( $field = null ) {
120 return is_null( $field ) ? time() : strtotime( $field );
121 }
122
123 /**
124 * Method to emulate MySQL FROM_UNIXTIME() function.
125 *
126 * @param int $field The unix timestamp.
127 * @param string $format Indicate the way of formatting(optional).
128 *
129 * @return string
130 */
131 public function from_unixtime( $field, $format = null ) {
132 // Convert to ISO time.
133 $date = gmdate( 'Y-m-d H:i:s', $field );
134
135 return is_null( $format ) ? $date : $this->dateformat( $date, $format );
136 }
137
138 /**
139 * Method to emulate MySQL NOW() function.
140 *
141 * @return string representing current time formatted as '0000-00-00 00:00:00'.
142 */
143 public function now() {
144 return gmdate( 'Y-m-d H:i:s' );
145 }
146
147 /**
148 * Method to emulate MySQL CURDATE() function.
149 *
150 * @return string representing current time formatted as '0000-00-00'.
151 */
152 public function curdate() {
153 return gmdate( 'Y-m-d' );
154 }
155
156 /**
157 * Method to emulate MySQL MD5() function.
158 *
159 * @param string $field The string to be hashed.
160 *
161 * @return string of the md5 hash value of the argument.
162 */
163 public function md5( $field ) {
164 return md5( $field );
165 }
166
167 /**
168 * Method to emulate MySQL RAND() function.
169 *
170 * SQLite does have a random generator, but it is called RANDOM() and returns random
171 * number between -9223372036854775808 and +9223372036854775807. So we substitute it
172 * with PHP random generator.
173 *
174 * This function uses mt_rand() which is four times faster than rand() and returns
175 * the random number between 0 and 1.
176 *
177 * @return int
178 */
179 public function rand() {
180 return mt_rand( 0, 1 );
181 }
182
183 /**
184 * Method to emulate MySQL DATEFORMAT() function.
185 *
186 * @param string $date Formatted as '0000-00-00' or datetime as '0000-00-00 00:00:00'.
187 * @param string $format The string format.
188 *
189 * @return string formatted according to $format
190 */
191 public function dateformat( $date, $format ) {
192 $mysql_php_date_formats = array(
193 '%a' => 'D',
194 '%b' => 'M',
195 '%c' => 'n',
196 '%D' => 'jS',
197 '%d' => 'd',
198 '%e' => 'j',
199 '%H' => 'H',
200 '%h' => 'h',
201 '%I' => 'h',
202 '%i' => 'i',
203 '%j' => 'z',
204 '%k' => 'G',
205 '%l' => 'g',
206 '%M' => 'F',
207 '%m' => 'm',
208 '%p' => 'A',
209 '%r' => 'h:i:s A',
210 '%S' => 's',
211 '%s' => 's',
212 '%T' => 'H:i:s',
213 '%U' => 'W',
214 '%u' => 'W',
215 '%V' => 'W',
216 '%v' => 'W',
217 '%W' => 'l',
218 '%w' => 'w',
219 '%X' => 'Y',
220 '%x' => 'o',
221 '%Y' => 'Y',
222 '%y' => 'y',
223 );
224
225 $time = strtotime( $date );
226 $format = strtr( $format, $mysql_php_date_formats );
227
228 return gmdate( $format, $time );
229 }
230
231 /**
232 * Method to extract the month value from the date.
233 *
234 * @param string $field Representing the date formatted as 0000-00-00.
235 *
236 * @return string Representing the number of the month between 1 and 12.
237 */
238 public function month( $field ) {
239 /*
240 * From https://www.php.net/manual/en/datetime.format.php:
241 *
242 * n - Numeric representation of a month, without leading zeros.
243 * 1 through 12
244 */
245 return intval( gmdate( 'n', strtotime( $field ) ) );
246 }
247
248 /**
249 * Method to extract the year value from the date.
250 *
251 * @param string $field Representing the date formatted as 0000-00-00.
252 *
253 * @return string Representing the number of the year.
254 */
255 public function year( $field ) {
256 /*
257 * From https://www.php.net/manual/en/datetime.format.php:
258 *
259 * Y - A full numeric representation of a year, 4 digits.
260 */
261 return intval( gmdate( 'Y', strtotime( $field ) ) );
262 }
263
264 /**
265 * Method to extract the day value from the date.
266 *
267 * @param string $field Representing the date formatted as 0000-00-00.
268 *
269 * @return string Representing the number of the day of the month from 1 and 31.
270 */
271 public function day( $field ) {
272 /*
273 * From https://www.php.net/manual/en/datetime.format.php:
274 *
275 * j - Day of the month without leading zeros.
276 * 1 to 31.
277 */
278 return intval( gmdate( 'j', strtotime( $field ) ) );
279 }
280
281 /**
282 * Method to emulate MySQL SECOND() function.
283 *
284 * @see https://www.php.net/manual/en/datetime.format.php
285 *
286 * @param string $field Representing the time formatted as '00:00:00'.
287 *
288 * @return number Unsigned integer
289 */
290 public function second( $field ) {
291 /*
292 * From https://www.php.net/manual/en/datetime.format.php:
293 *
294 * s - Seconds, with leading zeros (00 to 59)
295 */
296 return intval( gmdate( 's', strtotime( $field ) ) );
297 }
298
299 /**
300 * Method to emulate MySQL MINUTE() function.
301 *
302 * @param string $field Representing the time formatted as '00:00:00'.
303 *
304 * @return int
305 */
306 public function minute( $field ) {
307 /*
308 * From https://www.php.net/manual/en/datetime.format.php:
309 *
310 * i - Minutes with leading zeros.
311 * 00 to 59.
312 */
313 return intval( gmdate( 'i', strtotime( $field ) ) );
314 }
315
316 /**
317 * Method to emulate MySQL HOUR() function.
318 *
319 * Returns the hour for time, in 24-hour format, from 0 to 23.
320 * Importantly, midnight is 0, not 24.
321 *
322 * @param string $time Representing the time formatted, like '14:08:12'.
323 *
324 * @return int
325 */
326 public function hour( $time ) {
327 /*
328 * From https://www.php.net/manual/en/datetime.format.php:
329 *
330 * H 24-hour format of an hour with leading zeros.
331 * 00 through 23.
332 */
333 return intval( gmdate( 'H', strtotime( $time ) ) );
334 }
335
336 /**
337 * Covers MySQL WEEK() function.
338 *
339 * Always assumes $mode = 1.
340 *
341 * @TODO: Support other modes.
342 *
343 * From https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_week:
344 *
345 * > Returns the week number for date. The two-argument form of WEEK()
346 * > enables you to specify whether the week starts on Sunday or Monday
347 * > and whether the return value should be in the range from 0 to 53
348 * > or from 1 to 53. If the mode argument is omitted, the value of the
349 * > default_week_format system variable is used.
350 * >
351 * > The following table describes how the mode argument works:
352 * >
353 * > Mode First day of week Range Week 1 is the first week …
354 * > 0 Sunday 0-53 with a Sunday in this year
355 * > 1 Monday 0-53 with 4 or more days this year
356 * > 2 Sunday 1-53 with a Sunday in this year
357 * > 3 Monday 1-53 with 4 or more days this year
358 * > 4 Sunday 0-53 with 4 or more days this year
359 * > 5 Monday 0-53 with a Monday in this year
360 * > 6 Sunday 1-53 with 4 or more days this year
361 * > 7 Monday 1-53 with a Monday in this year
362 *
363 * @param string $field Representing the date.
364 * @param int $mode The mode argument.
365 */
366 public function week( $field, $mode ) {
367 /*
368 * From https://www.php.net/manual/en/datetime.format.php:
369 *
370 * W - ISO-8601 week number of year, weeks starting on Monday.
371 * Example: 42 (the 42nd week in the year)
372 *
373 * Week 1 is the first week with a Thursday in it.
374 */
375 return intval( gmdate( 'W', strtotime( $field ) ) );
376 }
377
378 /**
379 * Simulates WEEKDAY() function in MySQL.
380 *
381 * Returns the day of the week as an integer.
382 * The days of the week are numbered 0 to 6:
383 * * 0 for Monday
384 * * 1 for Tuesday
385 * * 2 for Wednesday
386 * * 3 for Thursday
387 * * 4 for Friday
388 * * 5 for Saturday
389 * * 6 for Sunday
390 *
391 * @param string $field Representing the date.
392 *
393 * @return int
394 */
395 public function weekday( $field ) {
396 /*
397 * date('N') returns 1 (for Monday) through 7 (for Sunday)
398 * That's one more than MySQL.
399 * Let's subtract one to make it compatible.
400 */
401 return intval( gmdate( 'N', strtotime( $field ) ) ) - 1;
402 }
403
404 /**
405 * Method to emulate MySQL DAYOFMONTH() function.
406 *
407 * @see https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_dayofmonth
408 *
409 * @param string $field Representing the date.
410 *
411 * @return int Returns the day of the month for date as a number in the range 1 to 31.
412 */
413 public function dayofmonth( $field ) {
414 return intval( gmdate( 'j', strtotime( $field ) ) );
415 }
416
417 /**
418 * Method to emulate MySQL DAYOFWEEK() function.
419 *
420 * > Returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday).
421 * > These index values correspond to the ODBC standard. Returns NULL if date is NULL.
422 *
423 * @param string $field Representing the date.
424 *
425 * @return int Returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday).
426 */
427 public function dayofweek( $field ) {
428 /**
429 * From https://www.php.net/manual/en/datetime.format.php:
430 *
431 * `w` – Numeric representation of the day of the week
432 * 0 (for Sunday) through 6 (for Saturday)
433 */
434 return intval( gmdate( 'w', strtotime( $field ) ) ) + 1;
435 }
436
437 /**
438 * Method to emulate MySQL DATE() function.
439 *
440 * @see https://www.php.net/manual/en/datetime.format.php
441 *
442 * @param string $date formatted as unix time.
443 *
444 * @return string formatted as '0000-00-00'.
445 */
446 public function date( $date ) {
447 return gmdate( 'Y-m-d', strtotime( $date ) );
448 }
449
450 /**
451 * Method to emulate MySQL ISNULL() function.
452 *
453 * This function returns true if the argument is null, and true if not.
454 *
455 * @param mixed $field The field to be tested.
456 *
457 * @return boolean
458 */
459 public function isnull( $field ) {
460 return is_null( $field );
461 }
462
463 /**
464 * Method to emulate MySQL IF() function.
465 *
466 * As 'IF' is a reserved word for PHP, function name must be changed.
467 *
468 * @param mixed $expression The statement to be evaluated as true or false.
469 * @param mixed $truthy Statement or value returned if $expression is true.
470 * @param mixed $falsy Statement or value returned if $expression is false.
471 *
472 * @return mixed
473 */
474 public function _if( $expression, $truthy, $falsy ) {
475 return ( true === $expression ) ? $truthy : $falsy;
476 }
477
478 /**
479 * Method to emulate MySQL REGEXP() function.
480 *
481 * @param string $pattern Regular expression to match.
482 * @param string $field Haystack.
483 *
484 * @return integer 1 if matched, 0 if not matched.
485 */
486 public function regexp( $pattern, $field ) {
487 /*
488 * If the original query says REGEXP BINARY
489 * the comparison is byte-by-byte and letter casing now
490 * matters since lower- and upper-case letters have different
491 * byte codes.
492 *
493 * The REGEXP function can't be easily made to accept two
494 * parameters, so we'll have to use a hack to get around this.
495 *
496 * If the first character of the pattern is a null byte, we'll
497 * remove it and make the comparison case-sensitive. This should
498 * be reasonably safe since PHP does not allow null bytes in
499 * regular expressions anyway.
500 */
501 if ( "\x00" === $pattern[0] ) {
502 $pattern = substr( $pattern, 1 );
503 $flags = '';
504 } else {
505 // Otherwise, the search is case-insensitive.
506 $flags = 'i';
507 }
508 $pattern = str_replace( '/', '\/', $pattern );
509 $pattern = '/' . $pattern . '/' . $flags;
510
511 return preg_match( $pattern, $field );
512 }
513
514 /**
515 * Method to emulate MySQL FIELD() function.
516 *
517 * This function gets the list argument and compares the first item to all the others.
518 * If the same value is found, it returns the position of that value. If not, it
519 * returns 0.
520 *
521 * @return int
522 */
523 public function field() {
524 $num_args = func_num_args();
525 if ( $num_args < 2 || is_null( func_get_arg( 0 ) ) ) {
526 return 0;
527 }
528 $arg_list = func_get_args();
529 $search_string = strtolower( array_shift( $arg_list ) );
530
531 for ( $i = 0; $i < $num_args - 1; $i++ ) {
532 if ( strtolower( $arg_list[ $i ] ) === $search_string ) {
533 return $i + 1;
534 }
535 }
536
537 return 0;
538 }
539
540 /**
541 * Method to emulate MySQL LOG() function.
542 *
543 * Used with one argument, it returns the natural logarithm of X.
544 * <code>
545 * LOG(X)
546 * </code>
547 * Used with two arguments, it returns the natural logarithm of X base B.
548 * <code>
549 * LOG(B, X)
550 * </code>
551 * In this case, it returns the value of log(X) / log(B).
552 *
553 * Used without an argument, it returns false. This returned value will be
554 * rewritten to 0, because SQLite doesn't understand true/false value.
555 *
556 * @return double|null
557 */
558 public function log() {
559 $num_args = func_num_args();
560 if ( 1 === $num_args ) {
561 $arg1 = func_get_arg( 0 );
562
563 return log( $arg1 );
564 }
565 if ( 2 === $num_args ) {
566 $arg1 = func_get_arg( 0 );
567 $arg2 = func_get_arg( 1 );
568
569 return log( $arg1 ) / log( $arg2 );
570 }
571 return null;
572 }
573
574 /**
575 * Method to emulate MySQL LEAST() function.
576 *
577 * This function rewrites the function name to SQLite compatible function name.
578 *
579 * @return mixed
580 */
581 public function least() {
582 $arg_list = func_get_args();
583
584 return min( $arg_list );
585 }
586
587 /**
588 * Method to emulate MySQL GREATEST() function.
589 *
590 * This function rewrites the function name to SQLite compatible function name.
591 *
592 * @return mixed
593 */
594 public function greatest() {
595 $arg_list = func_get_args();
596
597 return max( $arg_list );
598 }
599
600 /**
601 * Method to dummy out MySQL GET_LOCK() function.
602 *
603 * This function is meaningless in SQLite, so we do nothing.
604 *
605 * @param string $name Not used.
606 * @param integer $timeout Not used.
607 *
608 * @return string
609 */
610 public function get_lock( $name, $timeout ) {
611 return '1=1';
612 }
613
614 /**
615 * Method to dummy out MySQL RELEASE_LOCK() function.
616 *
617 * This function is meaningless in SQLite, so we do nothing.
618 *
619 * @param string $name Not used.
620 *
621 * @return string
622 */
623 public function release_lock( $name ) {
624 return '1=1';
625 }
626
627 /**
628 * Method to emulate MySQL UCASE() function.
629 *
630 * This is MySQL alias for upper() function. This function rewrites it
631 * to SQLite compatible name upper().
632 *
633 * @param string $content String to be converted to uppercase.
634 *
635 * @return string SQLite compatible function name.
636 */
637 public function ucase( $content ) {
638 return "upper($content)";
639 }
640
641 /**
642 * Method to emulate MySQL LCASE() function.
643 *
644 * This is MySQL alias for lower() function. This function rewrites it
645 * to SQLite compatible name lower().
646 *
647 * @param string $content String to be converted to lowercase.
648 *
649 * @return string SQLite compatible function name.
650 */
651 public function lcase( $content ) {
652 return "lower($content)";
653 }
654
655 /**
656 * Method to emulate MySQL UNHEX() function.
657 *
658 * For a string argument str, UNHEX(str) interprets each pair of characters
659 * in the argument as a hexadecimal number and converts it to the byte represented
660 * by the number. The return value is a binary string.
661 *
662 * @param string $number Number to be unhexed.
663 *
664 * @return string Binary string
665 */
666 public function unhex( $number ) {
667 return pack( 'H*', $number );
668 }
669
670 /**
671 * Method to emulate MySQL INET_NTOA() function.
672 *
673 * This function gets 4 or 8 bytes integer and turn it into the network address.
674 *
675 * @param integer $num Long integer.
676 *
677 * @return string
678 */
679 public function inet_ntoa( $num ) {
680 return long2ip( $num );
681 }
682
683 /**
684 * Method to emulate MySQL INET_ATON() function.
685 *
686 * This function gets the network address and turns it into integer.
687 *
688 * @param string $addr Network address.
689 *
690 * @return int long integer
691 */
692 public function inet_aton( $addr ) {
693 return absint( ip2long( $addr ) );
694 }
695
696 /**
697 * Method to emulate MySQL DATEDIFF() function.
698 *
699 * This function compares two dates value and returns the difference.
700 *
701 * @param string $start Start date.
702 * @param string $end End date.
703 *
704 * @return string
705 */
706 public function datediff( $start, $end ) {
707 $start_date = new DateTime( $start );
708 $end_date = new DateTime( $end );
709 $interval = $end_date->diff( $start_date, false );
710
711 return $interval->format( '%r%a' );
712 }
713
714 /**
715 * Method to emulate MySQL LOCATE() function.
716 *
717 * This function returns the position if $substr is found in $str. If not,
718 * it returns 0. If mbstring extension is loaded, mb_strpos() function is
719 * used.
720 *
721 * @param string $substr Needle.
722 * @param string $str Haystack.
723 * @param integer $pos Position.
724 *
725 * @return integer
726 */
727 public function locate( $substr, $str, $pos = 0 ) {
728 if ( ! extension_loaded( 'mbstring' ) ) {
729 $val = strpos( $str, $substr, $pos );
730 if ( false !== $val ) {
731 return $val + 1;
732 }
733 return 0;
734 }
735 $val = mb_strpos( $str, $substr, $pos );
736 if ( false !== $val ) {
737 return $val + 1;
738 }
739 return 0;
740 }
741
742 /**
743 * Method to return GMT date in the string format.
744 *
745 * @return string formatted GMT date 'dddd-mm-dd'
746 */
747 public function utc_date() {
748 return gmdate( 'Y-m-d', time() );
749 }
750
751 /**
752 * Method to return GMT time in the string format.
753 *
754 * @return string formatted GMT time '00:00:00'
755 */
756 public function utc_time() {
757 return gmdate( 'H:i:s', time() );
758 }
759
760 /**
761 * Method to return GMT time stamp in the string format.
762 *
763 * @return string formatted GMT timestamp 'yyyy-mm-dd 00:00:00'
764 */
765 public function utc_timestamp() {
766 return gmdate( 'Y-m-d H:i:s', time() );
767 }
768
769 /**
770 * Method to return MySQL version.
771 *
772 * This function only returns the current newest version number of MySQL,
773 * because it is meaningless for SQLite database.
774 *
775 * @return string representing the version number: major_version.minor_version
776 */
777 public function version() {
778 return '5.5';
779 }
780
781 /**
782 * A helper to covert LIKE pattern to a GLOB pattern for "LIKE BINARY" support.
783
784 * @TODO: Some of the MySQL string specifics described below are likely to
785 * affect also other patterns than just "LIKE BINARY". We should
786 * consider applying some of the conversions more broadly.
787 *
788 * @param string $pattern
789 * @return string
790 */
791 public function _helper_like_to_glob_pattern( $pattern ) {
792 if ( null === $pattern ) {
793 return null;
794 }
795
796 /*
797 * 1. Escape characters that have special meaning in GLOB patterns.
798 *
799 * We need to:
800 * 1. Escape "]" as "[]]" to avoid interpreting "[...]" as a character class.
801 * 2. Escape "*" as "[*]" (must be after 1 to avoid being escaped).
802 * 3. Escape "?" as "[?]" (must be after 1 to avoid being escaped).
803 */
804 $pattern = str_replace( ']', '[]]', $pattern );
805 $pattern = str_replace( '*', '[*]', $pattern );
806 $pattern = str_replace( '?', '[?]', $pattern );
807
808 /*
809 * 2. Convert LIKE wildcards to GLOB wildcards ("%" -> "*", "_" -> "?").
810 *
811 * We need to convert them only when they don't follow any backslashes,
812 * or when they follow an even number of backslashes (as "\\" is "\").
813 */
814 $pattern = preg_replace( '/(^|[^\\\\](?:\\\\{2})*)%/', '$1*', $pattern );
815 $pattern = preg_replace( '/(^|[^\\\\](?:\\\\{2})*)_/', '$1?', $pattern );
816
817 /*
818 * 3. Unescape LIKE escape sequences.
819 *
820 * While in MySQL LIKE patterns, a backslash is usually used to escape
821 * special characters ("%", "_", and "\"), it works with all characters.
822 *
823 * That is:
824 * SELECT '\\x' prints '\x', but LIKE '\\x' is equivalent to LIKE 'x'.
825 *
826 * This is true also for multi-byte characters:
827 * SELECT '\\©' prints '\©', but LIKE '\\©' is equivalent to LIKE '©'.
828 *
829 * However, the multi-byte behavior is likely to depend on the charset.
830 * For now, we'll assume UTF-8 and thus the "u" modifier for the regex.
831 */
832 $pattern = preg_replace( '/\\\\(.)/u', '$1', $pattern );
833
834 return $pattern;
835 }
836 }
837