ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

functions.php (253122B)


      1 <?php
      2 /**
      3  * Main WordPress API
      4  *
      5  * @package WordPress
      6  */
      7 
      8 require 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  */
     30 function 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  */
     71 function 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  */
     94 function 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  */
    108 function 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  */
    136 function 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  */
    165 function 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  */
    227 function 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  */
    322 function 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  */
    408 function 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  */
    450 function 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  */
    487 function 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  */
    557 function 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  */
    597 function 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  */
    622 function 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  */
    642 function 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  */
    708 function 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  */
    742 function 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  */
    766 function 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  */
    785 function 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  */
    800 function 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  */
    842 function 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  */
    943 function 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  */
    972 function 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  */
    997 function 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  */
   1018 function _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  */
   1090 function 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  */
   1166 function 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  */
   1183 function 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  */
   1234 function 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  */
   1258 function 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  */
   1288 function 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  */
   1311 function 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  */
   1403 function 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  */
   1444 function 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  */
   1482 function 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  */
   1503 function 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  */
   1520 function 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  */
   1535 function 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  */
   1551 function 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  */
   1595 function 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  */
   1606 function 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  */
   1619 function 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  */
   1636 function 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  */
   1652 function 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  */
   1686 function 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  */
   1717 function 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  */
   1808 function 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  */
   1839 function 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  */
   1865 function 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  */
   1889 function 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  */
   1914 function 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  */
   1937 function 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  */
   1954 function 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  */
   1972 function 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  */
   2049 function 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  */
   2091 function 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  */
   2115 function 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  */
   2152 function 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  */
   2195 function 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  */
   2219 function 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  */
   2257 function 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  */
   2298 function 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  */
   2361 function _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  */
   2483 function 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  */
   2704 function _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  */
   2728 function _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  */
   2782 function 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  */
   2890 function 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  */
   2911 function 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  */
   2937 function 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  */
   2980 function 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  */
   3180 function 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  */
   3254 function 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  */
   3383 function 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  */
   3419 function 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  */
   3452 function 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  */
   3529 function 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  */
   3615 function _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  */
   3817 function _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  */
   3859 function _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  */
   3897 function _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  */
   3941 function _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  */
   3971 function _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 
   3987 EOD;
   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  */
   4016 function _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  */
   4048 function _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  */
   4132 function 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  */
   4164 function _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  */
   4226 function _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  */
   4258 function _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  */
   4275 function 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  */
   4322 function 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  */
   4349 function 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  */
   4385 function 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  */
   4410 function _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  */
   4432 function _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  */
   4445 function _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  */
   4466 function _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  */
   4506 function 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  */
   4629 function 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  */
   4652 function 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  */
   4669 function 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  */
   4684 function 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  */
   4699 function 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  */
   4739 function _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  */
   4788 function _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  */
   4853 function _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  */
   4913 function 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  */
   4952 function 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  */
   4993 function 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  */
   5021 function 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  */
   5040 function 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  */
   5058 function 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  */
   5086 function 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  */
   5104 function 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  */
   5129 function 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  */
   5157 function 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  */
   5180 function _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  */
   5269 function _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  */
   5367 function _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  */
   5464 function _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  */
   5553 function _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  */
   5618 function _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  */
   5695 function 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  */
   5713 function 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  */
   5746 function 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  */
   5788 function 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  */
   5825 function 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  */
   5847 function 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  */
   5901 function 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  */
   5925 function 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  */
   5945 function 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  */
   5968 function 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  */
   5989 function 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  */
   6010 function 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  */
   6049 function 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  */
   6090 function 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  */
   6118 function 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  */
   6142 function _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  */
   6190 function 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  */
   6383 function _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  */
   6397 function 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  */
   6459 function 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  */
   6516 function __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  */
   6531 function __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  */
   6544 function __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  */
   6557 function __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  */
   6570 function __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  */
   6585 function __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  */
   6597 function 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  */
   6610 function _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  */
   6640 function 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  */
   6671 function 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  */
   6711 function 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  */
   6733 function 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  */
   6771 function 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  */
   6823 function _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  */
   6844 function _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  */
   6868 function 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  */
   6894 function 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  */
   6917 function 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  */
   6955 function 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  */
   7014 function 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  */
   7035 function 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  */
   7054 function _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  */
   7091 function 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  */
   7128 function 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  */
   7142 function 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  */
   7161 function 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  */
   7184 function 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  */
   7219 function 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  */
   7260 function 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  */
   7277 function 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  */
   7378 function 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  */
   7402 function 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  */
   7434 function 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  */
   7447 function 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  */
   7467 function 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 
   7494 This notice confirms that the admin email address was changed on ###SITENAME###.
   7495 
   7496 The new admin email address is ###NEW_EMAIL###.
   7497 
   7498 This email has been sent to ###OLD_EMAIL###
   7499 
   7500 Regards,
   7501 All 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  */
   7565 function 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  */
   7634 function 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  */
   7683 function 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  */
   7708 function 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  */
   7729 function 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  */
   7750 function 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  */
   7792 function 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  */
   7831 function 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  */
   7847 function 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  */
   7865 function 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  */
   7893 function 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  */
   7921 function 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  */
   7951 function 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  */
   7989 function 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  */
   8005 function 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  */
   8038 function 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  */
   8070 function 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  */
   8168 function 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  */
   8194 function 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  */
   8206 function 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  */
   8222 function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) {
   8223 	return abs( (float) $expected - (float) $actual ) <= $precision;
   8224 }