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