Make WordPress Core

Ticket #62845: functions.php

File functions.php, 276.8 KB (added by madum, 13 months ago)

file corrected

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