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