Make WordPress Core

source: tags/5.4.1/src/wp-includes/functions.php

Last change on this file was 47398, checked in by johnbillion, 4 years ago

Docs: Miscellaneous docs fixes and improvements.

See #48303.

  • Property svn:eol-style set to native
File size: 225.9 KB
Line 
1<?php
2/**
3 * Main WordPress API
4 *
5 * @package WordPress
6 */
7
8require ABSPATH . WPINC . '/option.php';
9
10/**
11 * Convert given MySQL date string into a different format.
12 *
13 * `$format` should be a PHP date format string.
14 * 'U' and 'G' formats will return a sum of timestamp with timezone offset.
15 * `$date` is expected to be local time in MySQL format (`Y-m-d H:i:s`).
16 *
17 * Historically UTC time could be passed to the function to produce Unix timestamp.
18 *
19 * If `$translate` is true then the given date and format string will
20 * be passed to `wp_date()` for translation.
21 *
22 * @since 0.71
23 *
24 * @param string $format    Format of the date to return.
25 * @param string $date      Date string to convert.
26 * @param bool   $translate Whether the return date should be translated. Default true.
27 * @return string|int|false Formatted date string or sum of Unix timestamp and timezone offset.
28 *                          False on failure.
29 */
30function mysql2date( $format, $date, $translate = true ) {
31        if ( empty( $date ) ) {
32                return false;
33        }
34
35        $datetime = date_create( $date, wp_timezone() );
36
37        if ( false === $datetime ) {
38                return false;
39        }
40
41        // Returns a sum of timestamp with timezone offset. Ideally should never be used.
42        if ( 'G' === $format || 'U' === $format ) {
43                return $datetime->getTimestamp() + $datetime->getOffset();
44        }
45
46        if ( $translate ) {
47                return wp_date( $format, $datetime->getTimestamp() );
48        }
49
50        return $datetime->format( $format );
51}
52
53/**
54 * Retrieves the current time based on specified type.
55 *
56 * The 'mysql' type will return the time in the format for MySQL DATETIME field.
57 * The 'timestamp' type will return the current timestamp or a sum of timestamp
58 * and timezone offset, depending on `$gmt`.
59 * Other strings will be interpreted as PHP date formats (e.g. 'Y-m-d').
60 *
61 * If $gmt is set to either '1' or 'true', then both types will use GMT time.
62 * if $gmt is false, the output is adjusted with the GMT offset in the WordPress option.
63 *
64 * @since 1.0.0
65 *
66 * @param string   $type Type of time to retrieve. Accepts 'mysql', 'timestamp',
67 *                       or PHP date format string (e.g. 'Y-m-d').
68 * @param int|bool $gmt  Optional. Whether to use GMT timezone. Default false.
69 * @return int|string Integer if $type is 'timestamp', string otherwise.
70 */
71function current_time( $type, $gmt = 0 ) {
72        // Don't use non-GMT timestamp, unless you know the difference and really need to.
73        if ( 'timestamp' === $type || 'U' === $type ) {
74                return $gmt ? time() : time() + (int) ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
75        }
76
77        if ( 'mysql' === $type ) {
78                $type = 'Y-m-d H:i:s';
79        }
80
81        $timezone = $gmt ? new DateTimeZone( 'UTC' ) : wp_timezone();
82        $datetime = new DateTime( 'now', $timezone );
83
84        return $datetime->format( $type );
85}
86
87/**
88 * Retrieves the current time as an object with the timezone from settings.
89 *
90 * @since 5.3.0
91 *
92 * @return DateTimeImmutable Date and time object.
93 */
94function current_datetime() {
95        return new DateTimeImmutable( 'now', wp_timezone() );
96}
97
98/**
99 * Retrieves the timezone from site settings as a string.
100 *
101 * Uses the `timezone_string` option to get a proper timezone if available,
102 * otherwise falls back to an offset.
103 *
104 * @since 5.3.0
105 *
106 * @return string PHP timezone string or a ±HH:MM offset.
107 */
108function wp_timezone_string() {
109        $timezone_string = get_option( 'timezone_string' );
110
111        if ( $timezone_string ) {
112                return $timezone_string;
113        }
114
115        $offset  = (float) get_option( 'gmt_offset' );
116        $hours   = (int) $offset;
117        $minutes = ( $offset - $hours );
118
119        $sign      = ( $offset < 0 ) ? '-' : '+';
120        $abs_hour  = abs( $hours );
121        $abs_mins  = abs( $minutes * 60 );
122        $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins );
123
124        return $tz_offset;
125}
126
127/**
128 * Retrieves the timezone from site settings as a `DateTimeZone` object.
129 *
130 * Timezone can be based on a PHP timezone string or a ±HH:MM offset.
131 *
132 * @since 5.3.0
133 *
134 * @return DateTimeZone Timezone object.
135 */
136function wp_timezone() {
137        return new DateTimeZone( wp_timezone_string() );
138}
139
140/**
141 * Retrieves the date in localized format, based on a sum of Unix timestamp and
142 * timezone offset in seconds.
143 *
144 * If the locale specifies the locale month and weekday, then the locale will
145 * take over the format for the date. If it isn't, then the date format string
146 * will be used instead.
147 *
148 * Note that due to the way WP typically generates a sum of timestamp and offset
149 * with `strtotime()`, it implies offset added at a _current_ time, not at the time
150 * the timestamp represents. Storing such timestamps or calculating them differently
151 * will lead to invalid output.
152 *
153 * @since 0.71
154 * @since 5.3.0 Converted into a wrapper for wp_date().
155 *
156 * @global WP_Locale $wp_locale WordPress date and time locale object.
157 *
158 * @param string   $format                Format to display the date.
159 * @param int|bool $timestamp_with_offset Optional. A sum of Unix timestamp and timezone offset
160 *                                        in seconds. Default false.
161 * @param bool     $gmt                   Optional. Whether to use GMT timezone. Only applies
162 *                                        if timestamp is not provided. Default false.
163 * @return string The date, translated if locale specifies it.
164 */
165function date_i18n( $format, $timestamp_with_offset = false, $gmt = false ) {
166        $timestamp = $timestamp_with_offset;
167
168        // If timestamp is omitted it should be current time (summed with offset, unless `$gmt` is true).
169        if ( ! is_numeric( $timestamp ) ) {
170                $timestamp = current_time( 'timestamp', $gmt );
171        }
172
173        /*
174         * This is a legacy implementation quirk that the returned timestamp is also with offset.
175         * Ideally this function should never be used to produce a timestamp.
176         */
177        if ( 'U' === $format ) {
178                $date = $timestamp;
179        } elseif ( $gmt && false === $timestamp_with_offset ) { // Current time in UTC.
180                $date = wp_date( $format, null, new DateTimeZone( 'UTC' ) );
181        } elseif ( false === $timestamp_with_offset ) { // Current time in site's timezone.
182                $date = wp_date( $format );
183        } else {
184                /*
185                 * Timestamp with offset is typically produced by a UTC `strtotime()` call on an input without timezone.
186                 * This is the best attempt to reverse that operation into a local time to use.
187                 */
188                $local_time = gmdate( 'Y-m-d H:i:s', $timestamp );
189                $timezone   = wp_timezone();
190                $datetime   = date_create( $local_time, $timezone );
191                $date       = wp_date( $format, $datetime->getTimestamp(), $timezone );
192        }
193
194        /**
195         * Filters the date formatted based on the locale.
196         *
197         * @since 2.8.0
198         *
199         * @param string $date      Formatted date string.
200         * @param string $format    Format to display the date.
201         * @param int    $timestamp A sum of Unix timestamp and timezone offset in seconds.
202         *                          Might be without offset if input omitted timestamp but requested GMT.
203         * @param bool   $gmt       Whether to use GMT timezone. Only applies if timestamp was not provided.
204         *                          Default false.
205         */
206        $date = apply_filters( 'date_i18n', $date, $format, $timestamp, $gmt );
207
208        return $date;
209}
210
211/**
212 * Retrieves the date, in localized format.
213 *
214 * This is a newer function, intended to replace `date_i18n()` without legacy quirks in it.
215 *
216 * Note that, unlike `date_i18n()`, this function accepts a true Unix timestamp, not summed
217 * with timezone offset.
218 *
219 * @since 5.3.0
220 *
221 * @param string       $format    PHP date format.
222 * @param int          $timestamp Optional. Unix timestamp. Defaults to current time.
223 * @param DateTimeZone $timezone  Optional. Timezone to output result in. Defaults to timezone
224 *                                from site settings.
225 * @return string|false The date, translated if locale specifies it. False on invalid timestamp input.
226 */
227function wp_date( $format, $timestamp = null, $timezone = null ) {
228        global $wp_locale;
229
230        if ( null === $timestamp ) {
231                $timestamp = time();
232        } elseif ( ! is_numeric( $timestamp ) ) {
233                return false;
234        }
235
236        if ( ! $timezone ) {
237                $timezone = wp_timezone();
238        }
239
240        $datetime = date_create( '@' . $timestamp );
241        $datetime->setTimezone( $timezone );
242
243        if ( empty( $wp_locale->month ) || empty( $wp_locale->weekday ) ) {
244                $date = $datetime->format( $format );
245        } else {
246                // We need to unpack shorthand `r` format because it has parts that might be localized.
247                $format = preg_replace( '/(?<!\\\\)r/', DATE_RFC2822, $format );
248
249                $new_format    = '';
250                $format_length = strlen( $format );
251                $month         = $wp_locale->get_month( $datetime->format( 'm' ) );
252                $weekday       = $wp_locale->get_weekday( $datetime->format( 'w' ) );
253
254                for ( $i = 0; $i < $format_length; $i ++ ) {
255                        switch ( $format[ $i ] ) {
256                                case 'D':
257                                        $new_format .= addcslashes( $wp_locale->get_weekday_abbrev( $weekday ), '\\A..Za..z' );
258                                        break;
259                                case 'F':
260                                        $new_format .= addcslashes( $month, '\\A..Za..z' );
261                                        break;
262                                case 'l':
263                                        $new_format .= addcslashes( $weekday, '\\A..Za..z' );
264                                        break;
265                                case 'M':
266                                        $new_format .= addcslashes( $wp_locale->get_month_abbrev( $month ), '\\A..Za..z' );
267                                        break;
268                                case 'a':
269                                        $new_format .= addcslashes( $wp_locale->get_meridiem( $datetime->format( 'a' ) ), '\\A..Za..z' );
270                                        break;
271                                case 'A':
272                                        $new_format .= addcslashes( $wp_locale->get_meridiem( $datetime->format( 'A' ) ), '\\A..Za..z' );
273                                        break;
274                                case '\\':
275                                        $new_format .= $format[ $i ];
276
277                                        // If character follows a slash, we add it without translating.
278                                        if ( $i < $format_length ) {
279                                                $new_format .= $format[ ++$i ];
280                                        }
281                                        break;
282                                default:
283                                        $new_format .= $format[ $i ];
284                                        break;
285                        }
286                }
287
288                $date = $datetime->format( $new_format );
289                $date = wp_maybe_decline_date( $date, $format );
290        }
291
292        /**
293         * Filters the date formatted based on the locale.
294         *
295         * @since 5.3.0
296         *
297         * @param string       $date      Formatted date string.
298         * @param string       $format    Format to display the date.
299         * @param int          $timestamp Unix timestamp.
300         * @param DateTimeZone $timezone  Timezone.
301         *
302         */
303        $date = apply_filters( 'wp_date', $date, $format, $timestamp, $timezone );
304
305        return $date;
306}
307
308/**
309 * Determines if the date should be declined.
310 *
311 * If the locale specifies that month names require a genitive case in certain
312 * formats (like 'j F Y'), the month name will be replaced with a correct form.
313 *
314 * @since 4.4.0
315 * @since 5.4.0 The `$format` parameter was added.
316 *
317 * @global WP_Locale $wp_locale WordPress date and time locale object.
318 *
319 * @param string $date   Formatted date string.
320 * @param string $format Optional. Date format to check. Default empty string.
321 * @return string The date, declined if locale specifies it.
322 */
323function wp_maybe_decline_date( $date, $format = '' ) {
324        global $wp_locale;
325
326        // i18n functions are not available in SHORTINIT mode.
327        if ( ! function_exists( '_x' ) ) {
328                return $date;
329        }
330
331        /*
332         * translators: If months in your language require a genitive case,
333         * translate this to 'on'. Do not translate into your own language.
334         */
335        if ( 'on' === _x( 'off', 'decline months names: on or off' ) ) {
336
337                $months          = $wp_locale->month;
338                $months_genitive = $wp_locale->month_genitive;
339
340                /*
341                 * Match a format like 'j F Y' or 'j. F' (day of the month, followed by month name)
342                 * and decline the month.
343                 */
344                if ( $format ) {
345                        $decline = preg_match( '#[dj]\.? F#', $format );
346                } else {
347                        // If the format is not passed, try to guess it from the date string.
348                        $decline = preg_match( '#\b\d{1,2}\.? [^\d ]+\b#u', $date );
349                }
350
351                if ( $decline ) {
352                        foreach ( $months as $key => $month ) {
353                                $months[ $key ] = '# ' . preg_quote( $month, '#' ) . '\b#u';
354                        }
355
356                        foreach ( $months_genitive as $key => $month ) {
357                                $months_genitive[ $key ] = ' ' . $month;
358                        }
359
360                        $date = preg_replace( $months, $months_genitive, $date );
361                }
362
363                /*
364                 * Match a format like 'F jS' or 'F j' (month name, followed by day with an optional ordinal suffix)
365                 * and change it to declined 'j F'.
366                 */
367                if ( $format ) {
368                        $decline = preg_match( '#F [dj]#', $format );
369                } else {
370                        // If the format is not passed, try to guess it from the date string.
371                        $decline = preg_match( '#\b[^\d ]+ \d{1,2}(st|nd|rd|th)?\b#u', trim( $date ) );
372                }
373
374                if ( $decline ) {
375                        foreach ( $months as $key => $month ) {
376                                $months[ $key ] = '#\b' . preg_quote( $month, '#' ) . ' (\d{1,2})(st|nd|rd|th)?([-–]\d{1,2})?(st|nd|rd|th)?\b#u';
377                        }
378
379                        foreach ( $months_genitive as $key => $month ) {
380                                $months_genitive[ $key ] = '$1$3 ' . $month;
381                        }
382
383                        $date = preg_replace( $months, $months_genitive, $date );
384                }
385        }
386
387        // Used for locale-specific rules.
388        $locale = get_locale();
389
390        if ( 'ca' === $locale ) {
391                // " de abril| de agost| de octubre..." -> " d'abril| d'agost| d'octubre..."
392                $date = preg_replace( '# de ([ao])#i', " d'\\1", $date );
393        }
394
395        return $date;
396}
397
398/**
399 * Convert float number to format based on the locale.
400 *
401 * @since 2.3.0
402 *
403 * @global WP_Locale $wp_locale WordPress date and time locale object.
404 *
405 * @param float $number   The number to convert based on locale.
406 * @param int   $decimals Optional. Precision of the number of decimal places. Default 0.
407 * @return string Converted number in string format.
408 */
409function number_format_i18n( $number, $decimals = 0 ) {
410        global $wp_locale;
411
412        if ( isset( $wp_locale ) ) {
413                $formatted = number_format( $number, absint( $decimals ), $wp_locale->number_format['decimal_point'], $wp_locale->number_format['thousands_sep'] );
414        } else {
415                $formatted = number_format( $number, absint( $decimals ) );
416        }
417
418        /**
419         * Filters the number formatted based on the locale.
420         *
421         * @since 2.8.0
422         * @since 4.9.0 The `$number` and `$decimals` parameters were added.
423         *
424         * @param string $formatted Converted number in string format.
425         * @param float  $number    The number to convert based on locale.
426         * @param int    $decimals  Precision of the number of decimal places.
427         */
428        return apply_filters( 'number_format_i18n', $formatted, $number, $decimals );
429}
430
431/**
432 * Convert number of bytes largest unit bytes will fit into.
433 *
434 * It is easier to read 1 KB than 1024 bytes and 1 MB than 1048576 bytes. Converts
435 * number of bytes to human readable number by taking the number of that unit
436 * that the bytes will go into it. Supports TB value.
437 *
438 * Please note that integers in PHP are limited to 32 bits, unless they are on
439 * 64 bit architecture, then they have 64 bit size. If you need to place the
440 * larger size then what PHP integer type will hold, then use a string. It will
441 * be converted to a double, which should always have 64 bit length.
442 *
443 * Technically the correct unit names for powers of 1024 are KiB, MiB etc.
444 *
445 * @since 2.3.0
446 *
447 * @param int|string $bytes    Number of bytes. Note max integer size for integers.
448 * @param int        $decimals Optional. Precision of number of decimal places. Default 0.
449 * @return string|false False on failure. Number string on success.
450 */
451function size_format( $bytes, $decimals = 0 ) {
452        $quant = array(
453                'TB' => TB_IN_BYTES,
454                'GB' => GB_IN_BYTES,
455                'MB' => MB_IN_BYTES,
456                'KB' => KB_IN_BYTES,
457                'B'  => 1,
458        );
459
460        if ( 0 === $bytes ) {
461                return number_format_i18n( 0, $decimals ) . ' B';
462        }
463
464        foreach ( $quant as $unit => $mag ) {
465                if ( doubleval( $bytes ) >= $mag ) {
466                        return number_format_i18n( $bytes / $mag, $decimals ) . ' ' . $unit;
467                }
468        }
469
470        return false;
471}
472
473/**
474 * Convert a duration to human readable format.
475 *
476 * @since 5.1.0
477 *
478 * @param string $duration Duration will be in string format (HH:ii:ss) OR (ii:ss),
479 *                         with a possible prepended negative sign (-).
480 * @return string|false A human readable duration string, false on failure.
481 */
482function human_readable_duration( $duration = '' ) {
483        if ( ( empty( $duration ) || ! is_string( $duration ) ) ) {
484                return false;
485        }
486
487        $duration = trim( $duration );
488
489        // Remove prepended negative sign.
490        if ( '-' === substr( $duration, 0, 1 ) ) {
491                $duration = substr( $duration, 1 );
492        }
493
494        // Extract duration parts.
495        $duration_parts = array_reverse( explode( ':', $duration ) );
496        $duration_count = count( $duration_parts );
497
498        $hour   = null;
499        $minute = null;
500        $second = null;
501
502        if ( 3 === $duration_count ) {
503                // Validate HH:ii:ss duration format.
504                if ( ! ( (bool) preg_match( '/^([0-9]+):([0-5]?[0-9]):([0-5]?[0-9])$/', $duration ) ) ) {
505                        return false;
506                }
507                // Three parts: hours, minutes & seconds.
508                list( $second, $minute, $hour ) = $duration_parts;
509        } elseif ( 2 === $duration_count ) {
510                // Validate ii:ss duration format.
511                if ( ! ( (bool) preg_match( '/^([0-5]?[0-9]):([0-5]?[0-9])$/', $duration ) ) ) {
512                        return false;
513                }
514                // Two parts: minutes & seconds.
515                list( $second, $minute ) = $duration_parts;
516        } else {
517                return false;
518        }
519
520        $human_readable_duration = array();
521
522        // Add the hour part to the string.
523        if ( is_numeric( $hour ) ) {
524                /* translators: %s: Time duration in hour or hours. */
525                $human_readable_duration[] = sprintf( _n( '%s hour', '%s hours', $hour ), (int) $hour );
526        }
527
528        // Add the minute part to the string.
529        if ( is_numeric( $minute ) ) {
530                /* translators: %s: Time duration in minute or minutes. */
531                $human_readable_duration[] = sprintf( _n( '%s minute', '%s minutes', $minute ), (int) $minute );
532        }
533
534        // Add the second part to the string.
535        if ( is_numeric( $second ) ) {
536                /* translators: %s: Time duration in second or seconds. */
537                $human_readable_duration[] = sprintf( _n( '%s second', '%s seconds', $second ), (int) $second );
538        }
539
540        return implode( ', ', $human_readable_duration );
541}
542
543/**
544 * Get the week start and end from the datetime or date string from MySQL.
545 *
546 * @since 0.71
547 *
548 * @param string     $mysqlstring   Date or datetime field type from MySQL.
549 * @param int|string $start_of_week Optional. Start of the week as an integer. Default empty string.
550 * @return array Keys are 'start' and 'end'.
551 */
552function get_weekstartend( $mysqlstring, $start_of_week = '' ) {
553        // MySQL string year.
554        $my = substr( $mysqlstring, 0, 4 );
555
556        // MySQL string month.
557        $mm = substr( $mysqlstring, 8, 2 );
558
559        // MySQL string day.
560        $md = substr( $mysqlstring, 5, 2 );
561
562        // The timestamp for MySQL string day.
563        $day = mktime( 0, 0, 0, $md, $mm, $my );
564
565        // The day of the week from the timestamp.
566        $weekday = gmdate( 'w', $day );
567
568        if ( ! is_numeric( $start_of_week ) ) {
569                $start_of_week = get_option( 'start_of_week' );
570        }
571
572        if ( $weekday < $start_of_week ) {
573                $weekday += 7;
574        }
575
576        // The most recent week start day on or before $day.
577        $start = $day - DAY_IN_SECONDS * ( $weekday - $start_of_week );
578
579        // $start + 1 week - 1 second.
580        $end = $start + WEEK_IN_SECONDS - 1;
581        return compact( 'start', 'end' );
582}
583
584/**
585 * Unserialize value only if it was serialized.
586 *
587 * @since 2.0.0
588 *
589 * @param string $original Maybe unserialized original, if is needed.
590 * @return mixed Unserialized data can be any type.
591 */
592function maybe_unserialize( $original ) {
593        if ( is_serialized( $original ) ) { // Don't attempt to unserialize data that wasn't serialized going in.
594                return @unserialize( $original );
595        }
596        return $original;
597}
598
599/**
600 * Check value to find if it was serialized.
601 *
602 * If $data is not an string, then returned value will always be false.
603 * Serialized data is always a string.
604 *
605 * @since 2.0.5
606 *
607 * @param string $data   Value to check to see if was serialized.
608 * @param bool   $strict Optional. Whether to be strict about the end of the string. Default true.
609 * @return bool False if not serialized and true if it was.
610 */
611function is_serialized( $data, $strict = true ) {
612        // If it isn't a string, it isn't serialized.
613        if ( ! is_string( $data ) ) {
614                return false;
615        }
616        $data = trim( $data );
617        if ( 'N;' == $data ) {
618                return true;
619        }
620        if ( strlen( $data ) < 4 ) {
621                return false;
622        }
623        if ( ':' !== $data[1] ) {
624                return false;
625        }
626        if ( $strict ) {
627                $lastc = substr( $data, -1 );
628                if ( ';' !== $lastc && '}' !== $lastc ) {
629                        return false;
630                }
631        } else {
632                $semicolon = strpos( $data, ';' );
633                $brace     = strpos( $data, '}' );
634                // Either ; or } must exist.
635                if ( false === $semicolon && false === $brace ) {
636                        return false;
637                }
638                // But neither must be in the first X characters.
639                if ( false !== $semicolon && $semicolon < 3 ) {
640                        return false;
641                }
642                if ( false !== $brace && $brace < 4 ) {
643                        return false;
644                }
645        }
646        $token = $data[0];
647        switch ( $token ) {
648                case 's':
649                        if ( $strict ) {
650                                if ( '"' !== substr( $data, -2, 1 ) ) {
651                                        return false;
652                                }
653                        } elseif ( false === strpos( $data, '"' ) ) {
654                                return false;
655                        }
656                        // Or else fall through.
657                case 'a':
658                case 'O':
659                        return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data );
660                case 'b':
661                case 'i':
662                case 'd':
663                        $end = $strict ? '$' : '';
664                        return (bool) preg_match( "/^{$token}:[0-9.E+-]+;$end/", $data );
665        }
666        return false;
667}
668
669/**
670 * Check whether serialized data is of string type.
671 *
672 * @since 2.0.5
673 *
674 * @param string $data Serialized data.
675 * @return bool False if not a serialized string, true if it is.
676 */
677function is_serialized_string( $data ) {
678        // if it isn't a string, it isn't a serialized string.
679        if ( ! is_string( $data ) ) {
680                return false;
681        }
682        $data = trim( $data );
683        if ( strlen( $data ) < 4 ) {
684                return false;
685        } elseif ( ':' !== $data[1] ) {
686                return false;
687        } elseif ( ';' !== substr( $data, -1 ) ) {
688                return false;
689        } elseif ( 's' !== $data[0] ) {
690                return false;
691        } elseif ( '"' !== substr( $data, -2, 1 ) ) {
692                return false;
693        } else {
694                return true;
695        }
696}
697
698/**
699 * Serialize data, if needed.
700 *
701 * @since 2.0.5
702 *
703 * @param string|array|object $data Data that might be serialized.
704 * @return mixed A scalar data
705 */
706function maybe_serialize( $data ) {
707        if ( is_array( $data ) || is_object( $data ) ) {
708                return serialize( $data );
709        }
710
711        /*
712         * Double serialization is required for backward compatibility.
713         * See https://core.trac.wordpress.org/ticket/12930
714         * Also the world will end. See WP 3.6.1.
715         */
716        if ( is_serialized( $data, false ) ) {
717                return serialize( $data );
718        }
719
720        return $data;
721}
722
723/**
724 * Retrieve post title from XMLRPC XML.
725 *
726 * If the title element is not part of the XML, then the default post title from
727 * the $post_default_title will be used instead.
728 *
729 * @since 0.71
730 *
731 * @global string $post_default_title Default XML-RPC post title.
732 *
733 * @param string $content XMLRPC XML Request content
734 * @return string Post title
735 */
736function xmlrpc_getposttitle( $content ) {
737        global $post_default_title;
738        if ( preg_match( '/<title>(.+?)<\/title>/is', $content, $matchtitle ) ) {
739                $post_title = $matchtitle[1];
740        } else {
741                $post_title = $post_default_title;
742        }
743        return $post_title;
744}
745
746/**
747 * Retrieve the post category or categories from XMLRPC XML.
748 *
749 * If the category element is not found, then the default post category will be
750 * used. The return type then would be what $post_default_category. If the
751 * category is found, then it will always be an array.
752 *
753 * @since 0.71
754 *
755 * @global string $post_default_category Default XML-RPC post category.
756 *
757 * @param string $content XMLRPC XML Request content
758 * @return string|array List of categories or category name.
759 */
760function xmlrpc_getpostcategory( $content ) {
761        global $post_default_category;
762        if ( preg_match( '/<category>(.+?)<\/category>/is', $content, $matchcat ) ) {
763                $post_category = trim( $matchcat[1], ',' );
764                $post_category = explode( ',', $post_category );
765        } else {
766                $post_category = $post_default_category;
767        }
768        return $post_category;
769}
770
771/**
772 * XMLRPC XML content without title and category elements.
773 *
774 * @since 0.71
775 *
776 * @param string $content XML-RPC XML Request content.
777 * @return string XMLRPC XML Request content without title and category elements.
778 */
779function xmlrpc_removepostdata( $content ) {
780        $content = preg_replace( '/<title>(.+?)<\/title>/si', '', $content );
781        $content = preg_replace( '/<category>(.+?)<\/category>/si', '', $content );
782        $content = trim( $content );
783        return $content;
784}
785
786/**
787 * Use RegEx to extract URLs from arbitrary content.
788 *
789 * @since 3.7.0
790 *
791 * @param string $content Content to extract URLs from.
792 * @return string[] Array of URLs found in passed string.
793 */
794function wp_extract_urls( $content ) {
795        preg_match_all(
796                "#([\"']?)("
797                        . '(?:([\w-]+:)?//?)'
798                        . '[^\s()<>]+'
799                        . '[.]'
800                        . '(?:'
801                                . '\([\w\d]+\)|'
802                                . '(?:'
803                                        . "[^`!()\[\]{};:'\".,<>«»“”‘’\s]|"
804                                        . '(?:[:]\d+)?/?'
805                                . ')+'
806                        . ')'
807                . ")\\1#",
808                $content,
809                $post_links
810        );
811
812        $post_links = array_unique( array_map( 'html_entity_decode', $post_links[2] ) );
813
814        return array_values( $post_links );
815}
816
817/**
818 * Check content for video and audio links to add as enclosures.
819 *
820 * Will not add enclosures that have already been added and will
821 * remove enclosures that are no longer in the post. This is called as
822 * pingbacks and trackbacks.
823 *
824 * @since 1.5.0
825 * @since 5.3.0 The `$content` parameter was made optional, and the `$post` parameter was
826 *              updated to accept a post ID or a WP_Post object.
827 *
828 * @global wpdb $wpdb WordPress database abstraction object.
829 *
830 * @param string         $content Post content. If `null`, the `post_content` field from `$post` is used.
831 * @param int|WP_Post    $post    Post ID or post object.
832 * @return null|bool Returns false if post is not found.
833 */
834function do_enclose( $content = null, $post ) {
835        global $wpdb;
836
837        // @todo Tidy this code and make the debug code optional.
838        include_once ABSPATH . WPINC . '/class-IXR.php';
839
840        $post = get_post( $post );
841        if ( ! $post ) {
842                return false;
843        }
844
845        if ( null === $content ) {
846                $content = $post->post_content;
847        }
848
849        $post_links = array();
850
851        $pung = get_enclosed( $post->ID );
852
853        $post_links_temp = wp_extract_urls( $content );
854
855        foreach ( $pung as $link_test ) {
856                // Link is no longer in post.
857                if ( ! in_array( $link_test, $post_links_temp, true ) ) {
858                        $mids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = 'enclosure' AND meta_value LIKE %s", $post->ID, $wpdb->esc_like( $link_test ) . '%' ) );
859                        foreach ( $mids as $mid ) {
860                                delete_metadata_by_mid( 'post', $mid );
861                        }
862                }
863        }
864
865        foreach ( (array) $post_links_temp as $link_test ) {
866                // If we haven't pung it already.
867                if ( ! in_array( $link_test, $pung, true ) ) {
868                        $test = @parse_url( $link_test );
869                        if ( false === $test ) {
870                                continue;
871                        }
872                        if ( isset( $test['query'] ) ) {
873                                $post_links[] = $link_test;
874                        } elseif ( isset( $test['path'] ) && ( '/' !== $test['path'] ) && ( '' !== $test['path'] ) ) {
875                                $post_links[] = $link_test;
876                        }
877                }
878        }
879
880        /**
881         * Filters the list of enclosure links before querying the database.
882         *
883         * Allows for the addition and/or removal of potential enclosures to save
884         * to postmeta before checking the database for existing enclosures.
885         *
886         * @since 4.4.0
887         *
888         * @param string[] $post_links An array of enclosure links.
889         * @param int      $post_ID    Post ID.
890         */
891        $post_links = apply_filters( 'enclosure_links', $post_links, $post->ID );
892
893        foreach ( (array) $post_links as $url ) {
894                if ( '' !== $url && ! $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = 'enclosure' AND meta_value LIKE %s", $post->ID, $wpdb->esc_like( $url ) . '%' ) ) ) {
895
896                        $headers = wp_get_http_headers( $url );
897                        if ( $headers ) {
898                                $len           = isset( $headers['content-length'] ) ? (int) $headers['content-length'] : 0;
899                                $type          = isset( $headers['content-type'] ) ? $headers['content-type'] : '';
900                                $allowed_types = array( 'video', 'audio' );
901
902                                // Check to see if we can figure out the mime type from the extension.
903                                $url_parts = @parse_url( $url );
904                                if ( false !== $url_parts ) {
905                                        $extension = pathinfo( $url_parts['path'], PATHINFO_EXTENSION );
906                                        if ( ! empty( $extension ) ) {
907                                                foreach ( wp_get_mime_types() as $exts => $mime ) {
908                                                        if ( preg_match( '!^(' . $exts . ')$!i', $extension ) ) {
909                                                                $type = $mime;
910                                                                break;
911                                                        }
912                                                }
913                                        }
914                                }
915
916                                if ( in_array( substr( $type, 0, strpos( $type, '/' ) ), $allowed_types, true ) ) {
917                                        add_post_meta( $post->ID, 'enclosure', "$url\n$len\n$mime\n" );
918                                }
919                        }
920                }
921        }
922}
923
924/**
925 * Retrieve HTTP Headers from URL.
926 *
927 * @since 1.5.1
928 *
929 * @param string $url        URL to retrieve HTTP headers from.
930 * @param bool   $deprecated Not Used.
931 * @return bool|string False on failure, headers on success.
932 */
933function wp_get_http_headers( $url, $deprecated = false ) {
934        if ( ! empty( $deprecated ) ) {
935                _deprecated_argument( __FUNCTION__, '2.7.0' );
936        }
937
938        $response = wp_safe_remote_head( $url );
939
940        if ( is_wp_error( $response ) ) {
941                return false;
942        }
943
944        return wp_remote_retrieve_headers( $response );
945}
946
947/**
948 * Determines whether the publish date of the current post in the loop is different
949 * from the publish date of the previous post in the loop.
950 *
951 * For more information on this and similar theme functions, check out
952 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
953 * Conditional Tags} article in the Theme Developer Handbook.
954 *
955 * @since 0.71
956 *
957 * @global string $currentday  The day of the current post in the loop.
958 * @global string $previousday The day of the previous post in the loop.
959 *
960 * @return int 1 when new day, 0 if not a new day.
961 */
962function is_new_day() {
963        global $currentday, $previousday;
964
965        if ( $currentday !== $previousday ) {
966                return 1;
967        } else {
968                return 0;
969        }
970}
971
972/**
973 * Build URL query based on an associative and, or indexed array.
974 *
975 * This is a convenient function for easily building url queries. It sets the
976 * separator to '&' and uses _http_build_query() function.
977 *
978 * @since 2.3.0
979 *
980 * @see _http_build_query() Used to build the query
981 * @link https://www.php.net/manual/en/function.http-build-query.php for more on what
982 *       http_build_query() does.
983 *
984 * @param array $data URL-encode key/value pairs.
985 * @return string URL-encoded string.
986 */
987function build_query( $data ) {
988        return _http_build_query( $data, null, '&', '', false );
989}
990
991/**
992 * From php.net (modified by Mark Jaquith to behave like the native PHP5 function).
993 *
994 * @since 3.2.0
995 * @access private
996 *
997 * @see https://www.php.net/manual/en/function.http-build-query.php
998 *
999 * @param array|object  $data       An array or object of data. Converted to array.
1000 * @param string        $prefix     Optional. Numeric index. If set, start parameter numbering with it.
1001 *                                  Default null.
1002 * @param string        $sep        Optional. Argument separator; defaults to 'arg_separator.output'.
1003 *                                  Default null.
1004 * @param string        $key        Optional. Used to prefix key name. Default empty.
1005 * @param bool          $urlencode  Optional. Whether to use urlencode() in the result. Default true.
1006 *
1007 * @return string The query string.
1008 */
1009function _http_build_query( $data, $prefix = null, $sep = null, $key = '', $urlencode = true ) {
1010        $ret = array();
1011
1012        foreach ( (array) $data as $k => $v ) {
1013                if ( $urlencode ) {
1014                        $k = urlencode( $k );
1015                }
1016                if ( is_int( $k ) && null != $prefix ) {
1017                        $k = $prefix . $k;
1018                }
1019                if ( ! empty( $key ) ) {
1020                        $k = $key . '%5B' . $k . '%5D';
1021                }
1022                if ( null === $v ) {
1023                        continue;
1024                } elseif ( false === $v ) {
1025                        $v = '0';
1026                }
1027
1028                if ( is_array( $v ) || is_object( $v ) ) {
1029                        array_push( $ret, _http_build_query( $v, '', $sep, $k, $urlencode ) );
1030                } elseif ( $urlencode ) {
1031                        array_push( $ret, $k . '=' . urlencode( $v ) );
1032                } else {
1033                        array_push( $ret, $k . '=' . $v );
1034                }
1035        }
1036
1037        if ( null === $sep ) {
1038                $sep = ini_get( 'arg_separator.output' );
1039        }
1040
1041        return implode( $sep, $ret );
1042}
1043
1044/**
1045 * Retrieves a modified URL query string.
1046 *
1047 * You can rebuild the URL and append query variables to the URL query by using this function.
1048 * There are two ways to use this function; either a single key and value, or an associative array.
1049 *
1050 * Using a single key and value:
1051 *
1052 *     add_query_arg( 'key', 'value', 'http://example.com' );
1053 *
1054 * Using an associative array:
1055 *
1056 *     add_query_arg( array(
1057 *         'key1' => 'value1',
1058 *         'key2' => 'value2',
1059 *     ), 'http://example.com' );
1060 *
1061 * Omitting the URL from either use results in the current URL being used
1062 * (the value of `$_SERVER['REQUEST_URI']`).
1063 *
1064 * Values are expected to be encoded appropriately with urlencode() or rawurlencode().
1065 *
1066 * Setting any query variable's value to boolean false removes the key (see remove_query_arg()).
1067 *
1068 * Important: The return value of add_query_arg() is not escaped by default. Output should be
1069 * late-escaped with esc_url() or similar to help prevent vulnerability to cross-site scripting
1070 * (XSS) attacks.
1071 *
1072 * @since 1.5.0
1073 * @since 5.3.0 Formalized the existing and already documented parameters
1074 *              by adding `...$args` to the function signature.
1075 *
1076 * @param string|array $key   Either a query variable key, or an associative array of query variables.
1077 * @param string       $value Optional. Either a query variable value, or a URL to act upon.
1078 * @param string       $url   Optional. A URL to act upon.
1079 * @return string New URL query string (unescaped).
1080 */
1081function add_query_arg( ...$args ) {
1082        if ( is_array( $args[0] ) ) {
1083                if ( count( $args ) < 2 || false === $args[1] ) {
1084                        $uri = $_SERVER['REQUEST_URI'];
1085                } else {
1086                        $uri = $args[1];
1087                }
1088        } else {
1089                if ( count( $args ) < 3 || false === $args[2] ) {
1090                        $uri = $_SERVER['REQUEST_URI'];
1091                } else {
1092                        $uri = $args[2];
1093                }
1094        }
1095
1096        $frag = strstr( $uri, '#' );
1097        if ( $frag ) {
1098                $uri = substr( $uri, 0, -strlen( $frag ) );
1099        } else {
1100                $frag = '';
1101        }
1102
1103        if ( 0 === stripos( $uri, 'http://' ) ) {
1104                $protocol = 'http://';
1105                $uri      = substr( $uri, 7 );
1106        } elseif ( 0 === stripos( $uri, 'https://' ) ) {
1107                $protocol = 'https://';
1108                $uri      = substr( $uri, 8 );
1109        } else {
1110                $protocol = '';
1111        }
1112
1113        if ( strpos( $uri, '?' ) !== false ) {
1114                list( $base, $query ) = explode( '?', $uri, 2 );
1115                $base                .= '?';
1116        } elseif ( $protocol || strpos( $uri, '=' ) === false ) {
1117                $base  = $uri . '?';
1118                $query = '';
1119        } else {
1120                $base  = '';
1121                $query = $uri;
1122        }
1123
1124        wp_parse_str( $query, $qs );
1125        $qs = urlencode_deep( $qs ); // This re-URL-encodes things that were already in the query string.
1126        if ( is_array( $args[0] ) ) {
1127                foreach ( $args[0] as $k => $v ) {
1128                        $qs[ $k ] = $v;
1129                }
1130        } else {
1131                $qs[ $args[0] ] = $args[1];
1132        }
1133
1134        foreach ( $qs as $k => $v ) {
1135                if ( false === $v ) {
1136                        unset( $qs[ $k ] );
1137                }
1138        }
1139
1140        $ret = build_query( $qs );
1141        $ret = trim( $ret, '?' );
1142        $ret = preg_replace( '#=(&|$)#', '$1', $ret );
1143        $ret = $protocol . $base . $ret . $frag;
1144        $ret = rtrim( $ret, '?' );
1145        return $ret;
1146}
1147
1148/**
1149 * Removes an item or items from a query string.
1150 *
1151 * @since 1.5.0
1152 *
1153 * @param string|array $key   Query key or keys to remove.
1154 * @param bool|string  $query Optional. When false uses the current URL. Default false.
1155 * @return string New URL query string.
1156 */
1157function remove_query_arg( $key, $query = false ) {
1158        if ( is_array( $key ) ) { // Removing multiple keys.
1159                foreach ( $key as $k ) {
1160                        $query = add_query_arg( $k, false, $query );
1161                }
1162                return $query;
1163        }
1164        return add_query_arg( $key, false, $query );
1165}
1166
1167/**
1168 * Returns an array of single-use query variable names that can be removed from a URL.
1169 *
1170 * @since 4.4.0
1171 *
1172 * @return string[] An array of parameters to remove from the URL.
1173 */
1174function wp_removable_query_args() {
1175        $removable_query_args = array(
1176                'activate',
1177                'activated',
1178                'approved',
1179                'deactivate',
1180                'deleted',
1181                'disabled',
1182                'doing_wp_cron',
1183                'enabled',
1184                'error',
1185                'hotkeys_highlight_first',
1186                'hotkeys_highlight_last',
1187                'locked',
1188                'message',
1189                'same',
1190                'saved',
1191                'settings-updated',
1192                'skipped',
1193                'spammed',
1194                'trashed',
1195                'unspammed',
1196                'untrashed',
1197                'update',
1198                'updated',
1199                'wp-post-new-reload',
1200        );
1201
1202        /**
1203         * Filters the list of query variables to remove.
1204         *
1205         * @since 4.2.0
1206         *
1207         * @param string[] $removable_query_args An array of query variables to remove from a URL.
1208         */
1209        return apply_filters( 'removable_query_args', $removable_query_args );
1210}
1211
1212/**
1213 * Walks the array while sanitizing the contents.
1214 *
1215 * @since 0.71
1216 *
1217 * @param array $array Array to walk while sanitizing contents.
1218 * @return array Sanitized $array.
1219 */
1220function add_magic_quotes( $array ) {
1221        foreach ( (array) $array as $k => $v ) {
1222                if ( is_array( $v ) ) {
1223                        $array[ $k ] = add_magic_quotes( $v );
1224                } else {
1225                        $array[ $k ] = addslashes( $v );
1226                }
1227        }
1228        return $array;
1229}
1230
1231/**
1232 * HTTP request for URI to retrieve content.
1233 *
1234 * @since 1.5.1
1235 *
1236 * @see wp_safe_remote_get()
1237 *
1238 * @param string $uri URI/URL of web page to retrieve.
1239 * @return string|false HTTP content. False on failure.
1240 */
1241function wp_remote_fopen( $uri ) {
1242        $parsed_url = @parse_url( $uri );
1243
1244        if ( ! $parsed_url || ! is_array( $parsed_url ) ) {
1245                return false;
1246        }
1247
1248        $options            = array();
1249        $options['timeout'] = 10;
1250
1251        $response = wp_safe_remote_get( $uri, $options );
1252
1253        if ( is_wp_error( $response ) ) {
1254                return false;
1255        }
1256
1257        return wp_remote_retrieve_body( $response );
1258}
1259
1260/**
1261 * Set up the WordPress query.
1262 *
1263 * @since 2.0.0
1264 *
1265 * @global WP       $wp           Current WordPress environment instance.
1266 * @global WP_Query $wp_query     WordPress Query object.
1267 * @global WP_Query $wp_the_query Copy of the WordPress Query object.
1268 *
1269 * @param string|array $query_vars Default WP_Query arguments.
1270 */
1271function wp( $query_vars = '' ) {
1272        global $wp, $wp_query, $wp_the_query;
1273
1274        $wp->main( $query_vars );
1275
1276        if ( ! isset( $wp_the_query ) ) {
1277                $wp_the_query = $wp_query;
1278        }
1279}
1280
1281/**
1282 * Retrieve the description for the HTTP status.
1283 *
1284 * @since 2.3.0
1285 * @since 3.9.0 Added status codes 418, 428, 429, 431, and 511.
1286 * @since 4.5.0 Added status codes 308, 421, and 451.
1287 * @since 5.1.0 Added status code 103.
1288 *
1289 * @global array $wp_header_to_desc
1290 *
1291 * @param int $code HTTP status code.
1292 * @return string Status description if found, an empty string otherwise.
1293 */
1294function get_status_header_desc( $code ) {
1295        global $wp_header_to_desc;
1296
1297        $code = absint( $code );
1298
1299        if ( ! isset( $wp_header_to_desc ) ) {
1300                $wp_header_to_desc = array(
1301                        100 => 'Continue',
1302                        101 => 'Switching Protocols',
1303                        102 => 'Processing',
1304                        103 => 'Early Hints',
1305
1306                        200 => 'OK',
1307                        201 => 'Created',
1308                        202 => 'Accepted',
1309                        203 => 'Non-Authoritative Information',
1310                        204 => 'No Content',
1311                        205 => 'Reset Content',
1312                        206 => 'Partial Content',
1313                        207 => 'Multi-Status',
1314                        226 => 'IM Used',
1315
1316                        300 => 'Multiple Choices',
1317                        301 => 'Moved Permanently',
1318                        302 => 'Found',
1319                        303 => 'See Other',
1320                        304 => 'Not Modified',
1321                        305 => 'Use Proxy',
1322                        306 => 'Reserved',
1323                        307 => 'Temporary Redirect',
1324                        308 => 'Permanent Redirect',
1325
1326                        400 => 'Bad Request',
1327                        401 => 'Unauthorized',
1328                        402 => 'Payment Required',
1329                        403 => 'Forbidden',
1330                        404 => 'Not Found',
1331                        405 => 'Method Not Allowed',
1332                        406 => 'Not Acceptable',
1333                        407 => 'Proxy Authentication Required',
1334                        408 => 'Request Timeout',
1335                        409 => 'Conflict',
1336                        410 => 'Gone',
1337                        411 => 'Length Required',
1338                        412 => 'Precondition Failed',
1339                        413 => 'Request Entity Too Large',
1340                        414 => 'Request-URI Too Long',
1341                        415 => 'Unsupported Media Type',
1342                        416 => 'Requested Range Not Satisfiable',
1343                        417 => 'Expectation Failed',
1344                        418 => 'I\'m a teapot',
1345                        421 => 'Misdirected Request',
1346                        422 => 'Unprocessable Entity',
1347                        423 => 'Locked',
1348                        424 => 'Failed Dependency',
1349                        426 => 'Upgrade Required',
1350                        428 => 'Precondition Required',
1351                        429 => 'Too Many Requests',
1352                        431 => 'Request Header Fields Too Large',
1353                        451 => 'Unavailable For Legal Reasons',
1354
1355                        500 => 'Internal Server Error',
1356                        501 => 'Not Implemented',
1357                        502 => 'Bad Gateway',
1358                        503 => 'Service Unavailable',
1359                        504 => 'Gateway Timeout',
1360                        505 => 'HTTP Version Not Supported',
1361                        506 => 'Variant Also Negotiates',
1362                        507 => 'Insufficient Storage',
1363                        510 => 'Not Extended',
1364                        511 => 'Network Authentication Required',
1365                );
1366        }
1367
1368        if ( isset( $wp_header_to_desc[ $code ] ) ) {
1369                return $wp_header_to_desc[ $code ];
1370        } else {
1371                return '';
1372        }
1373}
1374
1375/**
1376 * Set HTTP status header.
1377 *
1378 * @since 2.0.0
1379 * @since 4.4.0 Added the `$description` parameter.
1380 *
1381 * @see get_status_header_desc()
1382 *
1383 * @param int    $code        HTTP status code.
1384 * @param string $description Optional. A custom description for the HTTP status.
1385 */
1386function status_header( $code, $description = '' ) {
1387        if ( ! $description ) {
1388                $description = get_status_header_desc( $code );
1389        }
1390
1391        if ( empty( $description ) ) {
1392                return;
1393        }
1394
1395        $protocol      = wp_get_server_protocol();
1396        $status_header = "$protocol $code $description";
1397        if ( function_exists( 'apply_filters' ) ) {
1398
1399                /**
1400                 * Filters an HTTP status header.
1401                 *
1402                 * @since 2.2.0
1403                 *
1404                 * @param string $status_header HTTP status header.
1405                 * @param int    $code          HTTP status code.
1406                 * @param string $description   Description for the status code.
1407                 * @param string $protocol      Server protocol.
1408                 */
1409                $status_header = apply_filters( 'status_header', $status_header, $code, $description, $protocol );
1410        }
1411
1412        if ( ! headers_sent() ) {
1413                header( $status_header, true, $code );
1414        }
1415}
1416
1417/**
1418 * Get the header information to prevent caching.
1419 *
1420 * The several different headers cover the different ways cache prevention
1421 * is handled by different browsers
1422 *
1423 * @since 2.8.0
1424 *
1425 * @return array The associative array of header names and field values.
1426 */
1427function wp_get_nocache_headers() {
1428        $headers = array(
1429                'Expires'       => 'Wed, 11 Jan 1984 05:00:00 GMT',
1430                'Cache-Control' => 'no-cache, must-revalidate, max-age=0',
1431        );
1432
1433        if ( function_exists( 'apply_filters' ) ) {
1434                /**
1435                 * Filters the cache-controlling headers.
1436                 *
1437                 * @since 2.8.0
1438                 *
1439                 * @see wp_get_nocache_headers()
1440                 *
1441                 * @param array $headers {
1442                 *     Header names and field values.
1443                 *
1444                 *     @type string $Expires       Expires header.
1445                 *     @type string $Cache-Control Cache-Control header.
1446                 * }
1447                 */
1448                $headers = (array) apply_filters( 'nocache_headers', $headers );
1449        }
1450        $headers['Last-Modified'] = false;
1451        return $headers;
1452}
1453
1454/**
1455 * Set the headers to prevent caching for the different browsers.
1456 *
1457 * Different browsers support different nocache headers, so several
1458 * headers must be sent so that all of them get the point that no
1459 * caching should occur.
1460 *
1461 * @since 2.0.0
1462 *
1463 * @see wp_get_nocache_headers()
1464 */
1465function nocache_headers() {
1466        if ( headers_sent() ) {
1467                return;
1468        }
1469
1470        $headers = wp_get_nocache_headers();
1471
1472        unset( $headers['Last-Modified'] );
1473
1474        header_remove( 'Last-Modified' );
1475
1476        foreach ( $headers as $name => $field_value ) {
1477                header( "{$name}: {$field_value}" );
1478        }
1479}
1480
1481/**
1482 * Set the headers for caching for 10 days with JavaScript content type.
1483 *
1484 * @since 2.1.0
1485 */
1486function cache_javascript_headers() {
1487        $expiresOffset = 10 * DAY_IN_SECONDS;
1488
1489        header( 'Content-Type: text/javascript; charset=' . get_bloginfo( 'charset' ) );
1490        header( 'Vary: Accept-Encoding' ); // Handle proxies.
1491        header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $expiresOffset ) . ' GMT' );
1492}
1493
1494/**
1495 * Retrieve the number of database queries during the WordPress execution.
1496 *
1497 * @since 2.0.0
1498 *
1499 * @global wpdb $wpdb WordPress database abstraction object.
1500 *
1501 * @return int Number of database queries.
1502 */
1503function get_num_queries() {
1504        global $wpdb;
1505        return $wpdb->num_queries;
1506}
1507
1508/**
1509 * Whether input is yes or no.
1510 *
1511 * Must be 'y' to be true.
1512 *
1513 * @since 1.0.0
1514 *
1515 * @param string $yn Character string containing either 'y' (yes) or 'n' (no).
1516 * @return bool True if yes, false on anything else.
1517 */
1518function bool_from_yn( $yn ) {
1519        return ( strtolower( $yn ) == 'y' );
1520}
1521
1522/**
1523 * Load the feed template from the use of an action hook.
1524 *
1525 * If the feed action does not have a hook, then the function will die with a
1526 * message telling the visitor that the feed is not valid.
1527 *
1528 * It is better to only have one hook for each feed.
1529 *
1530 * @since 2.1.0
1531 *
1532 * @global WP_Query $wp_query WordPress Query object.
1533 */
1534function do_feed() {
1535        global $wp_query;
1536
1537        $feed = get_query_var( 'feed' );
1538
1539        // Remove the pad, if present.
1540        $feed = preg_replace( '/^_+/', '', $feed );
1541
1542        if ( '' == $feed || 'feed' === $feed ) {
1543                $feed = get_default_feed();
1544        }
1545
1546        if ( ! has_action( "do_feed_{$feed}" ) ) {
1547                wp_die( __( 'Error: This is not a valid feed template.' ), '', array( 'response' => 404 ) );
1548        }
1549
1550        /**
1551         * Fires once the given feed is loaded.
1552         *
1553         * The dynamic portion of the hook name, `$feed`, refers to the feed template name.
1554         * Possible values include: 'rdf', 'rss', 'rss2', and 'atom'.
1555         *
1556         * @since 2.1.0
1557         * @since 4.4.0 The `$feed` parameter was added.
1558         *
1559         * @param bool   $is_comment_feed Whether the feed is a comment feed.
1560         * @param string $feed            The feed name.
1561         */
1562        do_action( "do_feed_{$feed}", $wp_query->is_comment_feed, $feed );
1563}
1564
1565/**
1566 * Load the RDF RSS 0.91 Feed template.
1567 *
1568 * @since 2.1.0
1569 *
1570 * @see load_template()
1571 */
1572function do_feed_rdf() {
1573        load_template( ABSPATH . WPINC . '/feed-rdf.php' );
1574}
1575
1576/**
1577 * Load the RSS 1.0 Feed Template.
1578 *
1579 * @since 2.1.0
1580 *
1581 * @see load_template()
1582 */
1583function do_feed_rss() {
1584        load_template( ABSPATH . WPINC . '/feed-rss.php' );
1585}
1586
1587/**
1588 * Load either the RSS2 comment feed or the RSS2 posts feed.
1589 *
1590 * @since 2.1.0
1591 *
1592 * @see load_template()
1593 *
1594 * @param bool $for_comments True for the comment feed, false for normal feed.
1595 */
1596function do_feed_rss2( $for_comments ) {
1597        if ( $for_comments ) {
1598                load_template( ABSPATH . WPINC . '/feed-rss2-comments.php' );
1599        } else {
1600                load_template( ABSPATH . WPINC . '/feed-rss2.php' );
1601        }
1602}
1603
1604/**
1605 * Load either Atom comment feed or Atom posts feed.
1606 *
1607 * @since 2.1.0
1608 *
1609 * @see load_template()
1610 *
1611 * @param bool $for_comments True for the comment feed, false for normal feed.
1612 */
1613function do_feed_atom( $for_comments ) {
1614        if ( $for_comments ) {
1615                load_template( ABSPATH . WPINC . '/feed-atom-comments.php' );
1616        } else {
1617                load_template( ABSPATH . WPINC . '/feed-atom.php' );
1618        }
1619}
1620
1621/**
1622 * Displays the default robots.txt file content.
1623 *
1624 * @since 2.1.0
1625 * @since 5.3.0 Remove the "Disallow: /" output if search engine visiblity is
1626 *              discouraged in favor of robots meta HTML tag in wp_no_robots().
1627 */
1628function do_robots() {
1629        header( 'Content-Type: text/plain; charset=utf-8' );
1630
1631        /**
1632         * Fires when displaying the robots.txt file.
1633         *
1634         * @since 2.1.0
1635         */
1636        do_action( 'do_robotstxt' );
1637
1638        $output = "User-agent: *\n";
1639        $public = get_option( 'blog_public' );
1640
1641        $site_url = parse_url( site_url() );
1642        $path     = ( ! empty( $site_url['path'] ) ) ? $site_url['path'] : '';
1643        $output  .= "Disallow: $path/wp-admin/\n";
1644        $output  .= "Allow: $path/wp-admin/admin-ajax.php\n";
1645
1646        /**
1647         * Filters the robots.txt output.
1648         *
1649         * @since 3.0.0
1650         *
1651         * @param string $output The robots.txt output.
1652         * @param bool   $public Whether the site is considered "public".
1653         */
1654        echo apply_filters( 'robots_txt', $output, $public );
1655}
1656
1657/**
1658 * Display the favicon.ico file content.
1659 *
1660 * @since 5.4.0
1661 */
1662function do_favicon() {
1663        /**
1664         * Fires when serving the favicon.ico file.
1665         *
1666         * @since 5.4.0
1667         */
1668        do_action( 'do_faviconico' );
1669
1670        wp_redirect( get_site_icon_url( 32, admin_url( 'images/w-logo-blue.png' ) ) );
1671        exit;
1672}
1673
1674/**
1675 * Determines whether WordPress is already installed.
1676 *
1677 * The cache will be checked first. If you have a cache plugin, which saves
1678 * the cache values, then this will work. If you use the default WordPress
1679 * cache, and the database goes away, then you might have problems.
1680 *
1681 * Checks for the 'siteurl' option for whether WordPress is installed.
1682 *
1683 * For more information on this and similar theme functions, check out
1684 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1685 * Conditional Tags} article in the Theme Developer Handbook.
1686 *
1687 * @since 2.1.0
1688 *
1689 * @global wpdb $wpdb WordPress database abstraction object.
1690 *
1691 * @return bool Whether the site is already installed.
1692 */
1693function is_blog_installed() {
1694        global $wpdb;
1695
1696        /*
1697         * Check cache first. If options table goes away and we have true
1698         * cached, oh well.
1699         */
1700        if ( wp_cache_get( 'is_blog_installed' ) ) {
1701                return true;
1702        }
1703
1704        $suppress = $wpdb->suppress_errors();
1705        if ( ! wp_installing() ) {
1706                $alloptions = wp_load_alloptions();
1707        }
1708        // If siteurl is not set to autoload, check it specifically.
1709        if ( ! isset( $alloptions['siteurl'] ) ) {
1710                $installed = $wpdb->get_var( "SELECT option_value FROM $wpdb->options WHERE option_name = 'siteurl'" );
1711        } else {
1712                $installed = $alloptions['siteurl'];
1713        }
1714        $wpdb->suppress_errors( $suppress );
1715
1716        $installed = ! empty( $installed );
1717        wp_cache_set( 'is_blog_installed', $installed );
1718
1719        if ( $installed ) {
1720                return true;
1721        }
1722
1723        // If visiting repair.php, return true and let it take over.
1724        if ( defined( 'WP_REPAIRING' ) ) {
1725                return true;
1726        }
1727
1728        $suppress = $wpdb->suppress_errors();
1729
1730        /*
1731         * Loop over the WP tables. If none exist, then scratch installation is allowed.
1732         * If one or more exist, suggest table repair since we got here because the
1733         * options table could not be accessed.
1734         */
1735        $wp_tables = $wpdb->tables();
1736        foreach ( $wp_tables as $table ) {
1737                // The existence of custom user tables shouldn't suggest an insane state or prevent a clean installation.
1738                if ( defined( 'CUSTOM_USER_TABLE' ) && CUSTOM_USER_TABLE == $table ) {
1739                        continue;
1740                }
1741                if ( defined( 'CUSTOM_USER_META_TABLE' ) && CUSTOM_USER_META_TABLE == $table ) {
1742                        continue;
1743                }
1744
1745                if ( ! $wpdb->get_results( "DESCRIBE $table;" ) ) {
1746                        continue;
1747                }
1748
1749                // One or more tables exist. We are insane.
1750
1751                wp_load_translations_early();
1752
1753                // Die with a DB error.
1754                $wpdb->error = sprintf(
1755                        /* translators: %s: Database repair URL. */
1756                        __( 'One or more database tables are unavailable. The database may need to be <a href="%s">repaired</a>.' ),
1757                        'maint/repair.php?referrer=is_blog_installed'
1758                );
1759
1760                dead_db();
1761        }
1762
1763        $wpdb->suppress_errors( $suppress );
1764
1765        wp_cache_set( 'is_blog_installed', false );
1766
1767        return false;
1768}
1769
1770/**
1771 * Retrieve URL with nonce added to URL query.
1772 *
1773 * @since 2.0.4
1774 *
1775 * @param string     $actionurl URL to add nonce action.
1776 * @param int|string $action    Optional. Nonce action name. Default -1.
1777 * @param string     $name      Optional. Nonce name. Default '_wpnonce'.
1778 * @return string Escaped URL with nonce action added.
1779 */
1780function wp_nonce_url( $actionurl, $action = -1, $name = '_wpnonce' ) {
1781        $actionurl = str_replace( '&amp;', '&', $actionurl );
1782        return esc_html( add_query_arg( $name, wp_create_nonce( $action ), $actionurl ) );
1783}
1784
1785/**
1786 * Retrieve or display nonce hidden field for forms.
1787 *
1788 * The nonce field is used to validate that the contents of the form came from
1789 * the location on the current site and not somewhere else. The nonce does not
1790 * offer absolute protection, but should protect against most cases. It is very
1791 * important to use nonce field in forms.
1792 *
1793 * The $action and $name are optional, but if you want to have better security,
1794 * it is strongly suggested to set those two parameters. It is easier to just
1795 * call the function without any parameters, because validation of the nonce
1796 * doesn't require any parameters, but since crackers know what the default is
1797 * it won't be difficult for them to find a way around your nonce and cause
1798 * damage.
1799 *
1800 * The input name will be whatever $name value you gave. The input value will be
1801 * the nonce creation value.
1802 *
1803 * @since 2.0.4
1804 *
1805 * @param int|string $action  Optional. Action name. Default -1.
1806 * @param string     $name    Optional. Nonce name. Default '_wpnonce'.
1807 * @param bool       $referer Optional. Whether to set the referer field for validation. Default true.
1808 * @param bool       $echo    Optional. Whether to display or return hidden form field. Default true.
1809 * @return string Nonce field HTML markup.
1810 */
1811function wp_nonce_field( $action = -1, $name = '_wpnonce', $referer = true, $echo = true ) {
1812        $name        = esc_attr( $name );
1813        $nonce_field = '<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . wp_create_nonce( $action ) . '" />';
1814
1815        if ( $referer ) {
1816                $nonce_field .= wp_referer_field( false );
1817        }
1818
1819        if ( $echo ) {
1820                echo $nonce_field;
1821        }
1822
1823        return $nonce_field;
1824}
1825
1826/**
1827 * Retrieve or display referer hidden field for forms.
1828 *
1829 * The referer link is the current Request URI from the server super global. The
1830 * input name is '_wp_http_referer', in case you wanted to check manually.
1831 *
1832 * @since 2.0.4
1833 *
1834 * @param bool $echo Optional. Whether to echo or return the referer field. Default true.
1835 * @return string Referer field HTML markup.
1836 */
1837function wp_referer_field( $echo = true ) {
1838        $referer_field = '<input type="hidden" name="_wp_http_referer" value="' . esc_attr( wp_unslash( $_SERVER['REQUEST_URI'] ) ) . '" />';
1839
1840        if ( $echo ) {
1841                echo $referer_field;
1842        }
1843        return $referer_field;
1844}
1845
1846/**
1847 * Retrieve or display original referer hidden field for forms.
1848 *
1849 * The input name is '_wp_original_http_referer' and will be either the same
1850 * value of wp_referer_field(), if that was posted already or it will be the
1851 * current page, if it doesn't exist.
1852 *
1853 * @since 2.0.4
1854 *
1855 * @param bool   $echo         Optional. Whether to echo the original http referer. Default true.
1856 * @param string $jump_back_to Optional. Can be 'previous' or page you want to jump back to.
1857 *                             Default 'current'.
1858 * @return string Original referer field.
1859 */
1860function wp_original_referer_field( $echo = true, $jump_back_to = 'current' ) {
1861        $ref = wp_get_original_referer();
1862        if ( ! $ref ) {
1863                $ref = 'previous' == $jump_back_to ? wp_get_referer() : wp_unslash( $_SERVER['REQUEST_URI'] );
1864        }
1865        $orig_referer_field = '<input type="hidden" name="_wp_original_http_referer" value="' . esc_attr( $ref ) . '" />';
1866        if ( $echo ) {
1867                echo $orig_referer_field;
1868        }
1869        return $orig_referer_field;
1870}
1871
1872/**
1873 * Retrieve referer from '_wp_http_referer' or HTTP referer.
1874 *
1875 * If it's the same as the current request URL, will return false.
1876 *
1877 * @since 2.0.4
1878 *
1879 * @return string|false Referer URL on success, false on failure.
1880 */
1881function wp_get_referer() {
1882        if ( ! function_exists( 'wp_validate_redirect' ) ) {
1883                return false;
1884        }
1885
1886        $ref = wp_get_raw_referer();
1887
1888        if ( $ref && wp_unslash( $_SERVER['REQUEST_URI'] ) !== $ref && home_url() . wp_unslash( $_SERVER['REQUEST_URI'] ) !== $ref ) {
1889                return wp_validate_redirect( $ref, false );
1890        }
1891
1892        return false;
1893}
1894
1895/**
1896 * Retrieves unvalidated referer from '_wp_http_referer' or HTTP referer.
1897 *
1898 * Do not use for redirects, use wp_get_referer() instead.
1899 *
1900 * @since 4.5.0
1901 *
1902 * @return string|false Referer URL on success, false on failure.
1903 */
1904function wp_get_raw_referer() {
1905        if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
1906                return wp_unslash( $_REQUEST['_wp_http_referer'] );
1907        } elseif ( ! empty( $_SERVER['HTTP_REFERER'] ) ) {
1908                return wp_unslash( $_SERVER['HTTP_REFERER'] );
1909        }
1910
1911        return false;
1912}
1913
1914/**
1915 * Retrieve original referer that was posted, if it exists.
1916 *
1917 * @since 2.0.4
1918 *
1919 * @return string|false False if no original referer or original referer if set.
1920 */
1921function wp_get_original_referer() {
1922        if ( ! empty( $_REQUEST['_wp_original_http_referer'] ) && function_exists( 'wp_validate_redirect' ) ) {
1923                return wp_validate_redirect( wp_unslash( $_REQUEST['_wp_original_http_referer'] ), false );
1924        }
1925        return false;
1926}
1927
1928/**
1929 * Recursive directory creation based on full path.
1930 *
1931 * Will attempt to set permissions on folders.
1932 *
1933 * @since 2.0.1
1934 *
1935 * @param string $target Full path to attempt to create.
1936 * @return bool Whether the path was created. True if path already exists.
1937 */
1938function wp_mkdir_p( $target ) {
1939        $wrapper = null;
1940
1941        // Strip the protocol.
1942        if ( wp_is_stream( $target ) ) {
1943                list( $wrapper, $target ) = explode( '://', $target, 2 );
1944        }
1945
1946        // From php.net/mkdir user contributed notes.
1947        $target = str_replace( '//', '/', $target );
1948
1949        // Put the wrapper back on the target.
1950        if ( null !== $wrapper ) {
1951                $target = $wrapper . '://' . $target;
1952        }
1953
1954        /*
1955         * Safe mode fails with a trailing slash under certain PHP versions.
1956         * Use rtrim() instead of untrailingslashit to avoid formatting.php dependency.
1957         */
1958        $target = rtrim( $target, '/' );
1959        if ( empty( $target ) ) {
1960                $target = '/';
1961        }
1962
1963        if ( file_exists( $target ) ) {
1964                return @is_dir( $target );
1965        }
1966
1967        // Do not allow path traversals.
1968        if ( false !== strpos( $target, '../' ) || false !== strpos( $target, '..' . DIRECTORY_SEPARATOR ) ) {
1969                return false;
1970        }
1971
1972        // We need to find the permissions of the parent folder that exists and inherit that.
1973        $target_parent = dirname( $target );
1974        while ( '.' != $target_parent && ! is_dir( $target_parent ) && dirname( $target_parent ) !== $target_parent ) {
1975                $target_parent = dirname( $target_parent );
1976        }
1977
1978        // Get the permission bits.
1979        $stat = @stat( $target_parent );
1980        if ( $stat ) {
1981                $dir_perms = $stat['mode'] & 0007777;
1982        } else {
1983                $dir_perms = 0777;
1984        }
1985
1986        if ( @mkdir( $target, $dir_perms, true ) ) {
1987
1988                /*
1989                 * If a umask is set that modifies $dir_perms, we'll have to re-set
1990                 * the $dir_perms correctly with chmod()
1991                 */
1992                if ( ( $dir_perms & ~umask() ) != $dir_perms ) {
1993                        $folder_parts = explode( '/', substr( $target, strlen( $target_parent ) + 1 ) );
1994                        for ( $i = 1, $c = count( $folder_parts ); $i <= $c; $i++ ) {
1995                                chmod( $target_parent . '/' . implode( '/', array_slice( $folder_parts, 0, $i ) ), $dir_perms );
1996                        }
1997                }
1998
1999                return true;
2000        }
2001
2002        return false;
2003}
2004
2005/**
2006 * Test if a given filesystem path is absolute.
2007 *
2008 * For example, '/foo/bar', or 'c:\windows'.
2009 *
2010 * @since 2.5.0
2011 *
2012 * @param string $path File path.
2013 * @return bool True if path is absolute, false is not absolute.
2014 */
2015function path_is_absolute( $path ) {
2016        /*
2017         * Check to see if the path is a stream and check to see if its an actual
2018         * path or file as realpath() does not support stream wrappers.
2019         */
2020        if ( wp_is_stream( $path ) && ( is_dir( $path ) || is_file( $path ) ) ) {
2021                return true;
2022        }
2023
2024        /*
2025         * This is definitive if true but fails if $path does not exist or contains
2026         * a symbolic link.
2027         */
2028        if ( realpath( $path ) == $path ) {
2029                return true;
2030        }
2031
2032        if ( strlen( $path ) == 0 || '.' === $path[0] ) {
2033                return false;
2034        }
2035
2036        // Windows allows absolute paths like this.
2037        if ( preg_match( '#^[a-zA-Z]:\\\\#', $path ) ) {
2038                return true;
2039        }
2040
2041        // A path starting with / or \ is absolute; anything else is relative.
2042        return ( '/' === $path[0] || '\\' === $path[0] );
2043}
2044
2045/**
2046 * Join two filesystem paths together.
2047 *
2048 * For example, 'give me $path relative to $base'. If the $path is absolute,
2049 * then it the full path is returned.
2050 *
2051 * @since 2.5.0
2052 *
2053 * @param string $base Base path.
2054 * @param string $path Path relative to $base.
2055 * @return string The path with the base or absolute path.
2056 */
2057function path_join( $base, $path ) {
2058        if ( path_is_absolute( $path ) ) {
2059                return $path;
2060        }
2061
2062        return rtrim( $base, '/' ) . '/' . ltrim( $path, '/' );
2063}
2064
2065/**
2066 * Normalize a filesystem path.
2067 *
2068 * On windows systems, replaces backslashes with forward slashes
2069 * and forces upper-case drive letters.
2070 * Allows for two leading slashes for Windows network shares, but
2071 * ensures that all other duplicate slashes are reduced to a single.
2072 *
2073 * @since 3.9.0
2074 * @since 4.4.0 Ensures upper-case drive letters on Windows systems.
2075 * @since 4.5.0 Allows for Windows network shares.
2076 * @since 4.9.7 Allows for PHP file wrappers.
2077 *
2078 * @param string $path Path to normalize.
2079 * @return string Normalized path.
2080 */
2081function wp_normalize_path( $path ) {
2082        $wrapper = '';
2083
2084        if ( wp_is_stream( $path ) ) {
2085                list( $wrapper, $path ) = explode( '://', $path, 2 );
2086
2087                $wrapper .= '://';
2088        }
2089
2090        // Standardise all paths to use '/'.
2091        $path = str_replace( '\\', '/', $path );
2092
2093        // Replace multiple slashes down to a singular, allowing for network shares having two slashes.
2094        $path = preg_replace( '|(?<=.)/+|', '/', $path );
2095
2096        // Windows paths should uppercase the drive letter.
2097        if ( ':' === substr( $path, 1, 1 ) ) {
2098                $path = ucfirst( $path );
2099        }
2100
2101        return $wrapper . $path;
2102}
2103
2104/**
2105 * Determine a writable directory for temporary files.
2106 *
2107 * Function's preference is the return value of sys_get_temp_dir(),
2108 * followed by your PHP temporary upload directory, followed by WP_CONTENT_DIR,
2109 * before finally defaulting to /tmp/
2110 *
2111 * In the event that this function does not find a writable location,
2112 * It may be overridden by the WP_TEMP_DIR constant in your wp-config.php file.
2113 *
2114 * @since 2.5.0
2115 *
2116 * @staticvar string $temp
2117 *
2118 * @return string Writable temporary directory.
2119 */
2120function get_temp_dir() {
2121        static $temp = '';
2122        if ( defined( 'WP_TEMP_DIR' ) ) {
2123                return trailingslashit( WP_TEMP_DIR );
2124        }
2125
2126        if ( $temp ) {
2127                return trailingslashit( $temp );
2128        }
2129
2130        if ( function_exists( 'sys_get_temp_dir' ) ) {
2131                $temp = sys_get_temp_dir();
2132                if ( @is_dir( $temp ) && wp_is_writable( $temp ) ) {
2133                        return trailingslashit( $temp );
2134                }
2135        }
2136
2137        $temp = ini_get( 'upload_tmp_dir' );
2138        if ( @is_dir( $temp ) && wp_is_writable( $temp ) ) {
2139                return trailingslashit( $temp );
2140        }
2141
2142        $temp = WP_CONTENT_DIR . '/';
2143        if ( is_dir( $temp ) && wp_is_writable( $temp ) ) {
2144                return $temp;
2145        }
2146
2147        return '/tmp/';
2148}
2149
2150/**
2151 * Determine if a directory is writable.
2152 *
2153 * This function is used to work around certain ACL issues in PHP primarily
2154 * affecting Windows Servers.
2155 *
2156 * @since 3.6.0
2157 *
2158 * @see win_is_writable()
2159 *
2160 * @param string $path Path to check for write-ability.
2161 * @return bool Whether the path is writable.
2162 */
2163function wp_is_writable( $path ) {
2164        if ( 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) ) {
2165                return win_is_writable( $path );
2166        } else {
2167                return @is_writable( $path );
2168        }
2169}
2170
2171/**
2172 * Workaround for Windows bug in is_writable() function
2173 *
2174 * PHP has issues with Windows ACL's for determine if a
2175 * directory is writable or not, this works around them by
2176 * checking the ability to open files rather than relying
2177 * upon PHP to interprate the OS ACL.
2178 *
2179 * @since 2.8.0
2180 *
2181 * @see https://bugs.php.net/bug.php?id=27609
2182 * @see https://bugs.php.net/bug.php?id=30931
2183 *
2184 * @param string $path Windows path to check for write-ability.
2185 * @return bool Whether the path is writable.
2186 */
2187function win_is_writable( $path ) {
2188        if ( '/' === $path[ strlen( $path ) - 1 ] ) {
2189                // If it looks like a directory, check a random file within the directory.
2190                return win_is_writable( $path . uniqid( mt_rand() ) . '.tmp' );
2191        } elseif ( is_dir( $path ) ) {
2192                // If it's a directory (and not a file), check a random file within the directory.
2193                return win_is_writable( $path . '/' . uniqid( mt_rand() ) . '.tmp' );
2194        }
2195
2196        // Check tmp file for read/write capabilities.
2197        $should_delete_tmp_file = ! file_exists( $path );
2198
2199        $f = @fopen( $path, 'a' );
2200        if ( false === $f ) {
2201                return false;
2202        }
2203        fclose( $f );
2204
2205        if ( $should_delete_tmp_file ) {
2206                unlink( $path );
2207        }
2208
2209        return true;
2210}
2211
2212/**
2213 * Retrieves uploads directory information.
2214 *
2215 * Same as wp_upload_dir() but "light weight" as it doesn't attempt to create the uploads directory.
2216 * Intended for use in themes, when only 'basedir' and 'baseurl' are needed, generally in all cases
2217 * when not uploading files.
2218 *
2219 * @since 4.5.0
2220 *
2221 * @see wp_upload_dir()
2222 *
2223 * @return array See wp_upload_dir() for description.
2224 */
2225function wp_get_upload_dir() {
2226        return wp_upload_dir( null, false );
2227}
2228
2229/**
2230 * Returns an array containing the current upload directory's path and URL.
2231 *
2232 * Checks the 'upload_path' option, which should be from the web root folder,
2233 * and if it isn't empty it will be used. If it is empty, then the path will be
2234 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
2235 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
2236 *
2237 * The upload URL path is set either by the 'upload_url_path' option or by using
2238 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
2239 *
2240 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
2241 * the administration settings panel), then the time will be used. The format
2242 * will be year first and then month.
2243 *
2244 * If the path couldn't be created, then an error will be returned with the key
2245 * 'error' containing the error message. The error suggests that the parent
2246 * directory is not writable by the server.
2247 *
2248 * @since 2.0.0
2249 * @uses _wp_upload_dir()
2250 *
2251 * @staticvar array $cache
2252 * @staticvar array $tested_paths
2253 *
2254 * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
2255 * @param bool   $create_dir Optional. Whether to check and create the uploads directory.
2256 *                           Default true for backward compatibility.
2257 * @param bool   $refresh_cache Optional. Whether to refresh the cache. Default false.
2258 * @return array {
2259 *     Array of information about the upload directory.
2260 *
2261 *     @type string       $path    Base directory and subdirectory or full path to upload directory.
2262 *     @type string       $url     Base URL and subdirectory or absolute URL to upload directory.
2263 *     @type string       $subdir  Subdirectory if uploads use year/month folders option is on.
2264 *     @type string       $basedir Path without subdir.
2265 *     @type string       $baseurl URL path without subdir.
2266 *     @type string|false $error   False or error message.
2267 * }
2268 */
2269function wp_upload_dir( $time = null, $create_dir = true, $refresh_cache = false ) {
2270        static $cache = array(), $tested_paths = array();
2271
2272        $key = sprintf( '%d-%s', get_current_blog_id(), (string) $time );
2273
2274        if ( $refresh_cache || empty( $cache[ $key ] ) ) {
2275                $cache[ $key ] = _wp_upload_dir( $time );
2276        }
2277
2278        /**
2279         * Filters the uploads directory data.
2280         *
2281         * @since 2.0.0
2282         *
2283         * @param array $uploads {
2284         *     Array of information about the upload directory.
2285         *
2286         *     @type string       $path    Base directory and subdirectory or full path to upload directory.
2287         *     @type string       $url     Base URL and subdirectory or absolute URL to upload directory.
2288         *     @type string       $subdir  Subdirectory if uploads use year/month folders option is on.
2289         *     @type string       $basedir Path without subdir.
2290         *     @type string       $baseurl URL path without subdir.
2291         *     @type string|false $error   False or error message.
2292         * }
2293         */
2294        $uploads = apply_filters( 'upload_dir', $cache[ $key ] );
2295
2296        if ( $create_dir ) {
2297                $path = $uploads['path'];
2298
2299                if ( array_key_exists( $path, $tested_paths ) ) {
2300                        $uploads['error'] = $tested_paths[ $path ];
2301                } else {
2302                        if ( ! wp_mkdir_p( $path ) ) {
2303                                if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
2304                                        $error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
2305                                } else {
2306                                        $error_path = wp_basename( $uploads['basedir'] ) . $uploads['subdir'];
2307                                }
2308
2309                                $uploads['error'] = sprintf(
2310                                        /* translators: %s: Directory path. */
2311                                        __( 'Unable to create directory %s. Is its parent directory writable by the server?' ),
2312                                        esc_html( $error_path )
2313                                );
2314                        }
2315
2316                        $tested_paths[ $path ] = $uploads['error'];
2317                }
2318        }
2319
2320        return $uploads;
2321}
2322
2323/**
2324 * A non-filtered, non-cached version of wp_upload_dir() that doesn't check the path.
2325 *
2326 * @since 4.5.0
2327 * @access private
2328 *
2329 * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
2330 * @return array See wp_upload_dir()
2331 */
2332function _wp_upload_dir( $time = null ) {
2333        $siteurl     = get_option( 'siteurl' );
2334        $upload_path = trim( get_option( 'upload_path' ) );
2335
2336        if ( empty( $upload_path ) || 'wp-content/uploads' == $upload_path ) {
2337                $dir = WP_CONTENT_DIR . '/uploads';
2338        } elseif ( 0 !== strpos( $upload_path, ABSPATH ) ) {
2339                // $dir is absolute, $upload_path is (maybe) relative to ABSPATH.
2340                $dir = path_join( ABSPATH, $upload_path );
2341        } else {
2342                $dir = $upload_path;
2343        }
2344
2345        $url = get_option( 'upload_url_path' );
2346        if ( ! $url ) {
2347                if ( empty( $upload_path ) || ( 'wp-content/uploads' == $upload_path ) || ( $upload_path == $dir ) ) {
2348                        $url = WP_CONTENT_URL . '/uploads';
2349                } else {
2350                        $url = trailingslashit( $siteurl ) . $upload_path;
2351                }
2352        }
2353
2354        /*
2355         * Honor the value of UPLOADS. This happens as long as ms-files rewriting is disabled.
2356         * We also sometimes obey UPLOADS when rewriting is enabled -- see the next block.
2357         */
2358        if ( defined( 'UPLOADS' ) && ! ( is_multisite() && get_site_option( 'ms_files_rewriting' ) ) ) {
2359                $dir = ABSPATH . UPLOADS;
2360                $url = trailingslashit( $siteurl ) . UPLOADS;
2361        }
2362
2363        // If multisite (and if not the main site in a post-MU network).
2364        if ( is_multisite() && ! ( is_main_network() && is_main_site() && defined( 'MULTISITE' ) ) ) {
2365
2366                if ( ! get_site_option( 'ms_files_rewriting' ) ) {
2367                        /*
2368                         * If ms-files rewriting is disabled (networks created post-3.5), it is fairly
2369                         * straightforward: Append sites/%d if we're not on the main site (for post-MU
2370                         * networks). (The extra directory prevents a four-digit ID from conflicting with
2371                         * a year-based directory for the main site. But if a MU-era network has disabled
2372                         * ms-files rewriting manually, they don't need the extra directory, as they never
2373                         * had wp-content/uploads for the main site.)
2374                         */
2375
2376                        if ( defined( 'MULTISITE' ) ) {
2377                                $ms_dir = '/sites/' . get_current_blog_id();
2378                        } else {
2379                                $ms_dir = '/' . get_current_blog_id();
2380                        }
2381
2382                        $dir .= $ms_dir;
2383                        $url .= $ms_dir;
2384
2385                } elseif ( defined( 'UPLOADS' ) && ! ms_is_switched() ) {
2386                        /*
2387                         * Handle the old-form ms-files.php rewriting if the network still has that enabled.
2388                         * When ms-files rewriting is enabled, then we only listen to UPLOADS when:
2389                         * 1) We are not on the main site in a post-MU network, as wp-content/uploads is used
2390                         *    there, and
2391                         * 2) We are not switched, as ms_upload_constants() hardcodes these constants to reflect
2392                         *    the original blog ID.
2393                         *
2394                         * Rather than UPLOADS, we actually use BLOGUPLOADDIR if it is set, as it is absolute.
2395                         * (And it will be set, see ms_upload_constants().) Otherwise, UPLOADS can be used, as
2396                         * as it is relative to ABSPATH. For the final piece: when UPLOADS is used with ms-files
2397                         * rewriting in multisite, the resulting URL is /files. (#WP22702 for background.)
2398                         */
2399
2400                        if ( defined( 'BLOGUPLOADDIR' ) ) {
2401                                $dir = untrailingslashit( BLOGUPLOADDIR );
2402                        } else {
2403                                $dir = ABSPATH . UPLOADS;
2404                        }
2405                        $url = trailingslashit( $siteurl ) . 'files';
2406                }
2407        }
2408
2409        $basedir = $dir;
2410        $baseurl = $url;
2411
2412        $subdir = '';
2413        if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
2414                // Generate the yearly and monthly directories.
2415                if ( ! $time ) {
2416                        $time = current_time( 'mysql' );
2417                }
2418                $y      = substr( $time, 0, 4 );
2419                $m      = substr( $time, 5, 2 );
2420                $subdir = "/$y/$m";
2421        }
2422
2423        $dir .= $subdir;
2424        $url .= $subdir;
2425
2426        return array(
2427                'path'    => $dir,
2428                'url'     => $url,
2429                'subdir'  => $subdir,
2430                'basedir' => $basedir,
2431                'baseurl' => $baseurl,
2432                'error'   => false,
2433        );
2434}
2435
2436/**
2437 * Get a filename that is sanitized and unique for the given directory.
2438 *
2439 * If the filename is not unique, then a number will be added to the filename
2440 * before the extension, and will continue adding numbers until the filename is
2441 * unique.
2442 *
2443 * The callback is passed three parameters, the first one is the directory, the
2444 * second is the filename, and the third is the extension.
2445 *
2446 * @since 2.5.0
2447 *
2448 * @param string   $dir                      Directory.
2449 * @param string   $filename                 File name.
2450 * @param callable $unique_filename_callback Callback. Default null.
2451 * @return string New filename, if given wasn't unique.
2452 */
2453function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) {
2454        // Sanitize the file name before we begin processing.
2455        $filename = sanitize_file_name( $filename );
2456        $ext2     = null;
2457
2458        // Separate the filename into a name and extension.
2459        $ext  = pathinfo( $filename, PATHINFO_EXTENSION );
2460        $name = pathinfo( $filename, PATHINFO_BASENAME );
2461
2462        if ( $ext ) {
2463                $ext = '.' . $ext;
2464        }
2465
2466        // Edge case: if file is named '.ext', treat as an empty name.
2467        if ( $name === $ext ) {
2468                $name = '';
2469        }
2470
2471        /*
2472         * Increment the file number until we have a unique file to save in $dir.
2473         * Use callback if supplied.
2474         */
2475        if ( $unique_filename_callback && is_callable( $unique_filename_callback ) ) {
2476                $filename = call_user_func( $unique_filename_callback, $dir, $name, $ext );
2477        } else {
2478                $number = '';
2479                $fname  = pathinfo( $filename, PATHINFO_FILENAME );
2480
2481                // Always append a number to file names that can potentially match image sub-size file names.
2482                if ( $fname && preg_match( '/-(?:\d+x\d+|scaled|rotated)$/', $fname ) ) {
2483                        $number = 1;
2484
2485                        // At this point the file name may not be unique. This is tested below and the $number is incremented.
2486                        $filename = str_replace( "{$fname}{$ext}", "{$fname}-{$number}{$ext}", $filename );
2487                }
2488
2489                // Change '.ext' to lower case.
2490                if ( $ext && strtolower( $ext ) != $ext ) {
2491                        $ext2      = strtolower( $ext );
2492                        $filename2 = preg_replace( '|' . preg_quote( $ext ) . '$|', $ext2, $filename );
2493
2494                        // Check for both lower and upper case extension or image sub-sizes may be overwritten.
2495                        while ( file_exists( $dir . "/{$filename}" ) || file_exists( $dir . "/{$filename2}" ) ) {
2496                                $new_number = (int) $number + 1;
2497                                $filename   = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename );
2498                                $filename2  = str_replace( array( "-{$number}{$ext2}", "{$number}{$ext2}" ), "-{$new_number}{$ext2}", $filename2 );
2499                                $number     = $new_number;
2500                        }
2501
2502                        $filename = $filename2;
2503                } else {
2504                        while ( file_exists( $dir . "/{$filename}" ) ) {
2505                                $new_number = (int) $number + 1;
2506
2507                                if ( '' === "{$number}{$ext}" ) {
2508                                        $filename = "{$filename}-{$new_number}";
2509                                } else {
2510                                        $filename = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename );
2511                                }
2512
2513                                $number = $new_number;
2514                        }
2515                }
2516
2517                // Prevent collisions with existing file names that contain dimension-like strings
2518                // (whether they are subsizes or originals uploaded prior to #42437).
2519                $upload_dir = wp_get_upload_dir();
2520
2521                // The (resized) image files would have name and extension, and will be in the uploads dir.
2522                if ( $name && $ext && @is_dir( $dir ) && false !== strpos( $dir, $upload_dir['basedir'] ) ) {
2523                        // List of all files and directories contained in $dir.
2524                        $files = @scandir( $dir );
2525
2526                        if ( ! empty( $files ) ) {
2527                                // Remove "dot" dirs.
2528                                $files = array_diff( $files, array( '.', '..' ) );
2529                        }
2530
2531                        if ( ! empty( $files ) ) {
2532                                // The extension case may have changed above.
2533                                $new_ext = ! empty( $ext2 ) ? $ext2 : $ext;
2534
2535                                // Ensure this never goes into infinite loop
2536                                // as it uses pathinfo() and regex in the check, but string replacement for the changes.
2537                                $count = count( $files );
2538                                $i     = 0;
2539
2540                                while ( $i <= $count && _wp_check_existing_file_names( $filename, $files ) ) {
2541                                        $new_number = (int) $number + 1;
2542                                        $filename   = str_replace( array( "-{$number}{$new_ext}", "{$number}{$new_ext}" ), "-{$new_number}{$new_ext}", $filename );
2543                                        $number     = $new_number;
2544                                        $i++;
2545                                }
2546                        }
2547                }
2548        }
2549
2550        /**
2551         * Filters the result when generating a unique file name.
2552         *
2553         * @since 4.5.0
2554         *
2555         * @param string        $filename                 Unique file name.
2556         * @param string        $ext                      File extension, eg. ".png".
2557         * @param string        $dir                      Directory path.
2558         * @param callable|null $unique_filename_callback Callback function that generates the unique file name.
2559         */
2560        return apply_filters( 'wp_unique_filename', $filename, $ext, $dir, $unique_filename_callback );
2561}
2562
2563/**
2564 * Helper function to check if a file name could match an existing image sub-size file name.
2565 *
2566 * @since 5.3.1
2567 * @access private
2568 *
2569 * @param string $filename The file name to check.
2570 * $param array  $files    An array of existing files in the directory.
2571 * $return bool True if the tested file name could match an existing file, false otherwise.
2572 */
2573function _wp_check_existing_file_names( $filename, $files ) {
2574        $fname = pathinfo( $filename, PATHINFO_FILENAME );
2575        $ext   = pathinfo( $filename, PATHINFO_EXTENSION );
2576
2577        // Edge case, file names like `.ext`.
2578        if ( empty( $fname ) ) {
2579                return false;
2580        }
2581
2582        if ( $ext ) {
2583                $ext = ".$ext";
2584        }
2585
2586        $regex = '/^' . preg_quote( $fname ) . '-(?:\d+x\d+|scaled|rotated)' . preg_quote( $ext ) . '$/i';
2587
2588        foreach ( $files as $file ) {
2589                if ( preg_match( $regex, $file ) ) {
2590                        return true;
2591                }
2592        }
2593
2594        return false;
2595}
2596
2597/**
2598 * Create a file in the upload folder with given content.
2599 *
2600 * If there is an error, then the key 'error' will exist with the error message.
2601 * If success, then the key 'file' will have the unique file path, the 'url' key
2602 * will have the link to the new file. and the 'error' key will be set to false.
2603 *
2604 * This function will not move an uploaded file to the upload folder. It will
2605 * create a new file with the content in $bits parameter. If you move the upload
2606 * file, read the content of the uploaded file, and then you can give the
2607 * filename and content to this function, which will add it to the upload
2608 * folder.
2609 *
2610 * The permissions will be set on the new file automatically by this function.
2611 *
2612 * @since 2.0.0
2613 *
2614 * @param string       $name       Filename.
2615 * @param null|string  $deprecated Never used. Set to null.
2616 * @param string       $bits       File content
2617 * @param string       $time       Optional. Time formatted in 'yyyy/mm'. Default null.
2618 * @return array
2619 */
2620function wp_upload_bits( $name, $deprecated, $bits, $time = null ) {
2621        if ( ! empty( $deprecated ) ) {
2622                _deprecated_argument( __FUNCTION__, '2.0.0' );
2623        }
2624
2625        if ( empty( $name ) ) {
2626                return array( 'error' => __( 'Empty filename' ) );
2627        }
2628
2629        $wp_filetype = wp_check_filetype( $name );
2630        if ( ! $wp_filetype['ext'] && ! current_user_can( 'unfiltered_upload' ) ) {
2631                return array( 'error' => __( 'Sorry, this file type is not permitted for security reasons.' ) );
2632        }
2633
2634        $upload = wp_upload_dir( $time );
2635
2636        if ( false !== $upload['error'] ) {
2637                return $upload;
2638        }
2639
2640        /**
2641         * Filters whether to treat the upload bits as an error.
2642         *
2643         * Returning a non-array from the filter will effectively short-circuit preparing the upload
2644         * bits, returning that value instead. An error message should be returned as a string.
2645         *
2646         * @since 3.0.0
2647         *
2648         * @param array|string $upload_bits_error An array of upload bits data, or error message to return.
2649         */
2650        $upload_bits_error = apply_filters(
2651                'wp_upload_bits',
2652                array(
2653                        'name' => $name,
2654                        'bits' => $bits,
2655                        'time' => $time,
2656                )
2657        );
2658        if ( ! is_array( $upload_bits_error ) ) {
2659                $upload['error'] = $upload_bits_error;
2660                return $upload;
2661        }
2662
2663        $filename = wp_unique_filename( $upload['path'], $name );
2664
2665        $new_file = $upload['path'] . "/$filename";
2666        if ( ! wp_mkdir_p( dirname( $new_file ) ) ) {
2667                if ( 0 === strpos( $upload['basedir'], ABSPATH ) ) {
2668                        $error_path = str_replace( ABSPATH, '', $upload['basedir'] ) . $upload['subdir'];
2669                } else {
2670                        $error_path = wp_basename( $upload['basedir'] ) . $upload['subdir'];
2671                }
2672
2673                $message = sprintf(
2674                        /* translators: %s: Directory path. */
2675                        __( 'Unable to create directory %s. Is its parent directory writable by the server?' ),
2676                        $error_path
2677                );
2678                return array( 'error' => $message );
2679        }
2680
2681        $ifp = @fopen( $new_file, 'wb' );
2682        if ( ! $ifp ) {
2683                return array(
2684                        /* translators: %s: File name. */
2685                        'error' => sprintf( __( 'Could not write file %s' ), $new_file ),
2686                );
2687        }
2688
2689        fwrite( $ifp, $bits );
2690        fclose( $ifp );
2691        clearstatcache();
2692
2693        // Set correct file permissions.
2694        $stat  = @ stat( dirname( $new_file ) );
2695        $perms = $stat['mode'] & 0007777;
2696        $perms = $perms & 0000666;
2697        chmod( $new_file, $perms );
2698        clearstatcache();
2699
2700        // Compute the URL.
2701        $url = $upload['url'] . "/$filename";
2702
2703        /** This filter is documented in wp-admin/includes/file.php */
2704        return apply_filters(
2705                'wp_handle_upload',
2706                array(
2707                        'file'  => $new_file,
2708                        'url'   => $url,
2709                        'type'  => $wp_filetype['type'],
2710                        'error' => false,
2711                ),
2712                'sideload'
2713        );
2714}
2715
2716/**
2717 * Retrieve the file type based on the extension name.
2718 *
2719 * @since 2.5.0
2720 *
2721 * @param string $ext The extension to search.
2722 * @return string|void The file type, example: audio, video, document, spreadsheet, etc.
2723 */
2724function wp_ext2type( $ext ) {
2725        $ext = strtolower( $ext );
2726
2727        $ext2type = wp_get_ext_types();
2728        foreach ( $ext2type as $type => $exts ) {
2729                if ( in_array( $ext, $exts ) ) {
2730                        return $type;
2731                }
2732        }
2733}
2734
2735/**
2736 * Retrieve the file type from the file name.
2737 *
2738 * You can optionally define the mime array, if needed.
2739 *
2740 * @since 2.0.4
2741 *
2742 * @param string   $filename File name or path.
2743 * @param string[] $mimes    Optional. Array of mime types keyed by their file extension regex.
2744 * @return array {
2745 *     Values for the extension and mime type.
2746 *
2747 *     @type string|false $ext  File extension, or false if the file doesn't match a mime type.
2748 *     @type string|false $type File mime type, or false if the file doesn't match a mime type.
2749 * }
2750 */
2751function wp_check_filetype( $filename, $mimes = null ) {
2752        if ( empty( $mimes ) ) {
2753                $mimes = get_allowed_mime_types();
2754        }
2755        $type = false;
2756        $ext  = false;
2757
2758        foreach ( $mimes as $ext_preg => $mime_match ) {
2759                $ext_preg = '!\.(' . $ext_preg . ')$!i';
2760                if ( preg_match( $ext_preg, $filename, $ext_matches ) ) {
2761                        $type = $mime_match;
2762                        $ext  = $ext_matches[1];
2763                        break;
2764                }
2765        }
2766
2767        return compact( 'ext', 'type' );
2768}
2769
2770/**
2771 * Attempt to determine the real file type of a file.
2772 *
2773 * If unable to, the file name extension will be used to determine type.
2774 *
2775 * If it's determined that the extension does not match the file's real type,
2776 * then the "proper_filename" value will be set with a proper filename and extension.
2777 *
2778 * Currently this function only supports renaming images validated via wp_get_image_mime().
2779 *
2780 * @since 3.0.0
2781 *
2782 * @param string   $file     Full path to the file.
2783 * @param string   $filename The name of the file (may differ from $file due to $file being
2784 *                           in a tmp directory).
2785 * @param string[] $mimes    Optional. Array of mime types keyed by their file extension regex.
2786 * @return array {
2787 *     Values for the extension, mime type, and corrected filename.
2788 *
2789 *     @type string|false $ext             File extension, or false if the file doesn't match a mime type.
2790 *     @type string|false $type            File mime type, or false if the file doesn't match a mime type.
2791 *     @type string|false $proper_filename File name with its correct extension, or false if it cannot be determined.
2792 * }
2793 */
2794function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) {
2795        $proper_filename = false;
2796
2797        // Do basic extension validation and MIME mapping.
2798        $wp_filetype = wp_check_filetype( $filename, $mimes );
2799        $ext         = $wp_filetype['ext'];
2800        $type        = $wp_filetype['type'];
2801
2802        // We can't do any further validation without a file to work with.
2803        if ( ! file_exists( $file ) ) {
2804                return compact( 'ext', 'type', 'proper_filename' );
2805        }
2806
2807        $real_mime = false;
2808
2809        // Validate image types.
2810        if ( $type && 0 === strpos( $type, 'image/' ) ) {
2811
2812                // Attempt to figure out what type of image it actually is.
2813                $real_mime = wp_get_image_mime( $file );
2814
2815                if ( $real_mime && $real_mime != $type ) {
2816                        /**
2817                         * Filters the list mapping image mime types to their respective extensions.
2818                         *
2819                         * @since 3.0.0
2820                         *
2821                         * @param  array $mime_to_ext Array of image mime types and their matching extensions.
2822                         */
2823                        $mime_to_ext = apply_filters(
2824                                'getimagesize_mimes_to_exts',
2825                                array(
2826                                        'image/jpeg' => 'jpg',
2827                                        'image/png'  => 'png',
2828                                        'image/gif'  => 'gif',
2829                                        'image/bmp'  => 'bmp',
2830                                        'image/tiff' => 'tif',
2831                                )
2832                        );
2833
2834                        // Replace whatever is after the last period in the filename with the correct extension.
2835                        if ( ! empty( $mime_to_ext[ $real_mime ] ) ) {
2836                                $filename_parts = explode( '.', $filename );
2837                                array_pop( $filename_parts );
2838                                $filename_parts[] = $mime_to_ext[ $real_mime ];
2839                                $new_filename     = implode( '.', $filename_parts );
2840
2841                                if ( $new_filename != $filename ) {
2842                                        $proper_filename = $new_filename; // Mark that it changed.
2843                                }
2844                                // Redefine the extension / MIME.
2845                                $wp_filetype = wp_check_filetype( $new_filename, $mimes );
2846                                $ext         = $wp_filetype['ext'];
2847                                $type        = $wp_filetype['type'];
2848                        } else {
2849                                // Reset $real_mime and try validating again.
2850                                $real_mime = false;
2851                        }
2852                }
2853        }
2854
2855        // Validate files that didn't get validated during previous checks.
2856        if ( $type && ! $real_mime && extension_loaded( 'fileinfo' ) ) {
2857                $finfo     = finfo_open( FILEINFO_MIME_TYPE );
2858                $real_mime = finfo_file( $finfo, $file );
2859                finfo_close( $finfo );
2860
2861                // fileinfo often misidentifies obscure files as one of these types.
2862                $nonspecific_types = array(
2863                        'application/octet-stream',
2864                        'application/encrypted',
2865                        'application/CDFV2-encrypted',
2866                        'application/zip',
2867                );
2868
2869                /*
2870                 * If $real_mime doesn't match the content type we're expecting from the file's extension,
2871                 * we need to do some additional vetting. Media types and those listed in $nonspecific_types are
2872                 * allowed some leeway, but anything else must exactly match the real content type.
2873                 */
2874                if ( in_array( $real_mime, $nonspecific_types, true ) ) {
2875                        // File is a non-specific binary type. That's ok if it's a type that generally tends to be binary.
2876                        if ( ! in_array( substr( $type, 0, strcspn( $type, '/' ) ), array( 'application', 'video', 'audio' ) ) ) {
2877                                $type = false;
2878                                $ext  = false;
2879                        }
2880                } elseif ( 0 === strpos( $real_mime, 'video/' ) || 0 === strpos( $real_mime, 'audio/' ) ) {
2881                        /*
2882                         * For these types, only the major type must match the real value.
2883                         * This means that common mismatches are forgiven: application/vnd.apple.numbers is often misidentified as application/zip,
2884                         * and some media files are commonly named with the wrong extension (.mov instead of .mp4)
2885                         */
2886                        if ( substr( $real_mime, 0, strcspn( $real_mime, '/' ) ) !== substr( $type, 0, strcspn( $type, '/' ) ) ) {
2887                                $type = false;
2888                                $ext  = false;
2889                        }
2890                } elseif ( 'text/plain' === $real_mime ) {
2891                        // A few common file types are occasionally detected as text/plain; allow those.
2892                        if ( ! in_array(
2893                                $type,
2894                                array(
2895                                        'text/plain',
2896                                        'text/csv',
2897                                        'text/richtext',
2898                                        'text/tsv',
2899                                        'text/vtt',
2900                                )
2901                        )
2902                        ) {
2903                                $type = false;
2904                                $ext  = false;
2905                        }
2906                } elseif ( 'text/rtf' === $real_mime ) {
2907                        // Special casing for RTF files.
2908                        if ( ! in_array(
2909                                $type,
2910                                array(
2911                                        'text/rtf',
2912                                        'text/plain',
2913                                        'application/rtf',
2914                                )
2915                        )
2916                        ) {
2917                                $type = false;
2918                                $ext  = false;
2919                        }
2920                } else {
2921                        if ( $type !== $real_mime ) {
2922                                /*
2923                                 * Everything else including image/* and application/*:
2924                                 * If the real content type doesn't match the file extension, assume it's dangerous.
2925                                 */
2926                                $type = false;
2927                                $ext  = false;
2928                        }
2929                }
2930        }
2931
2932        // The mime type must be allowed.
2933        if ( $type ) {
2934                $allowed = get_allowed_mime_types();
2935
2936                if ( ! in_array( $type, $allowed ) ) {
2937                        $type = false;
2938                        $ext  = false;
2939                }
2940        }
2941
2942        /**
2943         * Filters the "real" file type of the given file.
2944         *
2945         * @since 3.0.0
2946         * @since 5.1.0 The $real_mime parameter was added.
2947         *
2948         * @param array       $wp_check_filetype_and_ext {
2949         *     Values for the extension, mime type, and corrected filename.
2950         *
2951         *     @type string|false $ext             File extension, or false if the file doesn't match a mime type.
2952         *     @type string|false $type            File mime type, or false if the file doesn't match a mime type.
2953         *     @type string|false $proper_filename File name with its correct extension, or false if it cannot be determined.
2954         * }
2955         * @param string      $file                      Full path to the file.
2956         * @param string      $filename                  The name of the file (may differ from $file due to
2957         *                                               $file being in a tmp directory).
2958         * @param string[]    $mimes                     Array of mime types keyed by their file extension regex.
2959         * @param string|bool $real_mime                 The actual mime type or false if the type cannot be determined.
2960         */
2961        return apply_filters( 'wp_check_filetype_and_ext', compact( 'ext', 'type', 'proper_filename' ), $file, $filename, $mimes, $real_mime );
2962}
2963
2964/**
2965 * Returns the real mime type of an image file.
2966 *
2967 * This depends on exif_imagetype() or getimagesize() to determine real mime types.
2968 *
2969 * @since 4.7.1
2970 *
2971 * @param string $file Full path to the file.
2972 * @return string|false The actual mime type or false if the type cannot be determined.
2973 */
2974function wp_get_image_mime( $file ) {
2975        /*
2976         * Use exif_imagetype() to check the mimetype if available or fall back to
2977         * getimagesize() if exif isn't avaialbe. If either function throws an Exception
2978         * we assume the file could not be validated.
2979         */
2980        try {
2981                if ( is_callable( 'exif_imagetype' ) ) {
2982                        $imagetype = exif_imagetype( $file );
2983                        $mime      = ( $imagetype ) ? image_type_to_mime_type( $imagetype ) : false;
2984                } elseif ( function_exists( 'getimagesize' ) ) {
2985                        $imagesize = @getimagesize( $file );
2986                        $mime      = ( isset( $imagesize['mime'] ) ) ? $imagesize['mime'] : false;
2987                } else {
2988                        $mime = false;
2989                }
2990        } catch ( Exception $e ) {
2991                $mime = false;
2992        }
2993
2994        return $mime;
2995}
2996
2997/**
2998 * Retrieve list of mime types and file extensions.
2999 *
3000 * @since 3.5.0
3001 * @since 4.2.0 Support was added for GIMP (.xcf) files.
3002 * @since 4.9.2 Support was added for Flac (.flac) files.
3003 * @since 4.9.6 Support was added for AAC (.aac) files.
3004 *
3005 * @return string[] Array of mime types keyed by the file extension regex corresponding to those types.
3006 */
3007function wp_get_mime_types() {
3008        /**
3009         * Filters the list of mime types and file extensions.
3010         *
3011         * This filter should be used to add, not remove, mime types. To remove
3012         * mime types, use the {@see 'upload_mimes'} filter.
3013         *
3014         * @since 3.5.0
3015         *
3016         * @param string[] $wp_get_mime_types Mime types keyed by the file extension regex
3017         *                                 corresponding to those types.
3018         */
3019        return apply_filters(
3020                'mime_types',
3021                array(
3022                        // Image formats.
3023                        'jpg|jpeg|jpe'                 => 'image/jpeg',
3024                        'gif'                          => 'image/gif',
3025                        'png'                          => 'image/png',
3026                        'bmp'                          => 'image/bmp',
3027                        'tiff|tif'                     => 'image/tiff',
3028                        'ico'                          => 'image/x-icon',
3029                        // Video formats.
3030                        'asf|asx'                      => 'video/x-ms-asf',
3031                        'wmv'                          => 'video/x-ms-wmv',
3032                        'wmx'                          => 'video/x-ms-wmx',
3033                        'wm'                           => 'video/x-ms-wm',
3034                        'avi'                          => 'video/avi',
3035                        'divx'                         => 'video/divx',
3036                        'flv'                          => 'video/x-flv',
3037                        'mov|qt'                       => 'video/quicktime',
3038                        'mpeg|mpg|mpe'                 => 'video/mpeg',
3039                        'mp4|m4v'                      => 'video/mp4',
3040                        'ogv'                          => 'video/ogg',
3041                        'webm'                         => 'video/webm',
3042                        'mkv'                          => 'video/x-matroska',
3043                        '3gp|3gpp'                     => 'video/3gpp',  // Can also be audio.
3044                        '3g2|3gp2'                     => 'video/3gpp2', // Can also be audio.
3045                        // Text formats.
3046                        'txt|asc|c|cc|h|srt'           => 'text/plain',
3047                        'csv'                          => 'text/csv',
3048                        'tsv'                          => 'text/tab-separated-values',
3049                        'ics'                          => 'text/calendar',
3050                        'rtx'                          => 'text/richtext',
3051                        'css'                          => 'text/css',
3052                        'htm|html'                     => 'text/html',
3053                        'vtt'                          => 'text/vtt',
3054                        'dfxp'                         => 'application/ttaf+xml',
3055                        // Audio formats.
3056                        'mp3|m4a|m4b'                  => 'audio/mpeg',
3057                        'aac'                          => 'audio/aac',
3058                        'ra|ram'                       => 'audio/x-realaudio',
3059                        'wav'                          => 'audio/wav',
3060                        'ogg|oga'                      => 'audio/ogg',
3061                        'flac'                         => 'audio/flac',
3062                        'mid|midi'                     => 'audio/midi',
3063                        'wma'                          => 'audio/x-ms-wma',
3064                        'wax'                          => 'audio/x-ms-wax',
3065                        'mka'                          => 'audio/x-matroska',
3066                        // Misc application formats.
3067                        'rtf'                          => 'application/rtf',
3068                        'js'                           => 'application/javascript',
3069                        'pdf'                          => 'application/pdf',
3070                        'swf'                          => 'application/x-shockwave-flash',
3071                        'class'                        => 'application/java',
3072                        'tar'                          => 'application/x-tar',
3073                        'zip'                          => 'application/zip',
3074                        'gz|gzip'                      => 'application/x-gzip',
3075                        'rar'                          => 'application/rar',
3076                        '7z'                           => 'application/x-7z-compressed',
3077                        'exe'                          => 'application/x-msdownload',
3078                        'psd'                          => 'application/octet-stream',
3079                        'xcf'                          => 'application/octet-stream',
3080                        // MS Office formats.
3081                        'doc'                          => 'application/msword',
3082                        'pot|pps|ppt'                  => 'application/vnd.ms-powerpoint',
3083                        'wri'                          => 'application/vnd.ms-write',
3084                        'xla|xls|xlt|xlw'              => 'application/vnd.ms-excel',
3085                        'mdb'                          => 'application/vnd.ms-access',
3086                        'mpp'                          => 'application/vnd.ms-project',
3087                        'docx'                         => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
3088                        'docm'                         => 'application/vnd.ms-word.document.macroEnabled.12',
3089                        'dotx'                         => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
3090                        'dotm'                         => 'application/vnd.ms-word.template.macroEnabled.12',
3091                        'xlsx'                         => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
3092                        'xlsm'                         => 'application/vnd.ms-excel.sheet.macroEnabled.12',
3093                        'xlsb'                         => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
3094                        'xltx'                         => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
3095                        'xltm'                         => 'application/vnd.ms-excel.template.macroEnabled.12',
3096                        'xlam'                         => 'application/vnd.ms-excel.addin.macroEnabled.12',
3097                        'pptx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
3098                        'pptm'                         => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
3099                        'ppsx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
3100                        'ppsm'                         => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
3101                        'potx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.template',
3102                        'potm'                         => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
3103                        'ppam'                         => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
3104                        'sldx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
3105                        'sldm'                         => 'application/vnd.ms-powerpoint.slide.macroEnabled.12',
3106                        'onetoc|onetoc2|onetmp|onepkg' => 'application/onenote',
3107                        'oxps'                         => 'application/oxps',
3108                        'xps'                          => 'application/vnd.ms-xpsdocument',
3109                        // OpenOffice formats.
3110                        'odt'                          => 'application/vnd.oasis.opendocument.text',
3111                        'odp'                          => 'application/vnd.oasis.opendocument.presentation',
3112                        'ods'                          => 'application/vnd.oasis.opendocument.spreadsheet',
3113                        'odg'                          => 'application/vnd.oasis.opendocument.graphics',
3114                        'odc'                          => 'application/vnd.oasis.opendocument.chart',
3115                        'odb'                          => 'application/vnd.oasis.opendocument.database',
3116                        'odf'                          => 'application/vnd.oasis.opendocument.formula',
3117                        // WordPerfect formats.
3118                        'wp|wpd'                       => 'application/wordperfect',
3119                        // iWork formats.
3120                        'key'                          => 'application/vnd.apple.keynote',
3121                        'numbers'                      => 'application/vnd.apple.numbers',
3122                        'pages'                        => 'application/vnd.apple.pages',
3123                )
3124        );
3125}
3126
3127/**
3128 * Retrieves the list of common file extensions and their types.
3129 *
3130 * @since 4.6.0
3131 *
3132 * @return array[] Multi-dimensional array of file extensions types keyed by the type of file.
3133 */
3134function wp_get_ext_types() {
3135
3136        /**
3137         * Filters file type based on the extension name.
3138         *
3139         * @since 2.5.0
3140         *
3141         * @see wp_ext2type()
3142         *
3143         * @param array[] $ext2type Multi-dimensional array of file extensions types keyed by the type of file.
3144         */
3145        return apply_filters(
3146                'ext2type',
3147                array(
3148                        'image'       => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico' ),
3149                        'audio'       => array( 'aac', 'ac3', 'aif', 'aiff', 'flac', 'm3a', 'm4a', 'm4b', 'mka', 'mp1', 'mp2', 'mp3', 'ogg', 'oga', 'ram', 'wav', 'wma' ),
3150                        'video'       => array( '3g2', '3gp', '3gpp', 'asf', 'avi', 'divx', 'dv', 'flv', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'mpv', 'ogm', 'ogv', 'qt', 'rm', 'vob', 'wmv' ),
3151                        'document'    => array( 'doc', 'docx', 'docm', 'dotm', 'odt', 'pages', 'pdf', 'xps', 'oxps', 'rtf', 'wp', 'wpd', 'psd', 'xcf' ),
3152                        'spreadsheet' => array( 'numbers', 'ods', 'xls', 'xlsx', 'xlsm', 'xlsb' ),
3153                        'interactive' => array( 'swf', 'key', 'ppt', 'pptx', 'pptm', 'pps', 'ppsx', 'ppsm', 'sldx', 'sldm', 'odp' ),
3154                        'text'        => array( 'asc', 'csv', 'tsv', 'txt' ),
3155                        'archive'     => array( 'bz2', 'cab', 'dmg', 'gz', 'rar', 'sea', 'sit', 'sqx', 'tar', 'tgz', 'zip', '7z' ),
3156                        'code'        => array( 'css', 'htm', 'html', 'php', 'js' ),
3157                )
3158        );
3159}
3160
3161/**
3162 * Retrieve list of allowed mime types and file extensions.
3163 *
3164 * @since 2.8.6
3165 *
3166 * @param int|WP_User $user Optional. User to check. Defaults to current user.
3167 * @return string[] Array of mime types keyed by the file extension regex corresponding
3168 *                  to those types.
3169 */
3170function get_allowed_mime_types( $user = null ) {
3171        $t = wp_get_mime_types();
3172
3173        unset( $t['swf'], $t['exe'] );
3174        if ( function_exists( 'current_user_can' ) ) {
3175                $unfiltered = $user ? user_can( $user, 'unfiltered_html' ) : current_user_can( 'unfiltered_html' );
3176        }
3177
3178        if ( empty( $unfiltered ) ) {
3179                unset( $t['htm|html'], $t['js'] );
3180        }
3181
3182        /**
3183         * Filters list of allowed mime types and file extensions.
3184         *
3185         * @since 2.0.0
3186         *
3187         * @param array            $t    Mime types keyed by the file extension regex corresponding to those types.
3188         * @param int|WP_User|null $user User ID, User object or null if not provided (indicates current user).
3189         */
3190        return apply_filters( 'upload_mimes', $t, $user );
3191}
3192
3193/**
3194 * Display "Are You Sure" message to confirm the action being taken.
3195 *
3196 * If the action has the nonce explain message, then it will be displayed
3197 * along with the "Are you sure?" message.
3198 *
3199 * @since 2.0.4
3200 *
3201 * @param string $action The nonce action.
3202 */
3203function wp_nonce_ays( $action ) {
3204        if ( 'log-out' == $action ) {
3205                $html = sprintf(
3206                        /* translators: %s: Site title. */
3207                        __( 'You are attempting to log out of %s' ),
3208                        get_bloginfo( 'name' )
3209                );
3210                $html       .= '</p><p>';
3211                $redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : '';
3212                $html       .= sprintf(
3213                        /* translators: %s: Logout URL. */
3214                        __( 'Do you really want to <a href="%s">log out</a>?' ),
3215                        wp_logout_url( $redirect_to )
3216                );
3217        } else {
3218                $html = __( 'The link you followed has expired.' );
3219                if ( wp_get_referer() ) {
3220                        $html .= '</p><p>';
3221                        $html .= sprintf(
3222                                '<a href="%s">%s</a>',
3223                                esc_url( remove_query_arg( 'updated', wp_get_referer() ) ),
3224                                __( 'Please try again.' )
3225                        );
3226                }
3227        }
3228
3229        wp_die( $html, __( 'Something went wrong.' ), 403 );
3230}
3231
3232/**
3233 * Kills WordPress execution and displays HTML page with an error message.
3234 *
3235 * This function complements the `die()` PHP function. The difference is that
3236 * HTML will be displayed to the user. It is recommended to use this function
3237 * only when the execution should not continue any further. It is not recommended
3238 * to call this function very often, and try to handle as many errors as possible
3239 * silently or more gracefully.
3240 *
3241 * As a shorthand, the desired HTTP response code may be passed as an integer to
3242 * the `$title` parameter (the default title would apply) or the `$args` parameter.
3243 *
3244 * @since 2.0.4
3245 * @since 4.1.0 The `$title` and `$args` parameters were changed to optionally accept
3246 *              an integer to be used as the response code.
3247 * @since 5.1.0 The `$link_url`, `$link_text`, and `$exit` arguments were added.
3248 * @since 5.3.0 The `$charset` argument was added.
3249 *
3250 * @global WP_Query $wp_query WordPress Query object.
3251 *
3252 * @param string|WP_Error  $message Optional. Error message. If this is a WP_Error object,
3253 *                                  and not an Ajax or XML-RPC request, the error's messages are used.
3254 *                                  Default empty.
3255 * @param string|int       $title   Optional. Error title. If `$message` is a `WP_Error` object,
3256 *                                  error data with the key 'title' may be used to specify the title.
3257 *                                  If `$title` is an integer, then it is treated as the response
3258 *                                  code. Default empty.
3259 * @param string|array|int $args {
3260 *     Optional. Arguments to control behavior. If `$args` is an integer, then it is treated
3261 *     as the response code. Default empty array.
3262 *
3263 *     @type int    $response       The HTTP response code. Default 200 for Ajax requests, 500 otherwise.
3264 *     @type string $link_url       A URL to include a link to. Only works in combination with $link_text.
3265 *                                  Default empty string.
3266 *     @type string $link_text      A label for the link to include. Only works in combination with $link_url.
3267 *                                  Default empty string.
3268 *     @type bool   $back_link      Whether to include a link to go back. Default false.
3269 *     @type string $text_direction The text direction. This is only useful internally, when WordPress
3270 *                                  is still loading and the site's locale is not set up yet. Accepts 'rtl'.
3271 *                                  Default is the value of is_rtl().
3272 *     @type string $charset        Character set of the HTML output. Default 'utf-8'.
3273 *     @type string $code           Error code to use. Default is 'wp_die', or the main error code if $message
3274 *                                  is a WP_Error.
3275 *     @type bool   $exit           Whether to exit the process after completion. Default true.
3276 * }
3277 */
3278function wp_die( $message = '', $title = '', $args = array() ) {
3279        global $wp_query;
3280
3281        if ( is_int( $args ) ) {
3282                $args = array( 'response' => $args );
3283        } elseif ( is_int( $title ) ) {
3284                $args  = array( 'response' => $title );
3285                $title = '';
3286        }
3287
3288        if ( wp_doing_ajax() ) {
3289                /**
3290                 * Filters the callback for killing WordPress execution for Ajax requests.
3291                 *
3292                 * @since 3.4.0
3293                 *
3294                 * @param callable $function Callback function name.
3295                 */
3296                $function = apply_filters( 'wp_die_ajax_handler', '_ajax_wp_die_handler' );
3297        } elseif ( wp_is_json_request() ) {
3298                /**
3299                 * Filters the callback for killing WordPress execution for JSON requests.
3300                 *
3301                 * @since 5.1.0
3302                 *
3303                 * @param callable $function Callback function name.
3304                 */
3305                $function = apply_filters( 'wp_die_json_handler', '_json_wp_die_handler' );
3306        } elseif ( wp_is_jsonp_request() ) {
3307                /**
3308                 * Filters the callback for killing WordPress execution for JSONP requests.
3309                 *
3310                 * @since 5.2.0
3311                 *
3312                 * @param callable $function Callback function name.
3313                 */
3314                $function = apply_filters( 'wp_die_jsonp_handler', '_jsonp_wp_die_handler' );
3315        } elseif ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
3316                /**
3317                 * Filters the callback for killing WordPress execution for XML-RPC requests.
3318                 *
3319                 * @since 3.4.0
3320                 *
3321                 * @param callable $function Callback function name.
3322                 */
3323                $function = apply_filters( 'wp_die_xmlrpc_handler', '_xmlrpc_wp_die_handler' );
3324        } elseif ( wp_is_xml_request()
3325                || isset( $wp_query ) &&
3326                        ( function_exists( 'is_feed' ) && is_feed()
3327                        || function_exists( 'is_comment_feed' ) && is_comment_feed()
3328                        || function_exists( 'is_trackback' ) && is_trackback() ) ) {
3329                /**
3330                 * Filters the callback for killing WordPress execution for XML requests.
3331                 *
3332                 * @since 5.2.0
3333                 *
3334                 * @param callable $function Callback function name.
3335                 */
3336                $function = apply_filters( 'wp_die_xml_handler', '_xml_wp_die_handler' );
3337        } else {
3338                /**
3339                 * Filters the callback for killing WordPress execution for all non-Ajax, non-JSON, non-XML requests.
3340                 *
3341                 * @since 3.0.0
3342                 *
3343                 * @param callable $function Callback function name.
3344                 */
3345                $function = apply_filters( 'wp_die_handler', '_default_wp_die_handler' );
3346        }
3347
3348        call_user_func( $function, $message, $title, $args );
3349}
3350
3351/**
3352 * Kills WordPress execution and displays HTML page with an error message.
3353 *
3354 * This is the default handler for wp_die(). If you want a custom one,
3355 * you can override this using the {@see 'wp_die_handler'} filter in wp_die().
3356 *
3357 * @since 3.0.0
3358 * @access private
3359 *
3360 * @param string|WP_Error $message Error message or WP_Error object.
3361 * @param string          $title   Optional. Error title. Default empty.
3362 * @param string|array    $args    Optional. Arguments to control behavior. Default empty array.
3363 */
3364function _default_wp_die_handler( $message, $title = '', $args = array() ) {
3365        list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3366
3367        if ( is_string( $message ) ) {
3368                if ( ! empty( $parsed_args['additional_errors'] ) ) {
3369                        $message = array_merge(
3370                                array( $message ),
3371                                wp_list_pluck( $parsed_args['additional_errors'], 'message' )
3372                        );
3373                        $message = "<ul>\n\t\t<li>" . join( "</li>\n\t\t<li>", $message ) . "</li>\n\t</ul>";
3374                }
3375
3376                $message = sprintf(
3377                        '<div class="wp-die-message">%s</div>',
3378                        $message
3379                );
3380        }
3381
3382        $have_gettext = function_exists( '__' );
3383
3384        if ( ! empty( $parsed_args['link_url'] ) && ! empty( $parsed_args['link_text'] ) ) {
3385                $link_url = $parsed_args['link_url'];
3386                if ( function_exists( 'esc_url' ) ) {
3387                        $link_url = esc_url( $link_url );
3388                }
3389                $link_text = $parsed_args['link_text'];
3390                $message  .= "\n<p><a href='{$link_url}'>{$link_text}</a></p>";
3391        }
3392
3393        if ( isset( $parsed_args['back_link'] ) && $parsed_args['back_link'] ) {
3394                $back_text = $have_gettext ? __( '&laquo; Back' ) : '&laquo; Back';
3395                $message  .= "\n<p><a href='javascript:history.back()'>$back_text</a></p>";
3396        }
3397
3398        if ( ! did_action( 'admin_head' ) ) :
3399                if ( ! headers_sent() ) {
3400                        header( "Content-Type: text/html; charset={$parsed_args['charset']}" );
3401                        status_header( $parsed_args['response'] );
3402                        nocache_headers();
3403                }
3404
3405                $text_direction = $parsed_args['text_direction'];
3406                if ( function_exists( 'language_attributes' ) && function_exists( 'is_rtl' ) ) {
3407                        $dir_attr = get_language_attributes();
3408                } else {
3409                        $dir_attr = "dir='$text_direction'";
3410                }
3411                ?>
3412<!DOCTYPE html>
3413<html xmlns="http://www.w3.org/1999/xhtml" <?php echo $dir_attr; ?>>
3414<head>
3415        <meta http-equiv="Content-Type" content="text/html; charset=<?php echo $parsed_args['charset']; ?>" />
3416        <meta name="viewport" content="width=device-width">
3417                <?php
3418                if ( function_exists( 'wp_no_robots' ) ) {
3419                        wp_no_robots();
3420                }
3421                ?>
3422        <title><?php echo $title; ?></title>
3423        <style type="text/css">
3424                html {
3425                        background: #f1f1f1;
3426                }
3427                body {
3428                        background: #fff;
3429                        color: #444;
3430                        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
3431                        margin: 2em auto;
3432                        padding: 1em 2em;
3433                        max-width: 700px;
3434                        -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
3435                        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
3436                }
3437                h1 {
3438                        border-bottom: 1px solid #dadada;
3439                        clear: both;
3440                        color: #666;
3441                        font-size: 24px;
3442                        margin: 30px 0 0 0;
3443                        padding: 0;
3444                        padding-bottom: 7px;
3445                }
3446                #error-page {
3447                        margin-top: 50px;
3448                }
3449                #error-page p,
3450                #error-page .wp-die-message {
3451                        font-size: 14px;
3452                        line-height: 1.5;
3453                        margin: 25px 0 20px;
3454                }
3455                #error-page code {
3456                        font-family: Consolas, Monaco, monospace;
3457                }
3458                ul li {
3459                        margin-bottom: 10px;
3460                        font-size: 14px ;
3461                }
3462                a {
3463                        color: #0073aa;
3464                }
3465                a:hover,
3466                a:active {
3467                        color: #00a0d2;
3468                }
3469                a:focus {
3470                        color: #124964;
3471                        -webkit-box-shadow:
3472                                0 0 0 1px #5b9dd9,
3473                                0 0 2px 1px rgba(30, 140, 190, 0.8);
3474                        box-shadow:
3475                                0 0 0 1px #5b9dd9,
3476                                0 0 2px 1px rgba(30, 140, 190, 0.8);
3477                        outline: none;
3478                }
3479                .button {
3480                        background: #f7f7f7;
3481                        border: 1px solid #ccc;
3482                        color: #555;
3483                        display: inline-block;
3484                        text-decoration: none;
3485                        font-size: 13px;
3486                        line-height: 2;
3487                        height: 28px;
3488                        margin: 0;
3489                        padding: 0 10px 1px;
3490                        cursor: pointer;
3491                        -webkit-border-radius: 3px;
3492                        -webkit-appearance: none;
3493                        border-radius: 3px;
3494                        white-space: nowrap;
3495                        -webkit-box-sizing: border-box;
3496                        -moz-box-sizing:    border-box;
3497                        box-sizing:         border-box;
3498
3499                        -webkit-box-shadow: 0 1px 0 #ccc;
3500                        box-shadow: 0 1px 0 #ccc;
3501                        vertical-align: top;
3502                }
3503
3504                .button.button-large {
3505                        height: 30px;
3506                        line-height: 2.15384615;
3507                        padding: 0 12px 2px;
3508                }
3509
3510                .button:hover,
3511                .button:focus {
3512                        background: #fafafa;
3513                        border-color: #999;
3514                        color: #23282d;
3515                }
3516
3517                .button:focus {
3518                        border-color: #5b9dd9;
3519                        -webkit-box-shadow: 0 0 3px rgba(0, 115, 170, 0.8);
3520                        box-shadow: 0 0 3px rgba(0, 115, 170, 0.8);
3521                        outline: none;
3522                }
3523
3524                .button:active {
3525                        background: #eee;
3526                        border-color: #999;
3527                        -webkit-box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5);
3528                        box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5);
3529                }
3530
3531                <?php
3532                if ( 'rtl' == $text_direction ) {
3533                        echo 'body { font-family: Tahoma, Arial; }';
3534                }
3535                ?>
3536        </style>
3537</head>
3538<body id="error-page">
3539<?php endif; // ! did_action( 'admin_head' ) ?>
3540        <?php echo $message; ?>
3541</body>
3542</html>
3543        <?php
3544        if ( $parsed_args['exit'] ) {
3545                die();
3546        }
3547}
3548
3549/**
3550 * Kills WordPress execution and displays Ajax response with an error message.
3551 *
3552 * This is the handler for wp_die() when processing Ajax requests.
3553 *
3554 * @since 3.4.0
3555 * @access private
3556 *
3557 * @param string       $message Error message.
3558 * @param string       $title   Optional. Error title (unused). Default empty.
3559 * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3560 */
3561function _ajax_wp_die_handler( $message, $title = '', $args = array() ) {
3562        // Set default 'response' to 200 for AJAX requests.
3563        $args = wp_parse_args(
3564                $args,
3565                array( 'response' => 200 )
3566        );
3567
3568        list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3569
3570        if ( ! headers_sent() ) {
3571                // This is intentional. For backward-compatibility, support passing null here.
3572                if ( null !== $args['response'] ) {
3573                        status_header( $parsed_args['response'] );
3574                }
3575                nocache_headers();
3576        }
3577
3578        if ( is_scalar( $message ) ) {
3579                $message = (string) $message;
3580        } else {
3581                $message = '0';
3582        }
3583
3584        if ( $parsed_args['exit'] ) {
3585                die( $message );
3586        }
3587
3588        echo $message;
3589}
3590
3591/**
3592 * Kills WordPress execution and displays JSON response with an error message.
3593 *
3594 * This is the handler for wp_die() when processing JSON requests.
3595 *
3596 * @since 5.1.0
3597 * @access private
3598 *
3599 * @param string       $message Error message.
3600 * @param string       $title   Optional. Error title. Default empty.
3601 * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3602 */
3603function _json_wp_die_handler( $message, $title = '', $args = array() ) {
3604        list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3605
3606        $data = array(
3607                'code'              => $parsed_args['code'],
3608                'message'           => $message,
3609                'data'              => array(
3610                        'status' => $parsed_args['response'],
3611                ),
3612                'additional_errors' => $parsed_args['additional_errors'],
3613        );
3614
3615        if ( ! headers_sent() ) {
3616                header( "Content-Type: application/json; charset={$parsed_args['charset']}" );
3617                if ( null !== $parsed_args['response'] ) {
3618                        status_header( $parsed_args['response'] );
3619                }
3620                nocache_headers();
3621        }
3622
3623        echo wp_json_encode( $data );
3624        if ( $parsed_args['exit'] ) {
3625                die();
3626        }
3627}
3628
3629/**
3630 * Kills WordPress execution and displays JSONP response with an error message.
3631 *
3632 * This is the handler for wp_die() when processing JSONP requests.
3633 *
3634 * @since 5.2.0
3635 * @access private
3636 *
3637 * @param string       $message Error message.
3638 * @param string       $title   Optional. Error title. Default empty.
3639 * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3640 */
3641function _jsonp_wp_die_handler( $message, $title = '', $args = array() ) {
3642        list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3643
3644        $data = array(
3645                'code'              => $parsed_args['code'],
3646                'message'           => $message,
3647                'data'              => array(
3648                        'status' => $parsed_args['response'],
3649                ),
3650                'additional_errors' => $parsed_args['additional_errors'],
3651        );
3652
3653        if ( ! headers_sent() ) {
3654                header( "Content-Type: application/javascript; charset={$parsed_args['charset']}" );
3655                header( 'X-Content-Type-Options: nosniff' );
3656                header( 'X-Robots-Tag: noindex' );
3657                if ( null !== $parsed_args['response'] ) {
3658                        status_header( $parsed_args['response'] );
3659                }
3660                nocache_headers();
3661        }
3662
3663        $result         = wp_json_encode( $data );
3664        $jsonp_callback = $_GET['_jsonp'];
3665        echo '/**/' . $jsonp_callback . '(' . $result . ')';
3666        if ( $parsed_args['exit'] ) {
3667                die();
3668        }
3669}
3670
3671/**
3672 * Kills WordPress execution and displays XML response with an error message.
3673 *
3674 * This is the handler for wp_die() when processing XMLRPC requests.
3675 *
3676 * @since 3.2.0
3677 * @access private
3678 *
3679 * @global wp_xmlrpc_server $wp_xmlrpc_server
3680 *
3681 * @param string       $message Error message.
3682 * @param string       $title   Optional. Error title. Default empty.
3683 * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3684 */
3685function _xmlrpc_wp_die_handler( $message, $title = '', $args = array() ) {
3686        global $wp_xmlrpc_server;
3687
3688        list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3689
3690        if ( ! headers_sent() ) {
3691                nocache_headers();
3692        }
3693
3694        if ( $wp_xmlrpc_server ) {
3695                $error = new IXR_Error( $parsed_args['response'], $message );
3696                $wp_xmlrpc_server->output( $error->getXml() );
3697        }
3698        if ( $parsed_args['exit'] ) {
3699                die();
3700        }
3701}
3702
3703/**
3704 * Kills WordPress execution and displays XML response with an error message.
3705 *
3706 * This is the handler for wp_die() when processing XML requests.
3707 *
3708 * @since 5.2.0
3709 * @access private
3710 *
3711 * @param string       $message Error message.
3712 * @param string       $title   Optional. Error title. Default empty.
3713 * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3714 */
3715function _xml_wp_die_handler( $message, $title = '', $args = array() ) {
3716        list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3717
3718        $message = htmlspecialchars( $message );
3719        $title   = htmlspecialchars( $title );
3720
3721        $xml = <<<EOD
3722<error>
3723    <code>{$parsed_args['code']}</code>
3724    <title><![CDATA[{$title}]]></title>
3725    <message><![CDATA[{$message}]]></message>
3726    <data>
3727        <status>{$parsed_args['response']}</status>
3728    </data>
3729</error>
3730
3731EOD;
3732
3733        if ( ! headers_sent() ) {
3734                header( "Content-Type: text/xml; charset={$parsed_args['charset']}" );
3735                if ( null !== $parsed_args['response'] ) {
3736                        status_header( $parsed_args['response'] );
3737                }
3738                nocache_headers();
3739        }
3740
3741        echo $xml;
3742        if ( $parsed_args['exit'] ) {
3743                die();
3744        }
3745}
3746
3747/**
3748 * Kills WordPress execution and displays an error message.
3749 *
3750 * This is the handler for wp_die() when processing APP requests.
3751 *
3752 * @since 3.4.0
3753 * @since 5.1.0 Added the $title and $args parameters.
3754 * @access private
3755 *
3756 * @param string       $message Optional. Response to print. Default empty.
3757 * @param string       $title   Optional. Error title (unused). Default empty.
3758 * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3759 */
3760function _scalar_wp_die_handler( $message = '', $title = '', $args = array() ) {
3761        list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3762
3763        if ( $parsed_args['exit'] ) {
3764                if ( is_scalar( $message ) ) {
3765                        die( (string) $message );
3766                }
3767                die();
3768        }
3769
3770        if ( is_scalar( $message ) ) {
3771                echo (string) $message;
3772        }
3773}
3774
3775/**
3776 * Processes arguments passed to wp_die() consistently for its handlers.
3777 *
3778 * @since 5.1.0
3779 * @access private
3780 *
3781 * @param string|WP_Error $message Error message or WP_Error object.
3782 * @param string          $title   Optional. Error title. Default empty.
3783 * @param string|array    $args    Optional. Arguments to control behavior. Default empty array.
3784 * @return array {
3785 *     Processed arguments.
3786 *
3787 *     @type string $0 Error message.
3788 *     @type string $1 Error title.
3789 *     @type array  $2 Arguments to control behavior.
3790 * }
3791 */
3792function _wp_die_process_input( $message, $title = '', $args = array() ) {
3793        $defaults = array(
3794                'response'          => 0,
3795                'code'              => '',
3796                'exit'              => true,
3797                'back_link'         => false,
3798                'link_url'          => '',
3799                'link_text'         => '',
3800                'text_direction'    => '',
3801                'charset'           => 'utf-8',
3802                'additional_errors' => array(),
3803        );
3804
3805        $args = wp_parse_args( $args, $defaults );
3806
3807        if ( function_exists( 'is_wp_error' ) && is_wp_error( $message ) ) {
3808                if ( ! empty( $message->errors ) ) {
3809                        $errors = array();
3810                        foreach ( (array) $message->errors as $error_code => $error_messages ) {
3811                                foreach ( (array) $error_messages as $error_message ) {
3812                                        $errors[] = array(
3813                                                'code'    => $error_code,
3814                                                'message' => $error_message,
3815                                                'data'    => $message->get_error_data( $error_code ),
3816                                        );
3817                                }
3818                        }
3819
3820                        $message = $errors[0]['message'];
3821                        if ( empty( $args['code'] ) ) {
3822                                $args['code'] = $errors[0]['code'];
3823                        }
3824                        if ( empty( $args['response'] ) && is_array( $errors[0]['data'] ) && ! empty( $errors[0]['data']['status'] ) ) {
3825                                $args['response'] = $errors[0]['data']['status'];
3826                        }
3827                        if ( empty( $title ) && is_array( $errors[0]['data'] ) && ! empty( $errors[0]['data']['title'] ) ) {
3828                                $title = $errors[0]['data']['title'];
3829                        }
3830
3831                        unset( $errors[0] );
3832                        $args['additional_errors'] = array_values( $errors );
3833                } else {
3834                        $message = '';
3835                }
3836        }
3837
3838        $have_gettext = function_exists( '__' );
3839
3840        // The $title and these specific $args must always have a non-empty value.
3841        if ( empty( $args['code'] ) ) {
3842                $args['code'] = 'wp_die';
3843        }
3844        if ( empty( $args['response'] ) ) {
3845                $args['response'] = 500;
3846        }
3847        if ( empty( $title ) ) {
3848                $title = $have_gettext ? __( 'WordPress &rsaquo; Error' ) : 'WordPress &rsaquo; Error';
3849        }
3850        if ( empty( $args['text_direction'] ) || ! in_array( $args['text_direction'], array( 'ltr', 'rtl' ), true ) ) {
3851                $args['text_direction'] = 'ltr';
3852                if ( function_exists( 'is_rtl' ) && is_rtl() ) {
3853                        $args['text_direction'] = 'rtl';
3854                }
3855        }
3856
3857        if ( ! empty( $args['charset'] ) ) {
3858                $args['charset'] = _canonical_charset( $args['charset'] );
3859        }
3860
3861        return array( $message, $title, $args );
3862}
3863
3864/**
3865 * Encode a variable into JSON, with some sanity checks.
3866 *
3867 * @since 4.1.0
3868 * @since 5.3.0 No longer handles support for PHP < 5.6.
3869 *
3870 * @param mixed $data    Variable (usually an array or object) to encode as JSON.
3871 * @param int   $options Optional. Options to be passed to json_encode(). Default 0.
3872 * @param int   $depth   Optional. Maximum depth to walk through $data. Must be
3873 *                       greater than 0. Default 512.
3874 * @return string|false The JSON encoded string, or false if it cannot be encoded.
3875 */
3876function wp_json_encode( $data, $options = 0, $depth = 512 ) {
3877        $json = json_encode( $data, $options, $depth );
3878
3879        // If json_encode() was successful, no need to do more sanity checking.
3880        if ( false !== $json ) {
3881                return $json;
3882        }
3883
3884        try {
3885                $data = _wp_json_sanity_check( $data, $depth );
3886        } catch ( Exception $e ) {
3887                return false;
3888        }
3889
3890        return json_encode( $data, $options, $depth );
3891}
3892
3893/**
3894 * Perform sanity checks on data that shall be encoded to JSON.
3895 *
3896 * @ignore
3897 * @since 4.1.0
3898 * @access private
3899 *
3900 * @see wp_json_encode()
3901 *
3902 * @param mixed $data  Variable (usually an array or object) to encode as JSON.
3903 * @param int   $depth Maximum depth to walk through $data. Must be greater than 0.
3904 * @return mixed The sanitized data that shall be encoded to JSON.
3905 */
3906function _wp_json_sanity_check( $data, $depth ) {
3907        if ( $depth < 0 ) {
3908                throw new Exception( 'Reached depth limit' );
3909        }
3910
3911        if ( is_array( $data ) ) {
3912                $output = array();
3913                foreach ( $data as $id => $el ) {
3914                        // Don't forget to sanitize the ID!
3915                        if ( is_string( $id ) ) {
3916                                $clean_id = _wp_json_convert_string( $id );
3917                        } else {
3918                                $clean_id = $id;
3919                        }
3920
3921                        // Check the element type, so that we're only recursing if we really have to.
3922                        if ( is_array( $el ) || is_object( $el ) ) {
3923                                $output[ $clean_id ] = _wp_json_sanity_check( $el, $depth - 1 );
3924                        } elseif ( is_string( $el ) ) {
3925                                $output[ $clean_id ] = _wp_json_convert_string( $el );
3926                        } else {
3927                                $output[ $clean_id ] = $el;
3928                        }
3929                }
3930        } elseif ( is_object( $data ) ) {
3931                $output = new stdClass;
3932                foreach ( $data as $id => $el ) {
3933                        if ( is_string( $id ) ) {
3934                                $clean_id = _wp_json_convert_string( $id );
3935                        } else {
3936                                $clean_id = $id;
3937                        }
3938
3939                        if ( is_array( $el ) || is_object( $el ) ) {
3940                                $output->$clean_id = _wp_json_sanity_check( $el, $depth - 1 );
3941                        } elseif ( is_string( $el ) ) {
3942                                $output->$clean_id = _wp_json_convert_string( $el );
3943                        } else {
3944                                $output->$clean_id = $el;
3945                        }
3946                }
3947        } elseif ( is_string( $data ) ) {
3948                return _wp_json_convert_string( $data );
3949        } else {
3950                return $data;
3951        }
3952
3953        return $output;
3954}
3955
3956/**
3957 * Convert a string to UTF-8, so that it can be safely encoded to JSON.
3958 *
3959 * @ignore
3960 * @since 4.1.0
3961 * @access private
3962 *
3963 * @see _wp_json_sanity_check()
3964 *
3965 * @staticvar bool $use_mb
3966 *
3967 * @param string $string The string which is to be converted.
3968 * @return string The checked string.
3969 */
3970function _wp_json_convert_string( $string ) {
3971        static $use_mb = null;
3972        if ( is_null( $use_mb ) ) {
3973                $use_mb = function_exists( 'mb_convert_encoding' );
3974        }
3975
3976        if ( $use_mb ) {
3977                $encoding = mb_detect_encoding( $string, mb_detect_order(), true );
3978                if ( $encoding ) {
3979                        return mb_convert_encoding( $string, 'UTF-8', $encoding );
3980                } else {
3981                        return mb_convert_encoding( $string, 'UTF-8', 'UTF-8' );
3982                }
3983        } else {
3984                return wp_check_invalid_utf8( $string, true );
3985        }
3986}
3987
3988/**
3989 * Prepares response data to be serialized to JSON.
3990 *
3991 * This supports the JsonSerializable interface for PHP 5.2-5.3 as well.
3992 *
3993 * @ignore
3994 * @since 4.4.0
3995 * @deprecated 5.3.0 This function is no longer needed as support for PHP 5.2-5.3
3996 *                   has been dropped.
3997 * @access     private
3998 *
3999 * @param mixed $data Native representation.
4000 * @return bool|int|float|null|string|array Data ready for `json_encode()`.
4001 */
4002function _wp_json_prepare_data( $data ) {
4003        _deprecated_function( __FUNCTION__, '5.3.0' );
4004        return $data;
4005}
4006
4007/**
4008 * Send a JSON response back to an Ajax request.
4009 *
4010 * @since 3.5.0
4011 * @since 4.7.0 The `$status_code` parameter was added.
4012 *
4013 * @param mixed $response    Variable (usually an array or object) to encode as JSON,
4014 *                           then print and die.
4015 * @param int   $status_code The HTTP status code to output.
4016 */
4017function wp_send_json( $response, $status_code = null ) {
4018        if ( ! headers_sent() ) {
4019                header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
4020                if ( null !== $status_code ) {
4021                        status_header( $status_code );
4022                }
4023        }
4024
4025        echo wp_json_encode( $response );
4026
4027        if ( wp_doing_ajax() ) {
4028                wp_die(
4029                        '',
4030                        '',
4031                        array(
4032                                'response' => null,
4033                        )
4034                );
4035        } else {
4036                die;
4037        }
4038}
4039
4040/**
4041 * Send a JSON response back to an Ajax request, indicating success.
4042 *
4043 * @since 3.5.0
4044 * @since 4.7.0 The `$status_code` parameter was added.
4045 *
4046 * @param mixed $data        Data to encode as JSON, then print and die.
4047 * @param int   $status_code The HTTP status code to output.
4048 */
4049function wp_send_json_success( $data = null, $status_code = null ) {
4050        $response = array( 'success' => true );
4051
4052        if ( isset( $data ) ) {
4053                $response['data'] = $data;
4054        }
4055
4056        wp_send_json( $response, $status_code );
4057}
4058
4059/**
4060 * Send a JSON response back to an Ajax request, indicating failure.
4061 *
4062 * If the `$data` parameter is a WP_Error object, the errors
4063 * within the object are processed and output as an array of error
4064 * codes and corresponding messages. All other types are output
4065 * without further processing.
4066 *
4067 * @since 3.5.0
4068 * @since 4.1.0 The `$data` parameter is now processed if a WP_Error object is passed in.
4069 * @since 4.7.0 The `$status_code` parameter was added.
4070 *
4071 * @param mixed $data        Data to encode as JSON, then print and die.
4072 * @param int   $status_code The HTTP status code to output.
4073 */
4074function wp_send_json_error( $data = null, $status_code = null ) {
4075        $response = array( 'success' => false );
4076
4077        if ( isset( $data ) ) {
4078                if ( is_wp_error( $data ) ) {
4079                        $result = array();
4080                        foreach ( $data->errors as $code => $messages ) {
4081                                foreach ( $messages as $message ) {
4082                                        $result[] = array(
4083                                                'code'    => $code,
4084                                                'message' => $message,
4085                                        );
4086                                }
4087                        }
4088
4089                        $response['data'] = $result;
4090                } else {
4091                        $response['data'] = $data;
4092                }
4093        }
4094
4095        wp_send_json( $response, $status_code );
4096}
4097
4098/**
4099 * Checks that a JSONP callback is a valid JavaScript callback.
4100 *
4101 * Only allows alphanumeric characters and the dot character in callback
4102 * function names. This helps to mitigate XSS attacks caused by directly
4103 * outputting user input.
4104 *
4105 * @since 4.6.0
4106 *
4107 * @param string $callback Supplied JSONP callback function.
4108 * @return bool True if valid callback, otherwise false.
4109 */
4110function wp_check_jsonp_callback( $callback ) {
4111        if ( ! is_string( $callback ) ) {
4112                return false;
4113        }
4114
4115        preg_replace( '/[^\w\.]/', '', $callback, -1, $illegal_char_count );
4116
4117        return 0 === $illegal_char_count;
4118}
4119
4120/**
4121 * Retrieve the WordPress home page URL.
4122 *
4123 * If the constant named 'WP_HOME' exists, then it will be used and returned
4124 * by the function. This can be used to counter the redirection on your local
4125 * development environment.
4126 *
4127 * @since 2.2.0
4128 * @access private
4129 *
4130 * @see WP_HOME
4131 *
4132 * @param string $url URL for the home location.
4133 * @return string Homepage location.
4134 */
4135function _config_wp_home( $url = '' ) {
4136        if ( defined( 'WP_HOME' ) ) {
4137                return untrailingslashit( WP_HOME );
4138        }
4139        return $url;
4140}
4141
4142/**
4143 * Retrieve the WordPress site URL.
4144 *
4145 * If the constant named 'WP_SITEURL' is defined, then the value in that
4146 * constant will always be returned. This can be used for debugging a site
4147 * on your localhost while not having to change the database to your URL.
4148 *
4149 * @since 2.2.0
4150 * @access private
4151 *
4152 * @see WP_SITEURL
4153 *
4154 * @param string $url URL to set the WordPress site location.
4155 * @return string The WordPress Site URL.
4156 */
4157function _config_wp_siteurl( $url = '' ) {
4158        if ( defined( 'WP_SITEURL' ) ) {
4159                return untrailingslashit( WP_SITEURL );
4160        }
4161        return $url;
4162}
4163
4164/**
4165 * Delete the fresh site option.
4166 *
4167 * @since 4.7.0
4168 * @access private
4169 */
4170function _delete_option_fresh_site() {
4171        update_option( 'fresh_site', '0' );
4172}
4173
4174/**
4175 * Set the localized direction for MCE plugin.
4176 *
4177 * Will only set the direction to 'rtl', if the WordPress locale has
4178 * the text direction set to 'rtl'.
4179 *
4180 * Fills in the 'directionality' setting, enables the 'directionality'
4181 * plugin, and adds the 'ltr' button to 'toolbar1', formerly
4182 * 'theme_advanced_buttons1' array keys. These keys are then returned
4183 * in the $mce_init (TinyMCE settings) array.
4184 *
4185 * @since 2.1.0
4186 * @access private
4187 *
4188 * @param array $mce_init MCE settings array.
4189 * @return array Direction set for 'rtl', if needed by locale.
4190 */
4191function _mce_set_direction( $mce_init ) {
4192        if ( is_rtl() ) {
4193                $mce_init['directionality'] = 'rtl';
4194                $mce_init['rtl_ui']         = true;
4195
4196                if ( ! empty( $mce_init['plugins'] ) && strpos( $mce_init['plugins'], 'directionality' ) === false ) {
4197                        $mce_init['plugins'] .= ',directionality';
4198                }
4199
4200                if ( ! empty( $mce_init['toolbar1'] ) && ! preg_match( '/\bltr\b/', $mce_init['toolbar1'] ) ) {
4201                        $mce_init['toolbar1'] .= ',ltr';
4202                }
4203        }
4204
4205        return $mce_init;
4206}
4207
4208
4209/**
4210 * Convert smiley code to the icon graphic file equivalent.
4211 *
4212 * You can turn off smilies, by going to the write setting screen and unchecking
4213 * the box, or by setting 'use_smilies' option to false or removing the option.
4214 *
4215 * Plugins may override the default smiley list by setting the $wpsmiliestrans
4216 * to an array, with the key the code the blogger types in and the value the
4217 * image file.
4218 *
4219 * The $wp_smiliessearch global is for the regular expression and is set each
4220 * time the function is called.
4221 *
4222 * The full list of smilies can be found in the function and won't be listed in
4223 * the description. Probably should create a Codex page for it, so that it is
4224 * available.
4225 *
4226 * @global array $wpsmiliestrans
4227 * @global array $wp_smiliessearch
4228 *
4229 * @since 2.2.0
4230 */
4231function smilies_init() {
4232        global $wpsmiliestrans, $wp_smiliessearch;
4233
4234        // Don't bother setting up smilies if they are disabled.
4235        if ( ! get_option( 'use_smilies' ) ) {
4236                return;
4237        }
4238
4239        if ( ! isset( $wpsmiliestrans ) ) {
4240                $wpsmiliestrans = array(
4241                        ':mrgreen:' => 'mrgreen.png',
4242                        ':neutral:' => "\xf0\x9f\x98\x90",
4243                        ':twisted:' => "\xf0\x9f\x98\x88",
4244                        ':arrow:'   => "\xe2\x9e\xa1",
4245                        ':shock:'   => "\xf0\x9f\x98\xaf",
4246                        ':smile:'   => "\xf0\x9f\x99\x82",
4247                        ':???:'     => "\xf0\x9f\x98\x95",
4248                        ':cool:'    => "\xf0\x9f\x98\x8e",
4249                        ':evil:'    => "\xf0\x9f\x91\xbf",
4250                        ':grin:'    => "\xf0\x9f\x98\x80",
4251                        ':idea:'    => "\xf0\x9f\x92\xa1",
4252                        ':oops:'    => "\xf0\x9f\x98\xb3",
4253                        ':razz:'    => "\xf0\x9f\x98\x9b",
4254                        ':roll:'    => "\xf0\x9f\x99\x84",
4255                        ':wink:'    => "\xf0\x9f\x98\x89",
4256                        ':cry:'     => "\xf0\x9f\x98\xa5",
4257                        ':eek:'     => "\xf0\x9f\x98\xae",
4258                        ':lol:'     => "\xf0\x9f\x98\x86",
4259                        ':mad:'     => "\xf0\x9f\x98\xa1",
4260                        ':sad:'     => "\xf0\x9f\x99\x81",
4261                        '8-)'       => "\xf0\x9f\x98\x8e",
4262                        '8-O'       => "\xf0\x9f\x98\xaf",
4263                        ':-('       => "\xf0\x9f\x99\x81",
4264                        ':-)'       => "\xf0\x9f\x99\x82",
4265                        ':-?'       => "\xf0\x9f\x98\x95",
4266                        ':-D'       => "\xf0\x9f\x98\x80",
4267                        ':-P'       => "\xf0\x9f\x98\x9b",
4268                        ':-o'       => "\xf0\x9f\x98\xae",
4269                        ':-x'       => "\xf0\x9f\x98\xa1",
4270                        ':-|'       => "\xf0\x9f\x98\x90",
4271                        ';-)'       => "\xf0\x9f\x98\x89",
4272                        // This one transformation breaks regular text with frequency.
4273                        //     '8)' => "\xf0\x9f\x98\x8e",
4274                        '8O'        => "\xf0\x9f\x98\xaf",
4275                        ':('        => "\xf0\x9f\x99\x81",
4276                        ':)'        => "\xf0\x9f\x99\x82",
4277                        ':?'        => "\xf0\x9f\x98\x95",
4278                        ':D'        => "\xf0\x9f\x98\x80",
4279                        ':P'        => "\xf0\x9f\x98\x9b",
4280                        ':o'        => "\xf0\x9f\x98\xae",
4281                        ':x'        => "\xf0\x9f\x98\xa1",
4282                        ':|'        => "\xf0\x9f\x98\x90",
4283                        ';)'        => "\xf0\x9f\x98\x89",
4284                        ':!:'       => "\xe2\x9d\x97",
4285                        ':?:'       => "\xe2\x9d\x93",
4286                );
4287        }
4288
4289        /**
4290         * Filters all the smilies.
4291         *
4292         * This filter must be added before `smilies_init` is run, as
4293         * it is normally only run once to setup the smilies regex.
4294         *
4295         * @since 4.7.0
4296         *
4297         * @param string[] $wpsmiliestrans List of the smilies' hexadecimal representations, keyed by their smily code.
4298         */
4299        $wpsmiliestrans = apply_filters( 'smilies', $wpsmiliestrans );
4300
4301        if ( count( $wpsmiliestrans ) == 0 ) {
4302                return;
4303        }
4304
4305        /*
4306         * NOTE: we sort the smilies in reverse key order. This is to make sure
4307         * we match the longest possible smilie (:???: vs :?) as the regular
4308         * expression used below is first-match
4309         */
4310        krsort( $wpsmiliestrans );
4311
4312        $spaces = wp_spaces_regexp();
4313
4314        // Begin first "subpattern".
4315        $wp_smiliessearch = '/(?<=' . $spaces . '|^)';
4316
4317        $subchar = '';
4318        foreach ( (array) $wpsmiliestrans as $smiley => $img ) {
4319                $firstchar = substr( $smiley, 0, 1 );
4320                $rest      = substr( $smiley, 1 );
4321
4322                // New subpattern?
4323                if ( $firstchar != $subchar ) {
4324                        if ( '' != $subchar ) {
4325                                $wp_smiliessearch .= ')(?=' . $spaces . '|$)';  // End previous "subpattern".
4326                                $wp_smiliessearch .= '|(?<=' . $spaces . '|^)'; // Begin another "subpattern".
4327                        }
4328                        $subchar           = $firstchar;
4329                        $wp_smiliessearch .= preg_quote( $firstchar, '/' ) . '(?:';
4330                } else {
4331                        $wp_smiliessearch .= '|';
4332                }
4333                $wp_smiliessearch .= preg_quote( $rest, '/' );
4334        }
4335
4336        $wp_smiliessearch .= ')(?=' . $spaces . '|$)/m';
4337
4338}
4339
4340/**
4341 * Merge user defined arguments into defaults array.
4342 *
4343 * This function is used throughout WordPress to allow for both string or array
4344 * to be merged into another array.
4345 *
4346 * @since 2.2.0
4347 * @since 2.3.0 `$args` can now also be an object.
4348 *
4349 * @param string|array|object $args     Value to merge with $defaults.
4350 * @param array               $defaults Optional. Array that serves as the defaults. Default empty.
4351 * @return array Merged user defined values with defaults.
4352 */
4353function wp_parse_args( $args, $defaults = '' ) {
4354        if ( is_object( $args ) ) {
4355                $parsed_args = get_object_vars( $args );
4356        } elseif ( is_array( $args ) ) {
4357                $parsed_args =& $args;
4358        } else {
4359                wp_parse_str( $args, $parsed_args );
4360        }
4361
4362        if ( is_array( $defaults ) ) {
4363                return array_merge( $defaults, $parsed_args );
4364        }
4365        return $parsed_args;
4366}
4367
4368/**
4369 * Cleans up an array, comma- or space-separated list of scalar values.
4370 *
4371 * @since 5.1.0
4372 *
4373 * @param array|string $list List of values.
4374 * @return array Sanitized array of values.
4375 */
4376function wp_parse_list( $list ) {
4377        if ( ! is_array( $list ) ) {
4378                return preg_split( '/[\s,]+/', $list, -1, PREG_SPLIT_NO_EMPTY );
4379        }
4380
4381        return $list;
4382}
4383
4384/**
4385 * Clean up an array, comma- or space-separated list of IDs.
4386 *
4387 * @since 3.0.0
4388 *
4389 * @param array|string $list List of ids.
4390 * @return int[] Sanitized array of IDs.
4391 */
4392function wp_parse_id_list( $list ) {
4393        $list = wp_parse_list( $list );
4394
4395        return array_unique( array_map( 'absint', $list ) );
4396}
4397
4398/**
4399 * Clean up an array, comma- or space-separated list of slugs.
4400 *
4401 * @since 4.7.0
4402 *
4403 * @param  array|string $list List of slugs.
4404 * @return string[] Sanitized array of slugs.
4405 */
4406function wp_parse_slug_list( $list ) {
4407        $list = wp_parse_list( $list );
4408
4409        return array_unique( array_map( 'sanitize_title', $list ) );
4410}
4411
4412/**
4413 * Extract a slice of an array, given a list of keys.
4414 *
4415 * @since 3.1.0
4416 *
4417 * @param array $array The original array.
4418 * @param array $keys  The list of keys.
4419 * @return array The array slice.
4420 */
4421function wp_array_slice_assoc( $array, $keys ) {
4422        $slice = array();
4423        foreach ( $keys as $key ) {
4424                if ( isset( $array[ $key ] ) ) {
4425                        $slice[ $key ] = $array[ $key ];
4426                }
4427        }
4428
4429        return $slice;
4430}
4431
4432/**
4433 * Determines if the variable is a numeric-indexed array.
4434 *
4435 * @since 4.4.0
4436 *
4437 * @param mixed $data Variable to check.
4438 * @return bool Whether the variable is a list.
4439 */
4440function wp_is_numeric_array( $data ) {
4441        if ( ! is_array( $data ) ) {
4442                return false;
4443        }
4444
4445        $keys        = array_keys( $data );
4446        $string_keys = array_filter( $keys, 'is_string' );
4447        return count( $string_keys ) === 0;
4448}
4449
4450/**
4451 * Filters a list of objects, based on a set of key => value arguments.
4452 *
4453 * @since 3.0.0
4454 * @since 4.7.0 Uses `WP_List_Util` class.
4455 *
4456 * @param array       $list     An array of objects to filter
4457 * @param array       $args     Optional. An array of key => value arguments to match
4458 *                              against each object. Default empty array.
4459 * @param string      $operator Optional. The logical operation to perform. 'or' means
4460 *                              only one element from the array needs to match; 'and'
4461 *                              means all elements must match; 'not' means no elements may
4462 *                              match. Default 'and'.
4463 * @param bool|string $field    A field from the object to place instead of the entire object.
4464 *                              Default false.
4465 * @return array A list of objects or object fields.
4466 */
4467function wp_filter_object_list( $list, $args = array(), $operator = 'and', $field = false ) {
4468        if ( ! is_array( $list ) ) {
4469                return array();
4470        }
4471
4472        $util = new WP_List_Util( $list );
4473
4474        $util->filter( $args, $operator );
4475
4476        if ( $field ) {
4477                $util->pluck( $field );
4478        }
4479
4480        return $util->get_output();
4481}
4482
4483/**
4484 * Filters a list of objects, based on a set of key => value arguments.
4485 *
4486 * @since 3.1.0
4487 * @since 4.7.0 Uses `WP_List_Util` class.
4488 *
4489 * @param array  $list     An array of objects to filter.
4490 * @param array  $args     Optional. An array of key => value arguments to match
4491 *                         against each object. Default empty array.
4492 * @param string $operator Optional. The logical operation to perform. 'AND' means
4493 *                         all elements from the array must match. 'OR' means only
4494 *                         one element needs to match. 'NOT' means no elements may
4495 *                         match. Default 'AND'.
4496 * @return array Array of found values.
4497 */
4498function wp_list_filter( $list, $args = array(), $operator = 'AND' ) {
4499        if ( ! is_array( $list ) ) {
4500                return array();
4501        }
4502
4503        $util = new WP_List_Util( $list );
4504        return $util->filter( $args, $operator );
4505}
4506
4507/**
4508 * Pluck a certain field out of each object in a list.
4509 *
4510 * This has the same functionality and prototype of
4511 * array_column() (PHP 5.5) but also supports objects.
4512 *
4513 * @since 3.1.0
4514 * @since 4.0.0 $index_key parameter added.
4515 * @since 4.7.0 Uses `WP_List_Util` class.
4516 *
4517 * @param array      $list      List of objects or arrays
4518 * @param int|string $field     Field from the object to place instead of the entire object
4519 * @param int|string $index_key Optional. Field from the object to use as keys for the new array.
4520 *                              Default null.
4521 * @return array Array of found values. If `$index_key` is set, an array of found values with keys
4522 *               corresponding to `$index_key`. If `$index_key` is null, array keys from the original
4523 *               `$list` will be preserved in the results.
4524 */
4525function wp_list_pluck( $list, $field, $index_key = null ) {
4526        $util = new WP_List_Util( $list );
4527        return $util->pluck( $field, $index_key );
4528}
4529
4530/**
4531 * Sorts a list of objects, based on one or more orderby arguments.
4532 *
4533 * @since 4.7.0
4534 *
4535 * @param array        $list          An array of objects to sort.
4536 * @param string|array $orderby       Optional. Either the field name to order by or an array
4537 *                                    of multiple orderby fields as $orderby => $order.
4538 * @param string       $order         Optional. Either 'ASC' or 'DESC'. Only used if $orderby
4539 *                                    is a string.
4540 * @param bool         $preserve_keys Optional. Whether to preserve keys. Default false.
4541 * @return array The sorted array.
4542 */
4543function wp_list_sort( $list, $orderby = array(), $order = 'ASC', $preserve_keys = false ) {
4544        if ( ! is_array( $list ) ) {
4545                return array();
4546        }
4547
4548        $util = new WP_List_Util( $list );
4549        return $util->sort( $orderby, $order, $preserve_keys );
4550}
4551
4552/**
4553 * Determines if Widgets library should be loaded.
4554 *
4555 * Checks to make sure that the widgets library hasn't already been loaded.
4556 * If it hasn't, then it will load the widgets library and run an action hook.
4557 *
4558 * @since 2.2.0
4559 */
4560function wp_maybe_load_widgets() {
4561        /**
4562         * Filters whether to load the Widgets library.
4563         *
4564         * Passing a falsey value to the filter will effectively short-circuit
4565         * the Widgets library from loading.
4566         *
4567         * @since 2.8.0
4568         *
4569         * @param bool $wp_maybe_load_widgets Whether to load the Widgets library.
4570         *                                    Default true.
4571         */
4572        if ( ! apply_filters( 'load_default_widgets', true ) ) {
4573                return;
4574        }
4575
4576        require_once ABSPATH . WPINC . '/default-widgets.php';
4577
4578        add_action( '_admin_menu', 'wp_widgets_add_menu' );
4579}
4580
4581/**
4582 * Append the Widgets menu to the themes main menu.
4583 *
4584 * @since 2.2.0
4585 *
4586 * @global array $submenu
4587 */
4588function wp_widgets_add_menu() {
4589        global $submenu;
4590
4591        if ( ! current_theme_supports( 'widgets' ) ) {
4592                return;
4593        }
4594
4595        $submenu['themes.php'][7] = array( __( 'Widgets' ), 'edit_theme_options', 'widgets.php' );
4596        ksort( $submenu['themes.php'], SORT_NUMERIC );
4597}
4598
4599/**
4600 * Flush all output buffers for PHP 5.2.
4601 *
4602 * Make sure all output buffers are flushed before our singletons are destroyed.
4603 *
4604 * @since 2.2.0
4605 */
4606function wp_ob_end_flush_all() {
4607        $levels = ob_get_level();
4608        for ( $i = 0; $i < $levels; $i++ ) {
4609                ob_end_flush();
4610        }
4611}
4612
4613/**
4614 * Load custom DB error or display WordPress DB error.
4615 *
4616 * If a file exists in the wp-content directory named db-error.php, then it will
4617 * be loaded instead of displaying the WordPress DB error. If it is not found,
4618 * then the WordPress DB error will be displayed instead.
4619 *
4620 * The WordPress DB error sets the HTTP status header to 500 to try to prevent
4621 * search engines from caching the message. Custom DB messages should do the
4622 * same.
4623 *
4624 * This function was backported to WordPress 2.3.2, but originally was added
4625 * in WordPress 2.5.0.
4626 *
4627 * @since 2.3.2
4628 *
4629 * @global wpdb $wpdb WordPress database abstraction object.
4630 */
4631function dead_db() {
4632        global $wpdb;
4633
4634        wp_load_translations_early();
4635
4636        // Load custom DB error template, if present.
4637        if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) {
4638                require_once WP_CONTENT_DIR . '/db-error.php';
4639                die();
4640        }
4641
4642        // If installing or in the admin, provide the verbose message.
4643        if ( wp_installing() || defined( 'WP_ADMIN' ) ) {
4644                wp_die( $wpdb->error );
4645        }
4646
4647        // Otherwise, be terse.
4648        wp_die( '<h1>' . __( 'Error establishing a database connection' ) . '</h1>', __( 'Database Error' ) );
4649}
4650
4651/**
4652 * Convert a value to non-negative integer.
4653 *
4654 * @since 2.5.0
4655 *
4656 * @param mixed $maybeint Data you wish to have converted to a non-negative integer.
4657 * @return int A non-negative integer.
4658 */
4659function absint( $maybeint ) {
4660        return abs( intval( $maybeint ) );
4661}
4662
4663/**
4664 * Mark a function as deprecated and inform when it has been used.
4665 *
4666 * There is a {@see 'hook deprecated_function_run'} that will be called that can be used
4667 * to get the backtrace up to what file and function called the deprecated
4668 * function.
4669 *
4670 * The current behavior is to trigger a user error if `WP_DEBUG` is true.
4671 *
4672 * This function is to be used in every function that is deprecated.
4673 *
4674 * @since 2.5.0
4675 * @since 5.4.0 This function is no longer marked as "private".
4676 * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
4677 *
4678 * @param string $function    The function that was called.
4679 * @param string $version     The version of WordPress that deprecated the function.
4680 * @param string $replacement Optional. The function that should have been called. Default null.
4681 */
4682function _deprecated_function( $function, $version, $replacement = null ) {
4683
4684        /**
4685         * Fires when a deprecated function is called.
4686         *
4687         * @since 2.5.0
4688         *
4689         * @param string $function    The function that was called.
4690         * @param string $replacement The function that should have been called.
4691         * @param string $version     The version of WordPress that deprecated the function.
4692         */
4693        do_action( 'deprecated_function_run', $function, $replacement, $version );
4694
4695        /**
4696         * Filters whether to trigger an error for deprecated functions.
4697         *
4698         * @since 2.5.0
4699         *
4700         * @param bool $trigger Whether to trigger the error for deprecated functions. Default true.
4701         */
4702        if ( WP_DEBUG && apply_filters( 'deprecated_function_trigger_error', true ) ) {
4703                if ( function_exists( '__' ) ) {
4704                        if ( ! is_null( $replacement ) ) {
4705                                trigger_error(
4706                                        sprintf(
4707                                                /* translators: 1: PHP function name, 2: Version number, 3: Alternative function name. */
4708                                                __( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
4709                                                $function,
4710                                                $version,
4711                                                $replacement
4712                                        ),
4713                                        E_USER_DEPRECATED
4714                                );
4715                        } else {
4716                                trigger_error(
4717                                        sprintf(
4718                                                /* translators: 1: PHP function name, 2: Version number. */
4719                                                __( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
4720                                                $function,
4721                                                $version
4722                                        ),
4723                                        E_USER_DEPRECATED
4724                                );
4725                        }
4726                } else {
4727                        if ( ! is_null( $replacement ) ) {
4728                                trigger_error(
4729                                        sprintf(
4730                                                '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
4731                                                $function,
4732                                                $version,
4733                                                $replacement
4734                                        ),
4735                                        E_USER_DEPRECATED
4736                                );
4737                        } else {
4738                                trigger_error(
4739                                        sprintf(
4740                                                '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.',
4741                                                $function,
4742                                                $version
4743                                        ),
4744                                        E_USER_DEPRECATED
4745                                );
4746                        }
4747                }
4748        }
4749}
4750
4751/**
4752 * Marks a constructor as deprecated and informs when it has been used.
4753 *
4754 * Similar to _deprecated_function(), but with different strings. Used to
4755 * remove PHP4 style constructors.
4756 *
4757 * The current behavior is to trigger a user error if `WP_DEBUG` is true.
4758 *
4759 * This function is to be used in every PHP4 style constructor method that is deprecated.
4760 *
4761 * @since 4.3.0
4762 * @since 4.5.0 Added the `$parent_class` parameter.
4763 * @since 5.4.0 This function is no longer marked as "private".
4764 * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
4765 *
4766 * @param string $class        The class containing the deprecated constructor.
4767 * @param string $version      The version of WordPress that deprecated the function.
4768 * @param string $parent_class Optional. The parent class calling the deprecated constructor.
4769 *                             Default empty string.
4770 */
4771function _deprecated_constructor( $class, $version, $parent_class = '' ) {
4772
4773        /**
4774         * Fires when a deprecated constructor is called.
4775         *
4776         * @since 4.3.0
4777         * @since 4.5.0 Added the `$parent_class` parameter.
4778         *
4779         * @param string $class        The class containing the deprecated constructor.
4780         * @param string $version      The version of WordPress that deprecated the function.
4781         * @param string $parent_class The parent class calling the deprecated constructor.
4782         */
4783        do_action( 'deprecated_constructor_run', $class, $version, $parent_class );
4784
4785        /**
4786         * Filters whether to trigger an error for deprecated functions.
4787         *
4788         * `WP_DEBUG` must be true in addition to the filter evaluating to true.
4789         *
4790         * @since 4.3.0
4791         *
4792         * @param bool $trigger Whether to trigger the error for deprecated functions. Default true.
4793         */
4794        if ( WP_DEBUG && apply_filters( 'deprecated_constructor_trigger_error', true ) ) {
4795                if ( function_exists( '__' ) ) {
4796                        if ( ! empty( $parent_class ) ) {
4797                                trigger_error(
4798                                        sprintf(
4799                                                /* translators: 1: PHP class name, 2: PHP parent class name, 3: Version number, 4: __construct() method. */
4800                                                __( 'The called constructor method for %1$s in %2$s is <strong>deprecated</strong> since version %3$s! Use %4$s instead.' ),
4801                                                $class,
4802                                                $parent_class,
4803                                                $version,
4804                                                '<code>__construct()</code>'
4805                                        ),
4806                                        E_USER_DEPRECATED
4807                                );
4808                        } else {
4809                                trigger_error(
4810                                        sprintf(
4811                                                /* translators: 1: PHP class name, 2: Version number, 3: __construct() method. */
4812                                                __( 'The called constructor method for %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
4813                                                $class,
4814                                                $version,
4815                                                '<code>__construct()</code>'
4816                                        ),
4817                                        E_USER_DEPRECATED
4818                                );
4819                        }
4820                } else {
4821                        if ( ! empty( $parent_class ) ) {
4822                                trigger_error(
4823                                        sprintf(
4824                                                'The called constructor method for %1$s in %2$s is <strong>deprecated</strong> since version %3$s! Use %4$s instead.',
4825                                                $class,
4826                                                $parent_class,
4827                                                $version,
4828                                                '<code>__construct()</code>'
4829                                        ),
4830                                        E_USER_DEPRECATED
4831                                );
4832                        } else {
4833                                trigger_error(
4834                                        sprintf(
4835                                                'The called constructor method for %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
4836                                                $class,
4837                                                $version,
4838                                                '<code>__construct()</code>'
4839                                        ),
4840                                        E_USER_DEPRECATED
4841                                );
4842                        }
4843                }
4844        }
4845
4846}
4847
4848/**
4849 * Mark a file as deprecated and inform when it has been used.
4850 *
4851 * There is a hook {@see 'deprecated_file_included'} that will be called that can be used
4852 * to get the backtrace up to what file and function included the deprecated
4853 * file.
4854 *
4855 * The current behavior is to trigger a user error if `WP_DEBUG` is true.
4856 *
4857 * This function is to be used in every file that is deprecated.
4858 *
4859 * @since 2.5.0
4860 * @since 5.4.0 This function is no longer marked as "private".
4861 * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
4862 *
4863 * @param string $file        The file that was included.
4864 * @param string $version     The version of WordPress that deprecated the file.
4865 * @param string $replacement Optional. The file that should have been included based on ABSPATH.
4866 *                            Default null.
4867 * @param string $message     Optional. A message regarding the change. Default empty.
4868 */
4869function _deprecated_file( $file, $version, $replacement = null, $message = '' ) {
4870
4871        /**
4872         * Fires when a deprecated file is called.
4873         *
4874         * @since 2.5.0
4875         *
4876         * @param string $file        The file that was called.
4877         * @param string $replacement The file that should have been included based on ABSPATH.
4878         * @param string $version     The version of WordPress that deprecated the file.
4879         * @param string $message     A message regarding the change.
4880         */
4881        do_action( 'deprecated_file_included', $file, $replacement, $version, $message );
4882
4883        /**
4884         * Filters whether to trigger an error for deprecated files.
4885         *
4886         * @since 2.5.0
4887         *
4888         * @param bool $trigger Whether to trigger the error for deprecated files. Default true.
4889         */
4890        if ( WP_DEBUG && apply_filters( 'deprecated_file_trigger_error', true ) ) {
4891                $message = empty( $message ) ? '' : ' ' . $message;
4892
4893                if ( function_exists( '__' ) ) {
4894                        if ( ! is_null( $replacement ) ) {
4895                                trigger_error(
4896                                        sprintf(
4897                                                /* translators: 1: PHP file name, 2: Version number, 3: Alternative file name. */
4898                                                __( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
4899                                                $file,
4900                                                $version,
4901                                                $replacement
4902                                        ) . $message,
4903                                        E_USER_DEPRECATED
4904                                );
4905                        } else {
4906                                trigger_error(
4907                                        sprintf(
4908                                                /* translators: 1: PHP file name, 2: Version number. */
4909                                                __( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
4910                                                $file,
4911                                                $version
4912                                        ) . $message,
4913                                        E_USER_DEPRECATED
4914                                );
4915                        }
4916                } else {
4917                        if ( ! is_null( $replacement ) ) {
4918                                trigger_error(
4919                                        sprintf(
4920                                                '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
4921                                                $file,
4922                                                $version,
4923                                                $replacement
4924                                        ) . $message,
4925                                        E_USER_DEPRECATED
4926                                );
4927                        } else {
4928                                trigger_error(
4929                                        sprintf(
4930                                                '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.',
4931                                                $file,
4932                                                $version
4933                                        ) . $message,
4934                                        E_USER_DEPRECATED
4935                                );
4936                        }
4937                }
4938        }
4939}
4940/**
4941 * Mark a function argument as deprecated and inform when it has been used.
4942 *
4943 * This function is to be used whenever a deprecated function argument is used.
4944 * Before this function is called, the argument must be checked for whether it was
4945 * used by comparing it to its default value or evaluating whether it is empty.
4946 * For example:
4947 *
4948 *     if ( ! empty( $deprecated ) ) {
4949 *         _deprecated_argument( __FUNCTION__, '3.0.0' );
4950 *     }
4951 *
4952 * There is a hook deprecated_argument_run that will be called that can be used
4953 * to get the backtrace up to what file and function used the deprecated
4954 * argument.
4955 *
4956 * The current behavior is to trigger a user error if WP_DEBUG is true.
4957 *
4958 * @since 3.0.0
4959 * @since 5.4.0 This function is no longer marked as "private".
4960 * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
4961 *
4962 * @param string $function The function that was called.
4963 * @param string $version  The version of WordPress that deprecated the argument used.
4964 * @param string $message  Optional. A message regarding the change. Default null.
4965 */
4966function _deprecated_argument( $function, $version, $message = null ) {
4967
4968        /**
4969         * Fires when a deprecated argument is called.
4970         *
4971         * @since 3.0.0
4972         *
4973         * @param string $function The function that was called.
4974         * @param string $message  A message regarding the change.
4975         * @param string $version  The version of WordPress that deprecated the argument used.
4976         */
4977        do_action( 'deprecated_argument_run', $function, $message, $version );
4978
4979        /**
4980         * Filters whether to trigger an error for deprecated arguments.
4981         *
4982         * @since 3.0.0
4983         *
4984         * @param bool $trigger Whether to trigger the error for deprecated arguments. Default true.
4985         */
4986        if ( WP_DEBUG && apply_filters( 'deprecated_argument_trigger_error', true ) ) {
4987                if ( function_exists( '__' ) ) {
4988                        if ( ! is_null( $message ) ) {
4989                                trigger_error(
4990                                        sprintf(
4991                                                /* translators: 1: PHP function name, 2: Version number, 3: Optional message regarding the change. */
4992                                                __( '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s! %3$s' ),
4993                                                $function,
4994                                                $version,
4995                                                $message
4996                                        ),
4997                                        E_USER_DEPRECATED
4998                                );
4999                        } else {
5000                                trigger_error(
5001                                        sprintf(
5002                                                /* translators: 1: PHP function name, 2: Version number. */
5003                                                __( '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
5004                                                $function,
5005                                                $version
5006                                        ),
5007                                        E_USER_DEPRECATED
5008                                );
5009                        }
5010                } else {
5011                        if ( ! is_null( $message ) ) {
5012                                trigger_error(
5013                                        sprintf(
5014                                                '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s! %3$s',
5015                                                $function,
5016                                                $version,
5017                                                $message
5018                                        ),
5019                                        E_USER_DEPRECATED
5020                                );
5021                        } else {
5022                                trigger_error(
5023                                        sprintf(
5024                                                '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s with no alternative available.',
5025                                                $function,
5026                                                $version
5027                                        ),
5028                                        E_USER_DEPRECATED
5029                                );
5030                        }
5031                }
5032        }
5033}
5034
5035/**
5036 * Marks a deprecated action or filter hook as deprecated and throws a notice.
5037 *
5038 * Use the {@see 'deprecated_hook_run'} action to get the backtrace describing where
5039 * the deprecated hook was called.
5040 *
5041 * Default behavior is to trigger a user error if `WP_DEBUG` is true.
5042 *
5043 * This function is called by the do_action_deprecated() and apply_filters_deprecated()
5044 * functions, and so generally does not need to be called directly.
5045 *
5046 * @since 4.6.0
5047 * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
5048 * @access private
5049 *
5050 * @param string $hook        The hook that was used.
5051 * @param string $version     The version of WordPress that deprecated the hook.
5052 * @param string $replacement Optional. The hook that should have been used. Default null.
5053 * @param string $message     Optional. A message regarding the change. Default null.
5054 */
5055function _deprecated_hook( $hook, $version, $replacement = null, $message = null ) {
5056        /**
5057         * Fires when a deprecated hook is called.
5058         *
5059         * @since 4.6.0
5060         *
5061         * @param string $hook        The hook that was called.
5062         * @param string $replacement The hook that should be used as a replacement.
5063         * @param string $version     The version of WordPress that deprecated the argument used.
5064         * @param string $message     A message regarding the change.
5065         */
5066        do_action( 'deprecated_hook_run', $hook, $replacement, $version, $message );
5067
5068        /**
5069         * Filters whether to trigger deprecated hook errors.
5070         *
5071         * @since 4.6.0
5072         *
5073         * @param bool $trigger Whether to trigger deprecated hook errors. Requires
5074         *                      `WP_DEBUG` to be defined true.
5075         */
5076        if ( WP_DEBUG && apply_filters( 'deprecated_hook_trigger_error', true ) ) {
5077                $message = empty( $message ) ? '' : ' ' . $message;
5078
5079                if ( ! is_null( $replacement ) ) {
5080                        trigger_error(
5081                                sprintf(
5082                                        /* translators: 1: WordPress hook name, 2: Version number, 3: Alternative hook name. */
5083                                        __( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
5084                                        $hook,
5085                                        $version,
5086                                        $replacement
5087                                ) . $message,
5088                                E_USER_DEPRECATED
5089                        );
5090                } else {
5091                        trigger_error(
5092                                sprintf(
5093                                        /* translators: 1: WordPress hook name, 2: Version number. */
5094                                        __( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
5095                                        $hook,
5096                                        $version
5097                                ) . $message,
5098                                E_USER_DEPRECATED
5099                        );
5100                }
5101        }
5102}
5103
5104/**
5105 * Mark something as being incorrectly called.
5106 *
5107 * There is a hook {@see 'doing_it_wrong_run'} that will be called that can be used
5108 * to get the backtrace up to what file and function called the deprecated
5109 * function.
5110 *
5111 * The current behavior is to trigger a user error if `WP_DEBUG` is true.
5112 *
5113 * @since 3.1.0
5114 * @since 5.4.0 This function is no longer marked as "private".
5115 *
5116 * @param string $function The function that was called.
5117 * @param string $message  A message explaining what has been done incorrectly.
5118 * @param string $version  The version of WordPress where the message was added.
5119 */
5120function _doing_it_wrong( $function, $message, $version ) {
5121
5122        /**
5123         * Fires when the given function is being used incorrectly.
5124         *
5125         * @since 3.1.0
5126         *
5127         * @param string $function The function that was called.
5128         * @param string $message  A message explaining what has been done incorrectly.
5129         * @param string $version  The version of WordPress where the message was added.
5130         */
5131        do_action( 'doing_it_wrong_run', $function, $message, $version );
5132
5133        /**
5134         * Filters whether to trigger an error for _doing_it_wrong() calls.
5135         *
5136         * @since 3.1.0
5137         * @since 5.1.0 Added the $function, $message and $version parameters.
5138         *
5139         * @param bool   $trigger  Whether to trigger the error for _doing_it_wrong() calls. Default true.
5140         * @param string $function The function that was called.
5141         * @param string $message  A message explaining what has been done incorrectly.
5142         * @param string $version  The version of WordPress where the message was added.
5143         */
5144        if ( WP_DEBUG && apply_filters( 'doing_it_wrong_trigger_error', true, $function, $message, $version ) ) {
5145                if ( function_exists( '__' ) ) {
5146                        if ( is_null( $version ) ) {
5147                                $version = '';
5148                        } else {
5149                                /* translators: %s: Version number. */
5150                                $version = sprintf( __( '(This message was added in version %s.)' ), $version );
5151                        }
5152
5153                        $message .= ' ' . sprintf(
5154                                /* translators: %s: Documentation URL. */
5155                                __( 'Please see <a href="%s">Debugging in WordPress</a> for more information.' ),
5156                                __( 'https://wordpress.org/support/article/debugging-in-wordpress/' )
5157                        );
5158
5159                        trigger_error(
5160                                sprintf(
5161                                        /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message, 3: Version information message. */
5162                                        __( '%1$s was called <strong>incorrectly</strong>. %2$s %3$s' ),
5163                                        $function,
5164                                        $message,
5165                                        $version
5166                                ),
5167                                E_USER_NOTICE
5168                        );
5169                } else {
5170                        if ( is_null( $version ) ) {
5171                                $version = '';
5172                        } else {
5173                                $version = sprintf( '(This message was added in version %s.)', $version );
5174                        }
5175
5176                        $message .= sprintf(
5177                                ' Please see <a href="%s">Debugging in WordPress</a> for more information.',
5178                                'https://wordpress.org/support/article/debugging-in-wordpress/'
5179                        );
5180
5181                        trigger_error(
5182                                sprintf(
5183                                        '%1$s was called <strong>incorrectly</strong>. %2$s %3$s',
5184                                        $function,
5185                                        $message,
5186                                        $version
5187                                ),
5188                                E_USER_NOTICE
5189                        );
5190                }
5191        }
5192}
5193
5194/**
5195 * Is the server running earlier than 1.5.0 version of lighttpd?
5196 *
5197 * @since 2.5.0
5198 *
5199 * @return bool Whether the server is running lighttpd < 1.5.0.
5200 */
5201function is_lighttpd_before_150() {
5202        $server_parts    = explode( '/', isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : '' );
5203        $server_parts[1] = isset( $server_parts[1] ) ? $server_parts[1] : '';
5204        return 'lighttpd' == $server_parts[0] && -1 == version_compare( $server_parts[1], '1.5.0' );
5205}
5206
5207/**
5208 * Does the specified module exist in the Apache config?
5209 *
5210 * @since 2.5.0
5211 *
5212 * @global bool $is_apache
5213 *
5214 * @param string $mod     The module, e.g. mod_rewrite.
5215 * @param bool   $default Optional. The default return value if the module is not found. Default false.
5216 * @return bool Whether the specified module is loaded.
5217 */
5218function apache_mod_loaded( $mod, $default = false ) {
5219        global $is_apache;
5220
5221        if ( ! $is_apache ) {
5222                return false;
5223        }
5224
5225        if ( function_exists( 'apache_get_modules' ) ) {
5226                $mods = apache_get_modules();
5227                if ( in_array( $mod, $mods ) ) {
5228                        return true;
5229                }
5230        } elseif ( function_exists( 'phpinfo' ) && false === strpos( ini_get( 'disable_functions' ), 'phpinfo' ) ) {
5231                        ob_start();
5232                        phpinfo( 8 );
5233                        $phpinfo = ob_get_clean();
5234                if ( false !== strpos( $phpinfo, $mod ) ) {
5235                        return true;
5236                }
5237        }
5238        return $default;
5239}
5240
5241/**
5242 * Check if IIS 7+ supports pretty permalinks.
5243 *
5244 * @since 2.8.0
5245 *
5246 * @global bool $is_iis7
5247 *
5248 * @return bool Whether IIS7 supports permalinks.
5249 */
5250function iis7_supports_permalinks() {
5251        global $is_iis7;
5252
5253        $supports_permalinks = false;
5254        if ( $is_iis7 ) {
5255                /* First we check if the DOMDocument class exists. If it does not exist, then we cannot
5256                 * easily update the xml configuration file, hence we just bail out and tell user that
5257                 * pretty permalinks cannot be used.
5258                 *
5259                 * Next we check if the URL Rewrite Module 1.1 is loaded and enabled for the web site. When
5260                 * URL Rewrite 1.1 is loaded it always sets a server variable called 'IIS_UrlRewriteModule'.
5261                 * Lastly we make sure that PHP is running via FastCGI. This is important because if it runs
5262                 * via ISAPI then pretty permalinks will not work.
5263                 */
5264                $supports_permalinks = class_exists( 'DOMDocument', false ) && isset( $_SERVER['IIS_UrlRewriteModule'] ) && ( PHP_SAPI == 'cgi-fcgi' );
5265        }
5266
5267        /**
5268         * Filters whether IIS 7+ supports pretty permalinks.
5269         *
5270         * @since 2.8.0
5271         *
5272         * @param bool $supports_permalinks Whether IIS7 supports permalinks. Default false.
5273         */
5274        return apply_filters( 'iis7_supports_permalinks', $supports_permalinks );
5275}
5276
5277/**
5278 * Validates a file name and path against an allowed set of rules.
5279 *
5280 * A return value of `1` means the file path contains directory traversal.
5281 *
5282 * A return value of `2` means the file path contains a Windows drive path.
5283 *
5284 * A return value of `3` means the file is not in the allowed files list.
5285 *
5286 * @since 1.2.0
5287 *
5288 * @param string   $file          File path.
5289 * @param string[] $allowed_files Optional. Array of allowed files.
5290 * @return int 0 means nothing is wrong, greater than 0 means something was wrong.
5291 */
5292function validate_file( $file, $allowed_files = array() ) {
5293        // `../` on its own is not allowed:
5294        if ( '../' === $file ) {
5295                return 1;
5296        }
5297
5298        // More than one occurence of `../` is not allowed:
5299        if ( preg_match_all( '#\.\./#', $file, $matches, PREG_SET_ORDER ) && ( count( $matches ) > 1 ) ) {
5300                return 1;
5301        }
5302
5303        // `../` which does not occur at the end of the path is not allowed:
5304        if ( false !== strpos( $file, '../' ) && '../' !== mb_substr( $file, -3, 3 ) ) {
5305                return 1;
5306        }
5307
5308        // Files not in the allowed file list are not allowed:
5309        if ( ! empty( $allowed_files ) && ! in_array( $file, $allowed_files ) ) {
5310                return 3;
5311        }
5312
5313        // Absolute Windows drive paths are not allowed:
5314        if ( ':' == substr( $file, 1, 1 ) ) {
5315                return 2;
5316        }
5317
5318        return 0;
5319}
5320
5321/**
5322 * Whether to force SSL used for the Administration Screens.
5323 *
5324 * @since 2.6.0
5325 *
5326 * @staticvar bool $forced
5327 *
5328 * @param string|bool $force Optional. Whether to force SSL in admin screens. Default null.
5329 * @return bool True if forced, false if not forced.
5330 */
5331function force_ssl_admin( $force = null ) {
5332        static $forced = false;
5333
5334        if ( ! is_null( $force ) ) {
5335                $old_forced = $forced;
5336                $forced     = $force;
5337                return $old_forced;
5338        }
5339
5340        return $forced;
5341}
5342
5343/**
5344 * Guess the URL for the site.
5345 *
5346 * Will remove wp-admin links to retrieve only return URLs not in the wp-admin
5347 * directory.
5348 *
5349 * @since 2.6.0
5350 *
5351 * @return string The guessed URL.
5352 */
5353function wp_guess_url() {
5354        if ( defined( 'WP_SITEURL' ) && '' != WP_SITEURL ) {
5355                $url = WP_SITEURL;
5356        } else {
5357                $abspath_fix         = str_replace( '\\', '/', ABSPATH );
5358                $script_filename_dir = dirname( $_SERVER['SCRIPT_FILENAME'] );
5359
5360                // The request is for the admin.
5361                if ( strpos( $_SERVER['REQUEST_URI'], 'wp-admin' ) !== false || strpos( $_SERVER['REQUEST_URI'], 'wp-login.php' ) !== false ) {
5362                        $path = preg_replace( '#/(wp-admin/.*|wp-login.php)#i', '', $_SERVER['REQUEST_URI'] );
5363
5364                        // The request is for a file in ABSPATH.
5365                } elseif ( $script_filename_dir . '/' == $abspath_fix ) {
5366                        // Strip off any file/query params in the path.
5367                        $path = preg_replace( '#/[^/]*$#i', '', $_SERVER['PHP_SELF'] );
5368
5369                } else {
5370                        if ( false !== strpos( $_SERVER['SCRIPT_FILENAME'], $abspath_fix ) ) {
5371                                // Request is hitting a file inside ABSPATH.
5372                                $directory = str_replace( ABSPATH, '', $script_filename_dir );
5373                                // Strip off the subdirectory, and any file/query params.
5374                                $path = preg_replace( '#/' . preg_quote( $directory, '#' ) . '/[^/]*$#i', '', $_SERVER['REQUEST_URI'] );
5375                        } elseif ( false !== strpos( $abspath_fix, $script_filename_dir ) ) {
5376                                // Request is hitting a file above ABSPATH.
5377                                $subdirectory = substr( $abspath_fix, strpos( $abspath_fix, $script_filename_dir ) + strlen( $script_filename_dir ) );
5378                                // Strip off any file/query params from the path, appending the subdirectory to the installation.
5379                                $path = preg_replace( '#/[^/]*$#i', '', $_SERVER['REQUEST_URI'] ) . $subdirectory;
5380                        } else {
5381                                $path = $_SERVER['REQUEST_URI'];
5382                        }
5383                }
5384
5385                $schema = is_ssl() ? 'https://' : 'http://'; // set_url_scheme() is not defined yet.
5386                $url    = $schema . $_SERVER['HTTP_HOST'] . $path;
5387        }
5388
5389        return rtrim( $url, '/' );
5390}
5391
5392/**
5393 * Temporarily suspend cache additions.
5394 *
5395 * Stops more data being added to the cache, but still allows cache retrieval.
5396 * This is useful for actions, such as imports, when a lot of data would otherwise
5397 * be almost uselessly added to the cache.
5398 *
5399 * Suspension lasts for a single page load at most. Remember to call this
5400 * function again if you wish to re-enable cache adds earlier.
5401 *
5402 * @since 3.3.0
5403 *
5404 * @staticvar bool $_suspend
5405 *
5406 * @param bool $suspend Optional. Suspends additions if true, re-enables them if false.
5407 * @return bool The current suspend setting
5408 */
5409function wp_suspend_cache_addition( $suspend = null ) {
5410        static $_suspend = false;
5411
5412        if ( is_bool( $suspend ) ) {
5413                $_suspend = $suspend;
5414        }
5415
5416        return $_suspend;
5417}
5418
5419/**
5420 * Suspend cache invalidation.
5421 *
5422 * Turns cache invalidation on and off. Useful during imports where you don't want to do
5423 * invalidations every time a post is inserted. Callers must be sure that what they are
5424 * doing won't lead to an inconsistent cache when invalidation is suspended.
5425 *
5426 * @since 2.7.0
5427 *
5428 * @global bool $_wp_suspend_cache_invalidation
5429 *
5430 * @param bool $suspend Optional. Whether to suspend or enable cache invalidation. Default true.
5431 * @return bool The current suspend setting.
5432 */
5433function wp_suspend_cache_invalidation( $suspend = true ) {
5434        global $_wp_suspend_cache_invalidation;
5435
5436        $current_suspend                = $_wp_suspend_cache_invalidation;
5437        $_wp_suspend_cache_invalidation = $suspend;
5438        return $current_suspend;
5439}
5440
5441/**
5442 * Determine whether a site is the main site of the current network.
5443 *
5444 * @since 3.0.0
5445 * @since 4.9.0 The `$network_id` parameter was added.
5446 *
5447 * @param int $site_id    Optional. Site ID to test. Defaults to current site.
5448 * @param int $network_id Optional. Network ID of the network to check for.
5449 *                        Defaults to current network.
5450 * @return bool True if $site_id is the main site of the network, or if not
5451 *              running Multisite.
5452 */
5453function is_main_site( $site_id = null, $network_id = null ) {
5454        if ( ! is_multisite() ) {
5455                return true;
5456        }
5457
5458        if ( ! $site_id ) {
5459                $site_id = get_current_blog_id();
5460        }
5461
5462        $site_id = (int) $site_id;
5463
5464        return get_main_site_id( $network_id ) === $site_id;
5465}
5466
5467/**
5468 * Gets the main site ID.
5469 *
5470 * @since 4.9.0
5471 *
5472 * @param int $network_id Optional. The ID of the network for which to get the main site.
5473 *                        Defaults to the current network.
5474 * @return int The ID of the main site.
5475 */
5476function get_main_site_id( $network_id = null ) {
5477        if ( ! is_multisite() ) {
5478                return get_current_blog_id();
5479        }
5480
5481        $network = get_network( $network_id );
5482        if ( ! $network ) {
5483                return 0;
5484        }
5485
5486        return $network->site_id;
5487}
5488
5489/**
5490 * Determine whether a network is the main network of the Multisite installation.
5491 *
5492 * @since 3.7.0
5493 *
5494 * @param int $network_id Optional. Network ID to test. Defaults to current network.
5495 * @return bool True if $network_id is the main network, or if not running Multisite.
5496 */
5497function is_main_network( $network_id = null ) {
5498        if ( ! is_multisite() ) {
5499                return true;
5500        }
5501
5502        if ( null === $network_id ) {
5503                $network_id = get_current_network_id();
5504        }
5505
5506        $network_id = (int) $network_id;
5507
5508        return ( get_main_network_id() === $network_id );
5509}
5510
5511/**
5512 * Get the main network ID.
5513 *
5514 * @since 4.3.0
5515 *
5516 * @return int The ID of the main network.
5517 */
5518function get_main_network_id() {
5519        if ( ! is_multisite() ) {
5520                return 1;
5521        }
5522
5523        $current_network = get_network();
5524
5525        if ( defined( 'PRIMARY_NETWORK_ID' ) ) {
5526                $main_network_id = PRIMARY_NETWORK_ID;
5527        } elseif ( isset( $current_network->id ) && 1 === (int) $current_network->id ) {
5528                // If the current network has an ID of 1, assume it is the main network.
5529                $main_network_id = 1;
5530        } else {
5531                $_networks       = get_networks(
5532                        array(
5533                                'fields' => 'ids',
5534                                'number' => 1,
5535                        )
5536                );
5537                $main_network_id = array_shift( $_networks );
5538        }
5539
5540        /**
5541         * Filters the main network ID.
5542         *
5543         * @since 4.3.0
5544         *
5545         * @param int $main_network_id The ID of the main network.
5546         */
5547        return (int) apply_filters( 'get_main_network_id', $main_network_id );
5548}
5549
5550/**
5551 * Determine whether global terms are enabled.
5552 *
5553 * @since 3.0.0
5554 *
5555 * @staticvar bool $global_terms
5556 *
5557 * @return bool True if multisite and global terms enabled.
5558 */
5559function global_terms_enabled() {
5560        if ( ! is_multisite() ) {
5561                return false;
5562        }
5563
5564        static $global_terms = null;
5565        if ( is_null( $global_terms ) ) {
5566
5567                /**
5568                 * Filters whether global terms are enabled.
5569                 *
5570                 * Passing a non-null value to the filter will effectively short-circuit the function,
5571                 * returning the value of the 'global_terms_enabled' site option instead.
5572                 *
5573                 * @since 3.0.0
5574                 *
5575                 * @param null $enabled Whether global terms are enabled.
5576                 */
5577                $filter = apply_filters( 'global_terms_enabled', null );
5578                if ( ! is_null( $filter ) ) {
5579                        $global_terms = (bool) $filter;
5580                } else {
5581                        $global_terms = (bool) get_site_option( 'global_terms_enabled', false );
5582                }
5583        }
5584        return $global_terms;
5585}
5586
5587/**
5588 * Determines whether site meta is enabled.
5589 *
5590 * This function checks whether the 'blogmeta' database table exists. The result is saved as
5591 * a setting for the main network, making it essentially a global setting. Subsequent requests
5592 * will refer to this setting instead of running the query.
5593 *
5594 * @since 5.1.0
5595 *
5596 * @global wpdb $wpdb WordPress database abstraction object.
5597 *
5598 * @return bool True if site meta is supported, false otherwise.
5599 */
5600function is_site_meta_supported() {
5601        global $wpdb;
5602
5603        if ( ! is_multisite() ) {
5604                return false;
5605        }
5606
5607        $network_id = get_main_network_id();
5608
5609        $supported = get_network_option( $network_id, 'site_meta_supported', false );
5610        if ( false === $supported ) {
5611                $supported = $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->blogmeta}'" ) ? 1 : 0;
5612
5613                update_network_option( $network_id, 'site_meta_supported', $supported );
5614        }
5615
5616        return (bool) $supported;
5617}
5618
5619/**
5620 * gmt_offset modification for smart timezone handling.
5621 *
5622 * Overrides the gmt_offset option if we have a timezone_string available.
5623 *
5624 * @since 2.8.0
5625 *
5626 * @return float|false Timezone GMT offset, false otherwise.
5627 */
5628function wp_timezone_override_offset() {
5629        $timezone_string = get_option( 'timezone_string' );
5630        if ( ! $timezone_string ) {
5631                return false;
5632        }
5633
5634        $timezone_object = timezone_open( $timezone_string );
5635        $datetime_object = date_create();
5636        if ( false === $timezone_object || false === $datetime_object ) {
5637                return false;
5638        }
5639        return round( timezone_offset_get( $timezone_object, $datetime_object ) / HOUR_IN_SECONDS, 2 );
5640}
5641
5642/**
5643 * Sort-helper for timezones.
5644 *
5645 * @since 2.9.0
5646 * @access private
5647 *
5648 * @param array $a
5649 * @param array $b
5650 * @return int
5651 */
5652function _wp_timezone_choice_usort_callback( $a, $b ) {
5653        // Don't use translated versions of Etc.
5654        if ( 'Etc' === $a['continent'] && 'Etc' === $b['continent'] ) {
5655                // Make the order of these more like the old dropdown.
5656                if ( 'GMT+' === substr( $a['city'], 0, 4 ) && 'GMT+' === substr( $b['city'], 0, 4 ) ) {
5657                        return -1 * ( strnatcasecmp( $a['city'], $b['city'] ) );
5658                }
5659                if ( 'UTC' === $a['city'] ) {
5660                        if ( 'GMT+' === substr( $b['city'], 0, 4 ) ) {
5661                                return 1;
5662                        }
5663                        return -1;
5664                }
5665                if ( 'UTC' === $b['city'] ) {
5666                        if ( 'GMT+' === substr( $a['city'], 0, 4 ) ) {
5667                                return -1;
5668                        }
5669                        return 1;
5670                }
5671                return strnatcasecmp( $a['city'], $b['city'] );
5672        }
5673        if ( $a['t_continent'] == $b['t_continent'] ) {
5674                if ( $a['t_city'] == $b['t_city'] ) {
5675                        return strnatcasecmp( $a['t_subcity'], $b['t_subcity'] );
5676                }
5677                return strnatcasecmp( $a['t_city'], $b['t_city'] );
5678        } else {
5679                // Force Etc to the bottom of the list.
5680                if ( 'Etc' === $a['continent'] ) {
5681                        return 1;
5682                }
5683                if ( 'Etc' === $b['continent'] ) {
5684                        return -1;
5685                }
5686                return strnatcasecmp( $a['t_continent'], $b['t_continent'] );
5687        }
5688}
5689
5690/**
5691 * Gives a nicely-formatted list of timezone strings.
5692 *
5693 * @since 2.9.0
5694 * @since 4.7.0 Added the `$locale` parameter.
5695 *
5696 * @staticvar bool $mo_loaded
5697 * @staticvar string $locale_loaded
5698 *
5699 * @param string $selected_zone Selected timezone.
5700 * @param string $locale        Optional. Locale to load the timezones in. Default current site locale.
5701 * @return string
5702 */
5703function wp_timezone_choice( $selected_zone, $locale = null ) {
5704        static $mo_loaded = false, $locale_loaded = null;
5705
5706        $continents = array( 'Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific' );
5707
5708        // Load translations for continents and cities.
5709        if ( ! $mo_loaded || $locale !== $locale_loaded ) {
5710                $locale_loaded = $locale ? $locale : get_locale();
5711                $mofile        = WP_LANG_DIR . '/continents-cities-' . $locale_loaded . '.mo';
5712                unload_textdomain( 'continents-cities' );
5713                load_textdomain( 'continents-cities', $mofile );
5714                $mo_loaded = true;
5715        }
5716
5717        $zonen = array();
5718        foreach ( timezone_identifiers_list() as $zone ) {
5719                $zone = explode( '/', $zone );
5720                if ( ! in_array( $zone[0], $continents ) ) {
5721                        continue;
5722                }
5723
5724                // This determines what gets set and translated - we don't translate Etc/* strings here, they are done later.
5725                $exists    = array(
5726                        0 => ( isset( $zone[0] ) && $zone[0] ),
5727                        1 => ( isset( $zone[1] ) && $zone[1] ),
5728                        2 => ( isset( $zone[2] ) && $zone[2] ),
5729                );
5730                $exists[3] = ( $exists[0] && 'Etc' !== $zone[0] );
5731                $exists[4] = ( $exists[1] && $exists[3] );
5732                $exists[5] = ( $exists[2] && $exists[3] );
5733
5734                // phpcs:disable WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText
5735                $zonen[] = array(
5736                        'continent'   => ( $exists[0] ? $zone[0] : '' ),
5737                        'city'        => ( $exists[1] ? $zone[1] : '' ),
5738                        'subcity'     => ( $exists[2] ? $zone[2] : '' ),
5739                        't_continent' => ( $exists[3] ? translate( str_replace( '_', ' ', $zone[0] ), 'continents-cities' ) : '' ),
5740                        't_city'      => ( $exists[4] ? translate( str_replace( '_', ' ', $zone[1] ), 'continents-cities' ) : '' ),
5741                        't_subcity'   => ( $exists[5] ? translate( str_replace( '_', ' ', $zone[2] ), 'continents-cities' ) : '' ),
5742                );
5743                // phpcs:enable
5744        }
5745        usort( $zonen, '_wp_timezone_choice_usort_callback' );
5746
5747        $structure = array();
5748
5749        if ( empty( $selected_zone ) ) {
5750                $structure[] = '<option selected="selected" value="">' . __( 'Select a city' ) . '</option>';
5751        }
5752
5753        foreach ( $zonen as $key => $zone ) {
5754                // Build value in an array to join later.
5755                $value = array( $zone['continent'] );
5756
5757                if ( empty( $zone['city'] ) ) {
5758                        // It's at the continent level (generally won't happen).
5759                        $display = $zone['t_continent'];
5760                } else {
5761                        // It's inside a continent group.
5762
5763                        // Continent optgroup.
5764                        if ( ! isset( $zonen[ $key - 1 ] ) || $zonen[ $key - 1 ]['continent'] !== $zone['continent'] ) {
5765                                $label       = $zone['t_continent'];
5766                                $structure[] = '<optgroup label="' . esc_attr( $label ) . '">';
5767                        }
5768
5769                        // Add the city to the value.
5770                        $value[] = $zone['city'];
5771
5772                        $display = $zone['t_city'];
5773                        if ( ! empty( $zone['subcity'] ) ) {
5774                                // Add the subcity to the value.
5775                                $value[]  = $zone['subcity'];
5776                                $display .= ' - ' . $zone['t_subcity'];
5777                        }
5778                }
5779
5780                // Build the value.
5781                $value    = join( '/', $value );
5782                $selected = '';
5783                if ( $value === $selected_zone ) {
5784                        $selected = 'selected="selected" ';
5785                }
5786                $structure[] = '<option ' . $selected . 'value="' . esc_attr( $value ) . '">' . esc_html( $display ) . '</option>';
5787
5788                // Close continent optgroup.
5789                if ( ! empty( $zone['city'] ) && ( ! isset( $zonen[ $key + 1 ] ) || ( isset( $zonen[ $key + 1 ] ) && $zonen[ $key + 1 ]['continent'] !== $zone['continent'] ) ) ) {
5790                        $structure[] = '</optgroup>';
5791                }
5792        }
5793
5794        // Do UTC.
5795        $structure[] = '<optgroup label="' . esc_attr__( 'UTC' ) . '">';
5796        $selected    = '';
5797        if ( 'UTC' === $selected_zone ) {
5798                $selected = 'selected="selected" ';
5799        }
5800        $structure[] = '<option ' . $selected . 'value="' . esc_attr( 'UTC' ) . '">' . __( 'UTC' ) . '</option>';
5801        $structure[] = '</optgroup>';
5802
5803        // Do manual UTC offsets.
5804        $structure[]  = '<optgroup label="' . esc_attr__( 'Manual Offsets' ) . '">';
5805        $offset_range = array(
5806                -12,
5807                -11.5,
5808                -11,
5809                -10.5,
5810                -10,
5811                -9.5,
5812                -9,
5813                -8.5,
5814                -8,
5815                -7.5,
5816                -7,
5817                -6.5,
5818                -6,
5819                -5.5,
5820                -5,
5821                -4.5,
5822                -4,
5823                -3.5,
5824                -3,
5825                -2.5,
5826                -2,
5827                -1.5,
5828                -1,
5829                -0.5,
5830                0,
5831                0.5,
5832                1,
5833                1.5,
5834                2,
5835                2.5,
5836                3,
5837                3.5,
5838                4,
5839                4.5,
5840                5,
5841                5.5,
5842                5.75,
5843                6,
5844                6.5,
5845                7,
5846                7.5,
5847                8,
5848                8.5,
5849                8.75,
5850                9,
5851                9.5,
5852                10,
5853                10.5,
5854                11,
5855                11.5,
5856                12,
5857                12.75,
5858                13,
5859                13.75,
5860                14,
5861        );
5862        foreach ( $offset_range as $offset ) {
5863                if ( 0 <= $offset ) {
5864                        $offset_name = '+' . $offset;
5865                } else {
5866                        $offset_name = (string) $offset;
5867                }
5868
5869                $offset_value = $offset_name;
5870                $offset_name  = str_replace( array( '.25', '.5', '.75' ), array( ':15', ':30', ':45' ), $offset_name );
5871                $offset_name  = 'UTC' . $offset_name;
5872                $offset_value = 'UTC' . $offset_value;
5873                $selected     = '';
5874                if ( $offset_value === $selected_zone ) {
5875                        $selected = 'selected="selected" ';
5876                }
5877                $structure[] = '<option ' . $selected . 'value="' . esc_attr( $offset_value ) . '">' . esc_html( $offset_name ) . '</option>';
5878
5879        }
5880        $structure[] = '</optgroup>';
5881
5882        return join( "\n", $structure );
5883}
5884
5885/**
5886 * Strip close comment and close php tags from file headers used by WP.
5887 *
5888 * @since 2.8.0
5889 * @access private
5890 *
5891 * @see https://core.trac.wordpress.org/ticket/8497
5892 *
5893 * @param string $str Header comment to clean up.
5894 * @return string
5895 */
5896function _cleanup_header_comment( $str ) {
5897        return trim( preg_replace( '/\s*(?:\*\/|\?>).*/', '', $str ) );
5898}
5899
5900/**
5901 * Permanently delete comments or posts of any type that have held a status
5902 * of 'trash' for the number of days defined in EMPTY_TRASH_DAYS.
5903 *
5904 * The default value of `EMPTY_TRASH_DAYS` is 30 (days).
5905 *
5906 * @since 2.9.0
5907 *
5908 * @global wpdb $wpdb WordPress database abstraction object.
5909 */
5910function wp_scheduled_delete() {
5911        global $wpdb;
5912
5913        $delete_timestamp = time() - ( DAY_IN_SECONDS * EMPTY_TRASH_DAYS );
5914
5915        $posts_to_delete = $wpdb->get_results( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_trash_meta_time' AND meta_value < %d", $delete_timestamp ), ARRAY_A );
5916
5917        foreach ( (array) $posts_to_delete as $post ) {
5918                $post_id = (int) $post['post_id'];
5919                if ( ! $post_id ) {
5920                        continue;
5921                }
5922
5923                $del_post = get_post( $post_id );
5924
5925                if ( ! $del_post || 'trash' != $del_post->post_status ) {
5926                        delete_post_meta( $post_id, '_wp_trash_meta_status' );
5927                        delete_post_meta( $post_id, '_wp_trash_meta_time' );
5928                } else {
5929                        wp_delete_post( $post_id );
5930                }
5931        }
5932
5933        $comments_to_delete = $wpdb->get_results( $wpdb->prepare( "SELECT comment_id FROM $wpdb->commentmeta WHERE meta_key = '_wp_trash_meta_time' AND meta_value < %d", $delete_timestamp ), ARRAY_A );
5934
5935        foreach ( (array) $comments_to_delete as $comment ) {
5936                $comment_id = (int) $comment['comment_id'];
5937                if ( ! $comment_id ) {
5938                        continue;
5939                }
5940
5941                $del_comment = get_comment( $comment_id );
5942
5943                if ( ! $del_comment || 'trash' != $del_comment->comment_approved ) {
5944                        delete_comment_meta( $comment_id, '_wp_trash_meta_time' );
5945                        delete_comment_meta( $comment_id, '_wp_trash_meta_status' );
5946                } else {
5947                        wp_delete_comment( $del_comment );
5948                }
5949        }
5950}
5951
5952/**
5953 * Retrieve metadata from a file.
5954 *
5955 * Searches for metadata in the first 8 KB of a file, such as a plugin or theme.
5956 * Each piece of metadata must be on its own line. Fields can not span multiple
5957 * lines, the value will get cut at the end of the first line.
5958 *
5959 * If the file data is not within that first 8 KB, then the author should correct
5960 * their plugin file and move the data headers to the top.
5961 *
5962 * @link https://codex.wordpress.org/File_Header
5963 *
5964 * @since 2.9.0
5965 *
5966 * @param string $file            Absolute path to the file.
5967 * @param array  $default_headers List of headers, in the format `array( 'HeaderKey' => 'Header Name' )`.
5968 * @param string $context         Optional. If specified adds filter hook {@see 'extra_$context_headers'}.
5969 *                                Default empty.
5970 * @return string[] Array of file header values keyed by header name.
5971 */
5972function get_file_data( $file, $default_headers, $context = '' ) {
5973        // We don't need to write to the file, so just open for reading.
5974        $fp = fopen( $file, 'r' );
5975
5976        // Pull only the first 8 KB of the file in.
5977        $file_data = fread( $fp, 8 * KB_IN_BYTES );
5978
5979        // PHP will close file handle, but we are good citizens.
5980        fclose( $fp );
5981
5982        // Make sure we catch CR-only line endings.
5983        $file_data = str_replace( "\r", "\n", $file_data );
5984
5985        /**
5986         * Filters extra file headers by context.
5987         *
5988         * The dynamic portion of the hook name, `$context`, refers to
5989         * the context where extra headers might be loaded.
5990         *
5991         * @since 2.9.0
5992         *
5993         * @param array $extra_context_headers Empty array by default.
5994         */
5995        $extra_headers = $context ? apply_filters( "extra_{$context}_headers", array() ) : array();
5996        if ( $extra_headers ) {
5997                $extra_headers = array_combine( $extra_headers, $extra_headers ); // Keys equal values.
5998                $all_headers   = array_merge( $extra_headers, (array) $default_headers );
5999        } else {
6000                $all_headers = $default_headers;
6001        }
6002
6003        foreach ( $all_headers as $field => $regex ) {
6004                if ( preg_match( '/^[ \t\/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi', $file_data, $match ) && $match[1] ) {
6005                        $all_headers[ $field ] = _cleanup_header_comment( $match[1] );
6006                } else {
6007                        $all_headers[ $field ] = '';
6008                }
6009        }
6010
6011        return $all_headers;
6012}
6013
6014/**
6015 * Returns true.
6016 *
6017 * Useful for returning true to filters easily.
6018 *
6019 * @since 3.0.0
6020 *
6021 * @see __return_false()
6022 *
6023 * @return true True.
6024 */
6025function __return_true() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
6026        return true;
6027}
6028
6029/**
6030 * Returns false.
6031 *
6032 * Useful for returning false to filters easily.
6033 *
6034 * @since 3.0.0
6035 *
6036 * @see __return_true()
6037 *
6038 * @return false False.
6039 */
6040function __return_false() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
6041        return false;
6042}
6043
6044/**
6045 * Returns 0.
6046 *
6047 * Useful for returning 0 to filters easily.
6048 *
6049 * @since 3.0.0
6050 *
6051 * @return int 0.
6052 */
6053function __return_zero() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
6054        return 0;
6055}
6056
6057/**
6058 * Returns an empty array.
6059 *
6060 * Useful for returning an empty array to filters easily.
6061 *
6062 * @since 3.0.0
6063 *
6064 * @return array Empty array.
6065 */
6066function __return_empty_array() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
6067        return array();
6068}
6069
6070/**
6071 * Returns null.
6072 *
6073 * Useful for returning null to filters easily.
6074 *
6075 * @since 3.4.0
6076 *
6077 * @return null Null value.
6078 */
6079function __return_null() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
6080        return null;
6081}
6082
6083/**
6084 * Returns an empty string.
6085 *
6086 * Useful for returning an empty string to filters easily.
6087 *
6088 * @since 3.7.0
6089 *
6090 * @see __return_null()
6091 *
6092 * @return string Empty string.
6093 */
6094function __return_empty_string() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
6095        return '';
6096}
6097
6098/**
6099 * Send a HTTP header to disable content type sniffing in browsers which support it.
6100 *
6101 * @since 3.0.0
6102 *
6103 * @see https://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx
6104 * @see https://src.chromium.org/viewvc/chrome?view=rev&revision=6985
6105 */
6106function send_nosniff_header() {
6107        header( 'X-Content-Type-Options: nosniff' );
6108}
6109
6110/**
6111 * Return a MySQL expression for selecting the week number based on the start_of_week option.
6112 *
6113 * @ignore
6114 * @since 3.0.0
6115 *
6116 * @param string $column Database column.
6117 * @return string SQL clause.
6118 */
6119function _wp_mysql_week( $column ) {
6120        $start_of_week = (int) get_option( 'start_of_week' );
6121        switch ( $start_of_week ) {
6122                case 1:
6123                        return "WEEK( $column, 1 )";
6124                case 2:
6125                case 3:
6126                case 4:
6127                case 5:
6128                case 6:
6129                        return "WEEK( DATE_SUB( $column, INTERVAL $start_of_week DAY ), 0 )";
6130                case 0:
6131                default:
6132                        return "WEEK( $column, 0 )";
6133        }
6134}
6135
6136/**
6137 * Find hierarchy loops using a callback function that maps object IDs to parent IDs.
6138 *
6139 * @since 3.1.0
6140 * @access private
6141 *
6142 * @param callable $callback      Function that accepts ( ID, $callback_args ) and outputs parent_ID.
6143 * @param int      $start         The ID to start the loop check at.
6144 * @param int      $start_parent  The parent_ID of $start to use instead of calling $callback( $start ).
6145 *                                Use null to always use $callback
6146 * @param array    $callback_args Optional. Additional arguments to send to $callback.
6147 * @return array IDs of all members of loop.
6148 */
6149function wp_find_hierarchy_loop( $callback, $start, $start_parent, $callback_args = array() ) {
6150        $override = is_null( $start_parent ) ? array() : array( $start => $start_parent );
6151
6152        $arbitrary_loop_member = wp_find_hierarchy_loop_tortoise_hare( $callback, $start, $override, $callback_args );
6153        if ( ! $arbitrary_loop_member ) {
6154                return array();
6155        }
6156
6157        return wp_find_hierarchy_loop_tortoise_hare( $callback, $arbitrary_loop_member, $override, $callback_args, true );
6158}
6159
6160/**
6161 * Use the "The Tortoise and the Hare" algorithm to detect loops.
6162 *
6163 * For every step of the algorithm, the hare takes two steps and the tortoise one.
6164 * If the hare ever laps the tortoise, there must be a loop.
6165 *
6166 * @since 3.1.0
6167 * @access private
6168 *
6169 * @param callable $callback      Function that accepts ( ID, callback_arg, ... ) and outputs parent_ID.
6170 * @param int      $start         The ID to start the loop check at.
6171 * @param array    $override      Optional. An array of ( ID => parent_ID, ... ) to use instead of $callback.
6172 *                                Default empty array.
6173 * @param array    $callback_args Optional. Additional arguments to send to $callback. Default empty array.
6174 * @param bool     $_return_loop  Optional. Return loop members or just detect presence of loop? Only set
6175 *                                to true if you already know the given $start is part of a loop (otherwise
6176 *                                the returned array might include branches). Default false.
6177 * @return mixed Scalar ID of some arbitrary member of the loop, or array of IDs of all members of loop if
6178 *               $_return_loop
6179 */
6180function wp_find_hierarchy_loop_tortoise_hare( $callback, $start, $override = array(), $callback_args = array(), $_return_loop = false ) {
6181        $tortoise        = $start;
6182        $hare            = $start;
6183        $evanescent_hare = $start;
6184        $return          = array();
6185
6186        // Set evanescent_hare to one past hare.
6187        // Increment hare two steps.
6188        while (
6189                $tortoise
6190        &&
6191                ( $evanescent_hare = isset( $override[ $hare ] ) ? $override[ $hare ] : call_user_func_array( $callback, array_merge( array( $hare ), $callback_args ) ) )
6192        &&
6193                ( $hare = isset( $override[ $evanescent_hare ] ) ? $override[ $evanescent_hare ] : call_user_func_array( $callback, array_merge( array( $evanescent_hare ), $callback_args ) ) )
6194        ) {
6195                if ( $_return_loop ) {
6196                        $return[ $tortoise ]        = true;
6197                        $return[ $evanescent_hare ] = true;
6198                        $return[ $hare ]            = true;
6199                }
6200
6201                // Tortoise got lapped - must be a loop.
6202                if ( $tortoise == $evanescent_hare || $tortoise == $hare ) {
6203                        return $_return_loop ? $return : $tortoise;
6204                }
6205
6206                // Increment tortoise by one step.
6207                $tortoise = isset( $override[ $tortoise ] ) ? $override[ $tortoise ] : call_user_func_array( $callback, array_merge( array( $tortoise ), $callback_args ) );
6208        }
6209
6210        return false;
6211}
6212
6213/**
6214 * Send a HTTP header to limit rendering of pages to same origin iframes.
6215 *
6216 * @since 3.1.3
6217 *
6218 * @see https://developer.mozilla.org/en/the_x-frame-options_response_header
6219 */
6220function send_frame_options_header() {
6221        header( 'X-Frame-Options: SAMEORIGIN' );
6222}
6223
6224/**
6225 * Retrieve a list of protocols to allow in HTML attributes.
6226 *
6227 * @since 3.3.0
6228 * @since 4.3.0 Added 'webcal' to the protocols array.
6229 * @since 4.7.0 Added 'urn' to the protocols array.
6230 * @since 5.3.0 Added 'sms' to the protocols array.
6231 *
6232 * @see wp_kses()
6233 * @see esc_url()
6234 *
6235 * @staticvar array $protocols
6236 *
6237 * @return string[] Array of allowed protocols. Defaults to an array containing 'http', 'https',
6238 *                  'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet',
6239 *                  'mms', 'rtsp', 'sms', 'svn', 'tel', 'fax', 'xmpp', 'webcal', and 'urn'.
6240 *                  This covers all common link protocols, except for 'javascript' which should not
6241 *                  be allowed for untrusted users.
6242 */
6243function wp_allowed_protocols() {
6244        static $protocols = array();
6245
6246        if ( empty( $protocols ) ) {
6247                $protocols = array( 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'sms', 'svn', 'tel', 'fax', 'xmpp', 'webcal', 'urn' );
6248        }
6249
6250        if ( ! did_action( 'wp_loaded' ) ) {
6251                /**
6252                 * Filters the list of protocols allowed in HTML attributes.
6253                 *
6254                 * @since 3.0.0
6255                 *
6256                 * @param string[] $protocols Array of allowed protocols e.g. 'http', 'ftp', 'tel', and more.
6257                 */
6258                $protocols = array_unique( (array) apply_filters( 'kses_allowed_protocols', $protocols ) );
6259        }
6260
6261        return $protocols;
6262}
6263
6264/**
6265 * Return a comma-separated string of functions that have been called to get
6266 * to the current point in code.
6267 *
6268 * @since 3.4.0
6269 *
6270 * @see https://core.trac.wordpress.org/ticket/19589
6271 *
6272 * @staticvar array $truncate_paths Array of paths to truncate.
6273 *
6274 * @param string $ignore_class Optional. A class to ignore all function calls within - useful
6275 *                             when you want to just give info about the callee. Default null.
6276 * @param int    $skip_frames  Optional. A number of stack frames to skip - useful for unwinding
6277 *                             back to the source of the issue. Default 0.
6278 * @param bool   $pretty       Optional. Whether or not you want a comma separated string or raw
6279 *                             array returned. Default true.
6280 * @return string|array Either a string containing a reversed comma separated trace or an array
6281 *                      of individual calls.
6282 */
6283function wp_debug_backtrace_summary( $ignore_class = null, $skip_frames = 0, $pretty = true ) {
6284        static $truncate_paths;
6285
6286        $trace       = debug_backtrace( false );
6287        $caller      = array();
6288        $check_class = ! is_null( $ignore_class );
6289        $skip_frames++; // Skip this function.
6290
6291        if ( ! isset( $truncate_paths ) ) {
6292                $truncate_paths = array(
6293                        wp_normalize_path( WP_CONTENT_DIR ),
6294                        wp_normalize_path( ABSPATH ),
6295                );
6296        }
6297
6298        foreach ( $trace as $call ) {
6299                if ( $skip_frames > 0 ) {
6300                        $skip_frames--;
6301                } elseif ( isset( $call['class'] ) ) {
6302                        if ( $check_class && $ignore_class == $call['class'] ) {
6303                                continue; // Filter out calls.
6304                        }
6305
6306                        $caller[] = "{$call['class']}{$call['type']}{$call['function']}";
6307                } else {
6308                        if ( in_array( $call['function'], array( 'do_action', 'apply_filters', 'do_action_ref_array', 'apply_filters_ref_array' ) ) ) {
6309                                $caller[] = "{$call['function']}('{$call['args'][0]}')";
6310                        } elseif ( in_array( $call['function'], array( 'include', 'include_once', 'require', 'require_once' ) ) ) {
6311                                $filename = isset( $call['args'][0] ) ? $call['args'][0] : '';
6312                                $caller[] = $call['function'] . "('" . str_replace( $truncate_paths, '', wp_normalize_path( $filename ) ) . "')";
6313                        } else {
6314                                $caller[] = $call['function'];
6315                        }
6316                }
6317        }
6318        if ( $pretty ) {
6319                return join( ', ', array_reverse( $caller ) );
6320        } else {
6321                return $caller;
6322        }
6323}
6324
6325/**
6326 * Retrieve IDs that are not already present in the cache.
6327 *
6328 * @since 3.4.0
6329 * @access private
6330 *
6331 * @param int[]  $object_ids Array of IDs.
6332 * @param string $cache_key  The cache bucket to check against.
6333 * @return int[] Array of IDs not present in the cache.
6334 */
6335function _get_non_cached_ids( $object_ids, $cache_key ) {
6336        $clean = array();
6337        foreach ( $object_ids as $id ) {
6338                $id = (int) $id;
6339                if ( ! wp_cache_get( $id, $cache_key ) ) {
6340                        $clean[] = $id;
6341                }
6342        }
6343
6344        return $clean;
6345}
6346
6347/**
6348 * Test if the current device has the capability to upload files.
6349 *
6350 * @since 3.4.0
6351 * @access private
6352 *
6353 * @return bool Whether the device is able to upload files.
6354 */
6355function _device_can_upload() {
6356        if ( ! wp_is_mobile() ) {
6357                return true;
6358        }
6359
6360        $ua = $_SERVER['HTTP_USER_AGENT'];
6361
6362        if ( strpos( $ua, 'iPhone' ) !== false
6363                || strpos( $ua, 'iPad' ) !== false
6364                || strpos( $ua, 'iPod' ) !== false ) {
6365                        return preg_match( '#OS ([\d_]+) like Mac OS X#', $ua, $version ) && version_compare( $version[1], '6', '>=' );
6366        }
6367
6368        return true;
6369}
6370
6371/**
6372 * Test if a given path is a stream URL
6373 *
6374 * @since 3.5.0
6375 *
6376 * @param string $path The resource path or URL.
6377 * @return bool True if the path is a stream URL.
6378 */
6379function wp_is_stream( $path ) {
6380        $scheme_separator = strpos( $path, '://' );
6381
6382        if ( false === $scheme_separator ) {
6383                // $path isn't a stream.
6384                return false;
6385        }
6386
6387        $stream = substr( $path, 0, $scheme_separator );
6388
6389        return in_array( $stream, stream_get_wrappers(), true );
6390}
6391
6392/**
6393 * Test if the supplied date is valid for the Gregorian calendar.
6394 *
6395 * @since 3.5.0
6396 *
6397 * @link https://www.php.net/manual/en/function.checkdate.php
6398 *
6399 * @param  int    $month       Month number.
6400 * @param  int    $day         Day number.
6401 * @param  int    $year        Year number.
6402 * @param  string $source_date The date to filter.
6403 * @return bool True if valid date, false if not valid date.
6404 */
6405function wp_checkdate( $month, $day, $year, $source_date ) {
6406        /**
6407         * Filters whether the given date is valid for the Gregorian calendar.
6408         *
6409         * @since 3.5.0
6410         *
6411         * @param bool   $checkdate   Whether the given date is valid.
6412         * @param string $source_date Date to check.
6413         */
6414        return apply_filters( 'wp_checkdate', checkdate( $month, $day, $year ), $source_date );
6415}
6416
6417/**
6418 * Load the auth check for monitoring whether the user is still logged in.
6419 *
6420 * Can be disabled with remove_action( 'admin_enqueue_scripts', 'wp_auth_check_load' );
6421 *
6422 * This is disabled for certain screens where a login screen could cause an
6423 * inconvenient interruption. A filter called {@see 'wp_auth_check_load'} can be used
6424 * for fine-grained control.
6425 *
6426 * @since 3.6.0
6427 */
6428function wp_auth_check_load() {
6429        if ( ! is_admin() && ! is_user_logged_in() ) {
6430                return;
6431        }
6432
6433        if ( defined( 'IFRAME_REQUEST' ) ) {
6434                return;
6435        }
6436
6437        $screen = get_current_screen();
6438        $hidden = array( 'update', 'update-network', 'update-core', 'update-core-network', 'upgrade', 'upgrade-network', 'network' );
6439        $show   = ! in_array( $screen->id, $hidden );
6440
6441        /**
6442         * Filters whether to load the authentication check.
6443         *
6444         * Passing a falsey value to the filter will effectively short-circuit
6445         * loading the authentication check.
6446         *
6447         * @since 3.6.0
6448         *
6449         * @param bool      $show   Whether to load the authentication check.
6450         * @param WP_Screen $screen The current screen object.
6451         */
6452        if ( apply_filters( 'wp_auth_check_load', $show, $screen ) ) {
6453                wp_enqueue_style( 'wp-auth-check' );
6454                wp_enqueue_script( 'wp-auth-check' );
6455
6456                add_action( 'admin_print_footer_scripts', 'wp_auth_check_html', 5 );
6457                add_action( 'wp_print_footer_scripts', 'wp_auth_check_html', 5 );
6458        }
6459}
6460
6461/**
6462 * Output the HTML that shows the wp-login dialog when the user is no longer logged in.
6463 *
6464 * @since 3.6.0
6465 */
6466function wp_auth_check_html() {
6467        $login_url      = wp_login_url();
6468        $current_domain = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'];
6469        $same_domain    = ( strpos( $login_url, $current_domain ) === 0 );
6470
6471        /**
6472         * Filters whether the authentication check originated at the same domain.
6473         *
6474         * @since 3.6.0
6475         *
6476         * @param bool $same_domain Whether the authentication check originated at the same domain.
6477         */
6478        $same_domain = apply_filters( 'wp_auth_check_same_domain', $same_domain );
6479        $wrap_class  = $same_domain ? 'hidden' : 'hidden fallback';
6480
6481        ?>
6482        <div id="wp-auth-check-wrap" class="<?php echo $wrap_class; ?>">
6483        <div id="wp-auth-check-bg"></div>
6484        <div id="wp-auth-check">
6485        <button type="button" class="wp-auth-check-close button-link"><span class="screen-reader-text"><?php _e( 'Close dialog' ); ?></span></button>
6486        <?php
6487
6488        if ( $same_domain ) {
6489                $login_src = add_query_arg(
6490                        array(
6491                                'interim-login' => '1',
6492                                'wp_lang'       => get_user_locale(),
6493                        ),
6494                        $login_url
6495                );
6496                ?>
6497                <div id="wp-auth-check-form" class="loading" data-src="<?php echo esc_url( $login_src ); ?>"></div>
6498                <?php
6499        }
6500
6501        ?>
6502        <div class="wp-auth-fallback">
6503                <p><b class="wp-auth-fallback-expired" tabindex="0"><?php _e( 'Session expired' ); ?></b></p>
6504                <p><a href="<?php echo esc_url( $login_url ); ?>" target="_blank"><?php _e( 'Please log in again.' ); ?></a>
6505                <?php _e( 'The login page will open in a new tab. After logging in you can close it and return to this page.' ); ?></p>
6506        </div>
6507        </div>
6508        </div>
6509        <?php
6510}
6511
6512/**
6513 * Check whether a user is still logged in, for the heartbeat.
6514 *
6515 * Send a result that shows a log-in box if the user is no longer logged in,
6516 * or if their cookie is within the grace period.
6517 *
6518 * @since 3.6.0
6519 *
6520 * @global int $login_grace_period
6521 *
6522 * @param array $response  The Heartbeat response.
6523 * @return array The Heartbeat response with 'wp-auth-check' value set.
6524 */
6525function wp_auth_check( $response ) {
6526        $response['wp-auth-check'] = is_user_logged_in() && empty( $GLOBALS['login_grace_period'] );
6527        return $response;
6528}
6529
6530/**
6531 * Return RegEx body to liberally match an opening HTML tag.
6532 *
6533 * Matches an opening HTML tag that:
6534 * 1. Is self-closing or
6535 * 2. Has no body but has a closing tag of the same name or
6536 * 3. Contains a body and a closing tag of the same name
6537 *
6538 * Note: this RegEx does not balance inner tags and does not attempt
6539 * to produce valid HTML
6540 *
6541 * @since 3.6.0
6542 *
6543 * @param string $tag An HTML tag name. Example: 'video'.
6544 * @return string Tag RegEx.
6545 */
6546function get_tag_regex( $tag ) {
6547        if ( empty( $tag ) ) {
6548                return;
6549        }
6550        return sprintf( '<%1$s[^<]*(?:>[\s\S]*<\/%1$s>|\s*\/>)', tag_escape( $tag ) );
6551}
6552
6553/**
6554 * Retrieve a canonical form of the provided charset appropriate for passing to PHP
6555 * functions such as htmlspecialchars() and charset html attributes.
6556 *
6557 * @since 3.6.0
6558 * @access private
6559 *
6560 * @see https://core.trac.wordpress.org/ticket/23688
6561 *
6562 * @param string $charset A charset name.
6563 * @return string The canonical form of the charset.
6564 */
6565function _canonical_charset( $charset ) {
6566        if ( 'utf-8' === strtolower( $charset ) || 'utf8' === strtolower( $charset ) ) {
6567
6568                return 'UTF-8';
6569        }
6570
6571        if ( 'iso-8859-1' === strtolower( $charset ) || 'iso8859-1' === strtolower( $charset ) ) {
6572
6573                return 'ISO-8859-1';
6574        }
6575
6576        return $charset;
6577}
6578
6579/**
6580 * Set the mbstring internal encoding to a binary safe encoding when func_overload
6581 * is enabled.
6582 *
6583 * When mbstring.func_overload is in use for multi-byte encodings, the results from
6584 * strlen() and similar functions respect the utf8 characters, causing binary data
6585 * to return incorrect lengths.
6586 *
6587 * This function overrides the mbstring encoding to a binary-safe encoding, and
6588 * resets it to the users expected encoding afterwards through the
6589 * `reset_mbstring_encoding` function.
6590 *
6591 * It is safe to recursively call this function, however each
6592 * `mbstring_binary_safe_encoding()` call must be followed up with an equal number
6593 * of `reset_mbstring_encoding()` calls.
6594 *
6595 * @since 3.7.0
6596 *
6597 * @see reset_mbstring_encoding()
6598 *
6599 * @staticvar array $encodings
6600 * @staticvar bool  $overloaded
6601 *
6602 * @param bool $reset Optional. Whether to reset the encoding back to a previously-set encoding.
6603 *                    Default false.
6604 */
6605function mbstring_binary_safe_encoding( $reset = false ) {
6606        static $encodings  = array();
6607        static $overloaded = null;
6608
6609        if ( is_null( $overloaded ) ) {
6610                $overloaded = function_exists( 'mb_internal_encoding' ) && ( ini_get( 'mbstring.func_overload' ) & 2 );
6611        }
6612
6613        if ( false === $overloaded ) {
6614                return;
6615        }
6616
6617        if ( ! $reset ) {
6618                $encoding = mb_internal_encoding();
6619                array_push( $encodings, $encoding );
6620                mb_internal_encoding( 'ISO-8859-1' );
6621        }
6622
6623        if ( $reset && $encodings ) {
6624                $encoding = array_pop( $encodings );
6625                mb_internal_encoding( $encoding );
6626        }
6627}
6628
6629/**
6630 * Reset the mbstring internal encoding to a users previously set encoding.
6631 *
6632 * @see mbstring_binary_safe_encoding()
6633 *
6634 * @since 3.7.0
6635 */
6636function reset_mbstring_encoding() {
6637        mbstring_binary_safe_encoding( true );
6638}
6639
6640/**
6641 * Filter/validate a variable as a boolean.
6642 *
6643 * Alternative to `filter_var( $var, FILTER_VALIDATE_BOOLEAN )`.
6644 *
6645 * @since 4.0.0
6646 *
6647 * @param mixed $var Boolean value to validate.
6648 * @return bool Whether the value is validated.
6649 */
6650function wp_validate_boolean( $var ) {
6651        if ( is_bool( $var ) ) {
6652                return $var;
6653        }
6654
6655        if ( is_string( $var ) && 'false' === strtolower( $var ) ) {
6656                return false;
6657        }
6658
6659        return (bool) $var;
6660}
6661
6662/**
6663 * Delete a file
6664 *
6665 * @since 4.2.0
6666 *
6667 * @param string $file The path to the file to delete.
6668 */
6669function wp_delete_file( $file ) {
6670        /**
6671         * Filters the path of the file to delete.
6672         *
6673         * @since 2.1.0
6674         *
6675         * @param string $file Path to the file to delete.
6676         */
6677        $delete = apply_filters( 'wp_delete_file', $file );
6678        if ( ! empty( $delete ) ) {
6679                @unlink( $delete );
6680        }
6681}
6682
6683/**
6684 * Deletes a file if its path is within the given directory.
6685 *
6686 * @since 4.9.7
6687 *
6688 * @param string $file      Absolute path to the file to delete.
6689 * @param string $directory Absolute path to a directory.
6690 * @return bool True on success, false on failure.
6691 */
6692function wp_delete_file_from_directory( $file, $directory ) {
6693        if ( wp_is_stream( $file ) ) {
6694                $real_file      = $file;
6695                $real_directory = $directory;
6696        } else {
6697                $real_file      = realpath( wp_normalize_path( $file ) );
6698                $real_directory = realpath( wp_normalize_path( $directory ) );
6699        }
6700
6701        if ( false !== $real_file ) {
6702                $real_file = wp_normalize_path( $real_file );
6703        }
6704
6705        if ( false !== $real_directory ) {
6706                $real_directory = wp_normalize_path( $real_directory );
6707        }
6708
6709        if ( false === $real_file || false === $real_directory || strpos( $real_file, trailingslashit( $real_directory ) ) !== 0 ) {
6710                return false;
6711        }
6712
6713        wp_delete_file( $file );
6714
6715        return true;
6716}
6717
6718/**
6719 * Outputs a small JS snippet on preview tabs/windows to remove `window.name` on unload.
6720 *
6721 * This prevents reusing the same tab for a preview when the user has navigated away.
6722 *
6723 * @since 4.3.0
6724 *
6725 * @global WP_Post $post Global post object.
6726 */
6727function wp_post_preview_js() {
6728        global $post;
6729
6730        if ( ! is_preview() || empty( $post ) ) {
6731                return;
6732        }
6733
6734        // Has to match the window name used in post_submit_meta_box().
6735        $name = 'wp-preview-' . (int) $post->ID;
6736
6737        ?>
6738        <script>
6739        ( function() {
6740                var query = document.location.search;
6741
6742                if ( query && query.indexOf( 'preview=true' ) !== -1 ) {
6743                        window.name = '<?php echo $name; ?>';
6744                }
6745
6746                if ( window.addEventListener ) {
6747                        window.addEventListener( 'unload', function() { window.name = ''; }, false );
6748                }
6749        }());
6750        </script>
6751        <?php
6752}
6753
6754/**
6755 * Parses and formats a MySQL datetime (Y-m-d H:i:s) for ISO8601 (Y-m-d\TH:i:s).
6756 *
6757 * Explicitly strips timezones, as datetimes are not saved with any timezone
6758 * information. Including any information on the offset could be misleading.
6759 *
6760 * Despite historical function name, the output does not conform to RFC3339 format,
6761 * which must contain timezone.
6762 *
6763 * @since 4.4.0
6764 *
6765 * @param string $date_string Date string to parse and format.
6766 * @return string Date formatted for ISO8601 without time zone.
6767 */
6768function mysql_to_rfc3339( $date_string ) {
6769        return mysql2date( 'Y-m-d\TH:i:s', $date_string, false );
6770}
6771
6772/**
6773 * Attempts to raise the PHP memory limit for memory intensive processes.
6774 *
6775 * Only allows raising the existing limit and prevents lowering it.
6776 *
6777 * @since 4.6.0
6778 *
6779 * @param string $context Optional. Context in which the function is called. Accepts either 'admin',
6780 *                        'image', or an arbitrary other context. If an arbitrary context is passed,
6781 *                        the similarly arbitrary {@see '{$context}_memory_limit'} filter will be
6782 *                        invoked. Default 'admin'.
6783 * @return bool|int|string The limit that was set or false on failure.
6784 */
6785function wp_raise_memory_limit( $context = 'admin' ) {
6786        // Exit early if the limit cannot be changed.
6787        if ( false === wp_is_ini_value_changeable( 'memory_limit' ) ) {
6788                return false;
6789        }
6790
6791        $current_limit     = ini_get( 'memory_limit' );
6792        $current_limit_int = wp_convert_hr_to_bytes( $current_limit );
6793
6794        if ( -1 === $current_limit_int ) {
6795                return false;
6796        }
6797
6798        $wp_max_limit     = WP_MAX_MEMORY_LIMIT;
6799        $wp_max_limit_int = wp_convert_hr_to_bytes( $wp_max_limit );
6800        $filtered_limit   = $wp_max_limit;
6801
6802        switch ( $context ) {
6803                case 'admin':
6804                        /**
6805                         * Filters the maximum memory limit available for administration screens.
6806                         *
6807                         * This only applies to administrators, who may require more memory for tasks
6808                         * like updates. Memory limits when processing images (uploaded or edited by
6809                         * users of any role) are handled separately.
6810                         *
6811                         * The `WP_MAX_MEMORY_LIMIT` constant specifically defines the maximum memory
6812                         * limit available when in the administration back end. The default is 256M
6813                         * (256 megabytes of memory) or the original `memory_limit` php.ini value if
6814                         * this is higher.
6815                         *
6816                         * @since 3.0.0
6817                         * @since 4.6.0 The default now takes the original `memory_limit` into account.
6818                         *
6819                         * @param int|string $filtered_limit The maximum WordPress memory limit. Accepts an integer
6820                         *                                   (bytes), or a shorthand string notation, such as '256M'.
6821                         */
6822                        $filtered_limit = apply_filters( 'admin_memory_limit', $filtered_limit );
6823                        break;
6824
6825                case 'image':
6826                        /**
6827                         * Filters the memory limit allocated for image manipulation.
6828                         *
6829                         * @since 3.5.0
6830                         * @since 4.6.0 The default now takes the original `memory_limit` into account.
6831                         *
6832                         * @param int|string $filtered_limit Maximum memory limit to allocate for images.
6833                         *                                   Default `WP_MAX_MEMORY_LIMIT` or the original
6834                         *                                   php.ini `memory_limit`, whichever is higher.
6835                         *                                   Accepts an integer (bytes), or a shorthand string
6836                         *                                   notation, such as '256M'.
6837                         */
6838                        $filtered_limit = apply_filters( 'image_memory_limit', $filtered_limit );
6839                        break;
6840
6841                default:
6842                        /**
6843                         * Filters the memory limit allocated for arbitrary contexts.
6844                         *
6845                         * The dynamic portion of the hook name, `$context`, refers to an arbitrary
6846                         * context passed on calling the function. This allows for plugins to define
6847                         * their own contexts for raising the memory limit.
6848                         *
6849                         * @since 4.6.0
6850                         *
6851                         * @param int|string $filtered_limit Maximum memory limit to allocate for images.
6852                         *                                   Default '256M' or the original php.ini `memory_limit`,
6853                         *                                   whichever is higher. Accepts an integer (bytes), or a
6854                         *                                   shorthand string notation, such as '256M'.
6855                         */
6856                        $filtered_limit = apply_filters( "{$context}_memory_limit", $filtered_limit );
6857                        break;
6858        }
6859
6860        $filtered_limit_int = wp_convert_hr_to_bytes( $filtered_limit );
6861
6862        if ( -1 === $filtered_limit_int || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) {
6863                if ( false !== ini_set( 'memory_limit', $filtered_limit ) ) {
6864                        return $filtered_limit;
6865                } else {
6866                        return false;
6867                }
6868        } elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) {
6869                if ( false !== ini_set( 'memory_limit', $wp_max_limit ) ) {
6870                        return $wp_max_limit;
6871                } else {
6872                        return false;
6873                }
6874        }
6875
6876        return false;
6877}
6878
6879/**
6880 * Generate a random UUID (version 4).
6881 *
6882 * @since 4.7.0
6883 *
6884 * @return string UUID.
6885 */
6886function wp_generate_uuid4() {
6887        return sprintf(
6888                '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
6889                mt_rand( 0, 0xffff ),
6890                mt_rand( 0, 0xffff ),
6891                mt_rand( 0, 0xffff ),
6892                mt_rand( 0, 0x0fff ) | 0x4000,
6893                mt_rand( 0, 0x3fff ) | 0x8000,
6894                mt_rand( 0, 0xffff ),
6895                mt_rand( 0, 0xffff ),
6896                mt_rand( 0, 0xffff )
6897        );
6898}
6899
6900/**
6901 * Validates that a UUID is valid.
6902 *
6903 * @since 4.9.0
6904 *
6905 * @param mixed $uuid    UUID to check.
6906 * @param int   $version Specify which version of UUID to check against. Default is none,
6907 *                       to accept any UUID version. Otherwise, only version allowed is `4`.
6908 * @return bool The string is a valid UUID or false on failure.
6909 */
6910function wp_is_uuid( $uuid, $version = null ) {
6911
6912        if ( ! is_string( $uuid ) ) {
6913                return false;
6914        }
6915
6916        if ( is_numeric( $version ) ) {
6917                if ( 4 !== (int) $version ) {
6918                        _doing_it_wrong( __FUNCTION__, __( 'Only UUID V4 is supported at this time.' ), '4.9.0' );
6919                        return false;
6920                }
6921                $regex = '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/';
6922        } else {
6923                $regex = '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/';
6924        }
6925
6926        return (bool) preg_match( $regex, $uuid );
6927}
6928
6929/**
6930 * Get unique ID.
6931 *
6932 * This is a PHP implementation of Underscore's uniqueId method. A static variable
6933 * contains an integer that is incremented with each call. This number is returned
6934 * with the optional prefix. As such the returned value is not universally unique,
6935 * but it is unique across the life of the PHP process.
6936 *
6937 * @since 5.0.3
6938 *
6939 * @staticvar int $id_counter
6940 *
6941 * @param string $prefix Prefix for the returned ID.
6942 * @return string Unique ID.
6943 */
6944function wp_unique_id( $prefix = '' ) {
6945        static $id_counter = 0;
6946        return $prefix . (string) ++$id_counter;
6947}
6948
6949/**
6950 * Get last changed date for the specified cache group.
6951 *
6952 * @since 4.7.0
6953 *
6954 * @param string $group Where the cache contents are grouped.
6955 *
6956 * @return string $last_changed UNIX timestamp with microseconds representing when the group was last changed.
6957 */
6958function wp_cache_get_last_changed( $group ) {
6959        $last_changed = wp_cache_get( 'last_changed', $group );
6960
6961        if ( ! $last_changed ) {
6962                $last_changed = microtime();
6963                wp_cache_set( 'last_changed', $last_changed, $group );
6964        }
6965
6966        return $last_changed;
6967}
6968
6969/**
6970 * Send an email to the old site admin email address when the site admin email address changes.
6971 *
6972 * @since 4.9.0
6973 *
6974 * @param string $old_email   The old site admin email address.
6975 * @param string $new_email   The new site admin email address.
6976 * @param string $option_name The relevant database option name.
6977 */
6978function wp_site_admin_email_change_notification( $old_email, $new_email, $option_name ) {
6979        $send = true;
6980
6981        // Don't send the notification to the default 'admin_email' value.
6982        if ( 'you@example.com' === $old_email ) {
6983                $send = false;
6984        }
6985
6986        /**
6987         * Filters whether to send the site admin email change notification email.
6988         *
6989         * @since 4.9.0
6990         *
6991         * @param bool   $send      Whether to send the email notification.
6992         * @param string $old_email The old site admin email address.
6993         * @param string $new_email The new site admin email address.
6994         */
6995        $send = apply_filters( 'send_site_admin_email_change_email', $send, $old_email, $new_email );
6996
6997        if ( ! $send ) {
6998                return;
6999        }
7000
7001        /* translators: Do not translate OLD_EMAIL, NEW_EMAIL, SITENAME, SITEURL: those are placeholders. */
7002        $email_change_text = __(
7003                'Hi,
7004
7005This notice confirms that the admin email address was changed on ###SITENAME###.
7006
7007The new admin email address is ###NEW_EMAIL###.
7008
7009This email has been sent to ###OLD_EMAIL###
7010
7011Regards,
7012All at ###SITENAME###
7013###SITEURL###'
7014        );
7015
7016        $email_change_email = array(
7017                'to'      => $old_email,
7018                /* translators: Site admin email change notification email subject. %s: Site title. */
7019                'subject' => __( '[%s] Admin Email Changed' ),
7020                'message' => $email_change_text,
7021                'headers' => '',
7022        );
7023
7024        // Get site name.
7025        $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
7026
7027        /**
7028         * Filters the contents of the email notification sent when the site admin email address is changed.
7029         *
7030         * @since 4.9.0
7031         *
7032         * @param array $email_change_email {
7033         *            Used to build wp_mail().
7034         *
7035         *            @type string $to      The intended recipient.
7036         *            @type string $subject The subject of the email.
7037         *            @type string $message The content of the email.
7038         *                The following strings have a special meaning and will get replaced dynamically:
7039         *                - ###OLD_EMAIL### The old site admin email address.
7040         *                - ###NEW_EMAIL### The new site admin email address.
7041         *                - ###SITENAME###  The name of the site.
7042         *                - ###SITEURL###   The URL to the site.
7043         *            @type string $headers Headers.
7044         *        }
7045         * @param string $old_email The old site admin email address.
7046         * @param string $new_email The new site admin email address.
7047         */
7048        $email_change_email = apply_filters( 'site_admin_email_change_email', $email_change_email, $old_email, $new_email );
7049
7050        $email_change_email['message'] = str_replace( '###OLD_EMAIL###', $old_email, $email_change_email['message'] );
7051        $email_change_email['message'] = str_replace( '###NEW_EMAIL###', $new_email, $email_change_email['message'] );
7052        $email_change_email['message'] = str_replace( '###SITENAME###', $site_name, $email_change_email['message'] );
7053        $email_change_email['message'] = str_replace( '###SITEURL###', home_url(), $email_change_email['message'] );
7054
7055        wp_mail(
7056                $email_change_email['to'],
7057                sprintf(
7058                        $email_change_email['subject'],
7059                        $site_name
7060                ),
7061                $email_change_email['message'],
7062                $email_change_email['headers']
7063        );
7064}
7065
7066/**
7067 * Return an anonymized IPv4 or IPv6 address.
7068 *
7069 * @since 4.9.6 Abstracted from `WP_Community_Events::get_unsafe_client_ip()`.
7070 *
7071 * @param  string $ip_addr        The IPv4 or IPv6 address to be anonymized.
7072 * @param  bool   $ipv6_fallback  Optional. Whether to return the original IPv6 address if the needed functions
7073 *                                to anonymize it are not present. Default false, return `::` (unspecified address).
7074 * @return string  The anonymized IP address.
7075 */
7076function wp_privacy_anonymize_ip( $ip_addr, $ipv6_fallback = false ) {
7077        // Detect what kind of IP address this is.
7078        $ip_prefix = '';
7079        $is_ipv6   = substr_count( $ip_addr, ':' ) > 1;
7080        $is_ipv4   = ( 3 === substr_count( $ip_addr, '.' ) );
7081
7082        if ( $is_ipv6 && $is_ipv4 ) {
7083                // IPv6 compatibility mode, temporarily strip the IPv6 part, and treat it like IPv4.
7084                $ip_prefix = '::ffff:';
7085                $ip_addr   = preg_replace( '/^\[?[0-9a-f:]*:/i', '', $ip_addr );
7086                $ip_addr   = str_replace( ']', '', $ip_addr );
7087                $is_ipv6   = false;
7088        }
7089
7090        if ( $is_ipv6 ) {
7091                // IPv6 addresses will always be enclosed in [] if there's a port.
7092                $left_bracket  = strpos( $ip_addr, '[' );
7093                $right_bracket = strpos( $ip_addr, ']' );
7094                $percent       = strpos( $ip_addr, '%' );
7095                $netmask       = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000';
7096
7097                // Strip the port (and [] from IPv6 addresses), if they exist.
7098                if ( false !== $left_bracket && false !== $right_bracket ) {
7099                        $ip_addr = substr( $ip_addr, $left_bracket + 1, $right_bracket - $left_bracket - 1 );
7100                } elseif ( false !== $left_bracket || false !== $right_bracket ) {
7101                        // The IP has one bracket, but not both, so it's malformed.
7102                        return '::';
7103                }
7104
7105                // Strip the reachability scope.
7106                if ( false !== $percent ) {
7107                        $ip_addr = substr( $ip_addr, 0, $percent );
7108                }
7109
7110                // No invalid characters should be left.
7111                if ( preg_match( '/[^0-9a-f:]/i', $ip_addr ) ) {
7112                        return '::';
7113                }
7114
7115                // Partially anonymize the IP by reducing it to the corresponding network ID.
7116                if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) {
7117                        $ip_addr = inet_ntop( inet_pton( $ip_addr ) & inet_pton( $netmask ) );
7118                        if ( false === $ip_addr ) {
7119                                return '::';
7120                        }
7121                } elseif ( ! $ipv6_fallback ) {
7122                        return '::';
7123                }
7124        } elseif ( $is_ipv4 ) {
7125                // Strip any port and partially anonymize the IP.
7126                $last_octet_position = strrpos( $ip_addr, '.' );
7127                $ip_addr             = substr( $ip_addr, 0, $last_octet_position ) . '.0';
7128        } else {
7129                return '0.0.0.0';
7130        }
7131
7132        // Restore the IPv6 prefix to compatibility mode addresses.
7133        return $ip_prefix . $ip_addr;
7134}
7135
7136/**
7137 * Return uniform "anonymous" data by type.
7138 *
7139 * @since 4.9.6
7140 *
7141 * @param  string $type The type of data to be anonymized.
7142 * @param  string $data Optional The data to be anonymized.
7143 * @return string The anonymous data for the requested type.
7144 */
7145function wp_privacy_anonymize_data( $type, $data = '' ) {
7146
7147        switch ( $type ) {
7148                case 'email':
7149                        $anonymous = 'deleted@site.invalid';
7150                        break;
7151                case 'url':
7152                        $anonymous = 'https://site.invalid';
7153                        break;
7154                case 'ip':
7155                        $anonymous = wp_privacy_anonymize_ip( $data );
7156                        break;
7157                case 'date':
7158                        $anonymous = '0000-00-00 00:00:00';
7159                        break;
7160                case 'text':
7161                        /* translators: Deleted text. */
7162                        $anonymous = __( '[deleted]' );
7163                        break;
7164                case 'longtext':
7165                        /* translators: Deleted long text. */
7166                        $anonymous = __( 'This content was deleted by the author.' );
7167                        break;
7168                default:
7169                        $anonymous = '';
7170                        break;
7171        }
7172
7173        /**
7174         * Filters the anonymous data for each type.
7175         *
7176         * @since 4.9.6
7177         *
7178         * @param string $anonymous Anonymized data.
7179         * @param string $type      Type of the data.
7180         * @param string $data      Original data.
7181         */
7182        return apply_filters( 'wp_privacy_anonymize_data', $anonymous, $type, $data );
7183}
7184
7185/**
7186 * Returns the directory used to store personal data export files.
7187 *
7188 * @since 4.9.6
7189 *
7190 * @see wp_privacy_exports_url
7191 *
7192 * @return string Exports directory.
7193 */
7194function wp_privacy_exports_dir() {
7195        $upload_dir  = wp_upload_dir();
7196        $exports_dir = trailingslashit( $upload_dir['basedir'] ) . 'wp-personal-data-exports/';
7197
7198        /**
7199         * Filters the directory used to store personal data export files.
7200         *
7201         * @since 4.9.6
7202         *
7203         * @param string $exports_dir Exports directory.
7204         */
7205        return apply_filters( 'wp_privacy_exports_dir', $exports_dir );
7206}
7207
7208/**
7209 * Returns the URL of the directory used to store personal data export files.
7210 *
7211 * @since 4.9.6
7212 *
7213 * @see wp_privacy_exports_dir
7214 *
7215 * @return string Exports directory URL.
7216 */
7217function wp_privacy_exports_url() {
7218        $upload_dir  = wp_upload_dir();
7219        $exports_url = trailingslashit( $upload_dir['baseurl'] ) . 'wp-personal-data-exports/';
7220
7221        /**
7222         * Filters the URL of the directory used to store personal data export files.
7223         *
7224         * @since 4.9.6
7225         *
7226         * @param string $exports_url Exports directory URL.
7227         */
7228        return apply_filters( 'wp_privacy_exports_url', $exports_url );
7229}
7230
7231/**
7232 * Schedule a `WP_Cron` job to delete expired export files.
7233 *
7234 * @since 4.9.6
7235 */
7236function wp_schedule_delete_old_privacy_export_files() {
7237        if ( wp_installing() ) {
7238                return;
7239        }
7240
7241        if ( ! wp_next_scheduled( 'wp_privacy_delete_old_export_files' ) ) {
7242                wp_schedule_event( time(), 'hourly', 'wp_privacy_delete_old_export_files' );
7243        }
7244}
7245
7246/**
7247 * Cleans up export files older than three days old.
7248 *
7249 * The export files are stored in `wp-content/uploads`, and are therefore publicly
7250 * accessible. A CSPRN is appended to the filename to mitigate the risk of an
7251 * unauthorized person downloading the file, but it is still possible. Deleting
7252 * the file after the data subject has had a chance to delete it adds an additional
7253 * layer of protection.
7254 *
7255 * @since 4.9.6
7256 */
7257function wp_privacy_delete_old_export_files() {
7258        $exports_dir = wp_privacy_exports_dir();
7259        if ( ! is_dir( $exports_dir ) ) {
7260                return;
7261        }
7262
7263        require_once ABSPATH . 'wp-admin/includes/file.php';
7264        $export_files = list_files( $exports_dir, 100, array( 'index.html' ) );
7265
7266        /**
7267         * Filters the lifetime, in seconds, of a personal data export file.
7268         *
7269         * By default, the lifetime is 3 days. Once the file reaches that age, it will automatically
7270         * be deleted by a cron job.
7271         *
7272         * @since 4.9.6
7273         *
7274         * @param int $expiration The expiration age of the export, in seconds.
7275         */
7276        $expiration = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS );
7277
7278        foreach ( (array) $export_files as $export_file ) {
7279                $file_age_in_seconds = time() - filemtime( $export_file );
7280
7281                if ( $expiration < $file_age_in_seconds ) {
7282                        unlink( $export_file );
7283                }
7284        }
7285}
7286
7287/**
7288 * Gets the URL to learn more about updating the PHP version the site is running on.
7289 *
7290 * This URL can be overridden by specifying an environment variable `WP_UPDATE_PHP_URL` or by using the
7291 * {@see 'wp_update_php_url'} filter. Providing an empty string is not allowed and will result in the
7292 * default URL being used. Furthermore the page the URL links to should preferably be localized in the
7293 * site language.
7294 *
7295 * @since 5.1.0
7296 *
7297 * @return string URL to learn more about updating PHP.
7298 */
7299function wp_get_update_php_url() {
7300        $default_url = wp_get_default_update_php_url();
7301
7302        $update_url = $default_url;
7303        if ( false !== getenv( 'WP_UPDATE_PHP_URL' ) ) {
7304                $update_url = getenv( 'WP_UPDATE_PHP_URL' );
7305        }
7306
7307        /**
7308         * Filters the URL to learn more about updating the PHP version the site is running on.
7309         *
7310         * Providing an empty string is not allowed and will result in the default URL being used. Furthermore
7311         * the page the URL links to should preferably be localized in the site language.
7312         *
7313         * @since 5.1.0
7314         *
7315         * @param string $update_url URL to learn more about updating PHP.
7316         */
7317        $update_url = apply_filters( 'wp_update_php_url', $update_url );
7318
7319        if ( empty( $update_url ) ) {
7320                $update_url = $default_url;
7321        }
7322
7323        return $update_url;
7324}
7325
7326/**
7327 * Gets the default URL to learn more about updating the PHP version the site is running on.
7328 *
7329 * Do not use this function to retrieve this URL. Instead, use {@see wp_get_update_php_url()} when relying on the URL.
7330 * This function does not allow modifying the returned URL, and is only used to compare the actually used URL with the
7331 * default one.
7332 *
7333 * @since 5.1.0
7334 * @access private
7335 *
7336 * @return string Default URL to learn more about updating PHP.
7337 */
7338function wp_get_default_update_php_url() {
7339        return _x( 'https://wordpress.org/support/update-php/', 'localized PHP upgrade information page' );
7340}
7341
7342/**
7343 * Prints the default annotation for the web host altering the "Update PHP" page URL.
7344 *
7345 * This function is to be used after {@see wp_get_update_php_url()} to display a consistent
7346 * annotation if the web host has altered the default "Update PHP" page URL.
7347 *
7348 * @since 5.1.0
7349 * @since 5.2.0 Added the `$before` and `$after` parameters.
7350 *
7351 * @param string $before Markup to output before the annotation. Default `<p class="description">`.
7352 * @param string $after  Markup to output after the annotation. Default `</p>`.
7353 */
7354function wp_update_php_annotation( $before = '<p class="description">', $after = '</p>' ) {
7355        $annotation = wp_get_update_php_annotation();
7356
7357        if ( $annotation ) {
7358                echo $before . $annotation . $after;
7359        }
7360}
7361
7362/**
7363 * Returns the default annotation for the web hosting altering the "Update PHP" page URL.
7364 *
7365 * This function is to be used after {@see wp_get_update_php_url()} to return a consistent
7366 * annotation if the web host has altered the default "Update PHP" page URL.
7367 *
7368 * @since 5.2.0
7369 *
7370 * @return string $message Update PHP page annotation. An empty string if no custom URLs are provided.
7371 */
7372function wp_get_update_php_annotation() {
7373        $update_url  = wp_get_update_php_url();
7374        $default_url = wp_get_default_update_php_url();
7375
7376        if ( $update_url === $default_url ) {
7377                return '';
7378        }
7379
7380        $annotation = sprintf(
7381                /* translators: %s: Default Update PHP page URL. */
7382                __( 'This resource is provided by your web host, and is specific to your site. For more information, <a href="%s" target="_blank">see the official WordPress documentation</a>.' ),
7383                esc_url( $default_url )
7384        );
7385
7386        return $annotation;
7387}
7388
7389/**
7390 * Gets the URL for directly updating the PHP version the site is running on.
7391 *
7392 * A URL will only be returned if the `WP_DIRECT_UPDATE_PHP_URL` environment variable is specified or
7393 * by using the {@see 'wp_direct_php_update_url'} filter. This allows hosts to send users directly to
7394 * the page where they can update PHP to a newer version.
7395 *
7396 * @since 5.1.1
7397 *
7398 * @return string URL for directly updating PHP or empty string.
7399 */
7400function wp_get_direct_php_update_url() {
7401        $direct_update_url = '';
7402
7403        if ( false !== getenv( 'WP_DIRECT_UPDATE_PHP_URL' ) ) {
7404                $direct_update_url = getenv( 'WP_DIRECT_UPDATE_PHP_URL' );
7405        }
7406
7407        /**
7408         * Filters the URL for directly updating the PHP version the site is running on from the host.
7409         *
7410         * @since 5.1.1
7411         *
7412         * @param string $direct_update_url URL for directly updating PHP.
7413         */
7414        $direct_update_url = apply_filters( 'wp_direct_php_update_url', $direct_update_url );
7415
7416        return $direct_update_url;
7417}
7418
7419/**
7420 * Display a button directly linking to a PHP update process.
7421 *
7422 * This provides hosts with a way for users to be sent directly to their PHP update process.
7423 *
7424 * The button is only displayed if a URL is returned by `wp_get_direct_php_update_url()`.
7425 *
7426 * @since 5.1.1
7427 */
7428function wp_direct_php_update_button() {
7429        $direct_update_url = wp_get_direct_php_update_url();
7430
7431        if ( empty( $direct_update_url ) ) {
7432                return;
7433        }
7434
7435        echo '<p class="button-container">';
7436        printf(
7437                '<a class="button button-primary" href="%1$s" target="_blank" rel="noopener noreferrer">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
7438                esc_url( $direct_update_url ),
7439                __( 'Update PHP' ),
7440                /* translators: Accessibility text. */
7441                __( '(opens in a new tab)' )
7442        );
7443        echo '</p>';
7444}
7445
7446/**
7447 * Get the size of a directory.
7448 *
7449 * A helper function that is used primarily to check whether
7450 * a blog has exceeded its allowed upload space.
7451 *
7452 * @since MU (3.0.0)
7453 * @since 5.2.0 $max_execution_time parameter added.
7454 *
7455 * @param string $directory Full path of a directory.
7456 * @param int    $max_execution_time Maximum time to run before giving up. In seconds.
7457 *                                   The timeout is global and is measured from the moment WordPress started to load.
7458 * @return int|false|null Size in bytes if a valid directory. False if not. Null if timeout.
7459 */
7460function get_dirsize( $directory, $max_execution_time = null ) {
7461        $dirsize = get_transient( 'dirsize_cache' );
7462
7463        if ( is_array( $dirsize ) && isset( $dirsize[ $directory ]['size'] ) ) {
7464                return $dirsize[ $directory ]['size'];
7465        }
7466
7467        if ( ! is_array( $dirsize ) ) {
7468                $dirsize = array();
7469        }
7470
7471        // Exclude individual site directories from the total when checking the main site of a network,
7472        // as they are subdirectories and should not be counted.
7473        if ( is_multisite() && is_main_site() ) {
7474                $dirsize[ $directory ]['size'] = recurse_dirsize( $directory, $directory . '/sites', $max_execution_time );
7475        } else {
7476                $dirsize[ $directory ]['size'] = recurse_dirsize( $directory, null, $max_execution_time );
7477        }
7478
7479        set_transient( 'dirsize_cache', $dirsize, HOUR_IN_SECONDS );
7480        return $dirsize[ $directory ]['size'];
7481}
7482
7483/**
7484 * Get the size of a directory recursively.
7485 *
7486 * Used by get_dirsize() to get a directory's size when it contains
7487 * other directories.
7488 *
7489 * @since MU (3.0.0)
7490 * @since 4.3.0 $exclude parameter added.
7491 * @since 5.2.0 $max_execution_time parameter added.
7492 *
7493 * @param string $directory       Full path of a directory.
7494 * @param string|array $exclude   Optional. Full path of a subdirectory to exclude from the total, or array of paths.
7495 *                                Expected without trailing slash(es).
7496 * @param int $max_execution_time Maximum time to run before giving up. In seconds.
7497 *                                The timeout is global and is measured from the moment WordPress started to load.
7498 * @return int|false|null Size in bytes if a valid directory. False if not. Null if timeout.
7499 */
7500function recurse_dirsize( $directory, $exclude = null, $max_execution_time = null ) {
7501        $size = 0;
7502
7503        $directory = untrailingslashit( $directory );
7504
7505        if ( ! file_exists( $directory ) || ! is_dir( $directory ) || ! is_readable( $directory ) ) {
7506                return false;
7507        }
7508
7509        if (
7510                ( is_string( $exclude ) && $directory === $exclude ) ||
7511                ( is_array( $exclude ) && in_array( $directory, $exclude, true ) )
7512        ) {
7513                return false;
7514        }
7515
7516        if ( null === $max_execution_time ) {
7517                // Keep the previous behavior but attempt to prevent fatal errors from timeout if possible.
7518                if ( function_exists( 'ini_get' ) ) {
7519                        $max_execution_time = ini_get( 'max_execution_time' );
7520                } else {
7521                        // Disable...
7522                        $max_execution_time = 0;
7523                }
7524
7525                // Leave 1 second "buffer" for other operations if $max_execution_time has reasonable value.
7526                if ( $max_execution_time > 10 ) {
7527                        $max_execution_time -= 1;
7528                }
7529        }
7530
7531        $handle = opendir( $directory );
7532        if ( $handle ) {
7533                while ( ( $file = readdir( $handle ) ) !== false ) {
7534                        $path = $directory . '/' . $file;
7535                        if ( '.' !== $file && '..' !== $file ) {
7536                                if ( is_file( $path ) ) {
7537                                        $size += filesize( $path );
7538                                } elseif ( is_dir( $path ) ) {
7539                                        $handlesize = recurse_dirsize( $path, $exclude, $max_execution_time );
7540                                        if ( $handlesize > 0 ) {
7541                                                $size += $handlesize;
7542                                        }
7543                                }
7544
7545                                if ( $max_execution_time > 0 && microtime( true ) - WP_START_TIMESTAMP > $max_execution_time ) {
7546                                        // Time exceeded. Give up instead of risking a fatal timeout.
7547                                        $size = null;
7548                                        break;
7549                                }
7550                        }
7551                }
7552                closedir( $handle );
7553        }
7554        return $size;
7555}
7556
7557/**
7558 * Checks compatibility with the current WordPress version.
7559 *
7560 * @since 5.2.0
7561 *
7562 * @param string $required Minimum required WordPress version.
7563 * @return bool True if required version is compatible or empty, false if not.
7564 */
7565function is_wp_version_compatible( $required ) {
7566        return empty( $required ) || version_compare( get_bloginfo( 'version' ), $required, '>=' );
7567}
7568
7569/**
7570 * Checks compatibility with the current PHP version.
7571 *
7572 * @since 5.2.0
7573 *
7574 * @param string $required Minimum required PHP version.
7575 * @return bool True if required version is compatible or empty, false if not.
7576 */
7577function is_php_version_compatible( $required ) {
7578        return empty( $required ) || version_compare( phpversion(), $required, '>=' );
7579}
7580
7581/**
7582 * Check if two numbers are nearly the same.
7583 *
7584 * This is similar to using `round()` but the precision is more fine-grained.
7585 *
7586 * @since 5.3.0
7587 *
7588 * @param int|float $expected  The expected value.
7589 * @param int|float $actual    The actual number.
7590 * @param int|float $precision The allowed variation.
7591 * @return bool Whether the numbers match whithin the specified precision.
7592 */
7593function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) {
7594        return abs( (float) $expected - (float) $actual ) <= $precision;
7595}
Note: See TracBrowser for help on using the repository browser.