f;', '📰', '📱', '📲', '📳', '📴', '📵', '📶', '📷', '📸', '📹', '📺', '📻', '📼', '📽', '📿', '🔀', '🔁', '🔂', '🔃', '🔄', '🔅', '🔆', '🔇', '🔈', '🔉', '🔊', '🔋', '🔌', '🔍', '🔎', '🔏', '🔐', '🔑', '🔒', '🔓', '🔔', '🔕', '🔖', '🔗', '🔘', '🔙', '🔚', '🔛', '🔜', '🔝', '🔞', '🔟', '🔠', '🔡', '🔢', '🔣', '🔤', '🔦', '🔨', '🔩', '🔪', '🔫', '🔭', '🔮', '🔯', '🔰', '🔱', '🔲', '🔳', '🔴', '🔵', '🔶', '🔷', '🔸', '🔹', '🔺', '🔻', '🔼', '🔽', '🕉', '🕊', '🕋', '🕌', '🕍', '🕎', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚', '🕛', '🕜', '🕝', '🕞', '🕟', '🕠', '🕡', '🕢', '🕣', '🕤', '🕥', '🕦', '🕧', '🕯', '🕰', '🕳', '🕴', '🕵', '🕶', '🕷', '🕸', '🕹', '🕺', '🖇', '🖊', '🖋', '🖌', '🖍', '🖐', '🖕', '🖖', '🖤', '🖥', '🖨', '🖱', '🖲', '🖼', '🗂', '🗃', '🗄', '🗑', '🗒', '🗓', '🗜', '🗝', '🗞', '🗡', '🗣', '🗯', '🗳', '🗺', '🗻', '🗼', '🗽', '🗾', '🗿', '😀', '😁', '😂', '😃', '😄', '😅', '😆', '😇', '😈', '😉', '😊', '😋', '😌', '😍', '😎', '😏', '😐', '😑', '😒', '😓', '😔', '😕', '😖', '😗', '😘', '😙', '😚', '😛', '😜', '😝', '😞', '😟', '😠', '😡', '😢', '😣', '😤', '😥', '😦', '😧', '😨', '😩', '😪', '😫', '😬', '😭', '😮', '😯', '😰', '😱', '😲', '😳', '😴', '😵', '😶', '😷', '😸', '😹', '😺', '😻', '😼', '😽', '😾', '😿', '🙀', '🙁', '🙂', '↔', '↕', '🙃', '🙄', '🙅', '🙆', '🙇', '🙈', '🙉', '🙊', '🙋', '🙌', '🙍', '🙎', '🙏', '🚁', '🚂', '🚃', '🚄', '🚅', '🚆', '🚇', '🚈', '🚉', '🚊', '🚋', '🚌', '🚍', '🚎', '🚏', '🚐', '🚑', '🚓', '🚔', '🚕', '🚖', '🚗', '🚘', '🚙', '🚚', '🚛', '🚜', '🚝', '🚞', '🚟', '🚠', '🚡', '🚢', '🚣', '🚤', '🚥', '🚦', '🚧', '🚨', '🚩', '🚪', '🚫', '🚬', '🚭', '🚮', '🚯', '🚰', '🚱', '🚲', '🚳', '🚴', '🚵', '🚶', '🚷', '🚸', '🚹', '🚺', '🚻', '🚼', '🚽', '🚾', '🚿', '🛀', '🛁', '🛂', '🛃', '🛄', '🛅', '🛋', '🛌', '🛍', '🛎', '🛏', '🛐', '🛑', '🛒', '🛕', '🛖', '🛗', '', '🛜', '🛝', '🛞', '🛟', '🛠', '🛡', '🛢', '🛣', '🛤', '🛥', '🛩', '🛫', '🛬', '🛰', '🛳', '🛴', '🛵', '🛶', '🛷', '🛸', '🛹', '🛺', '🛻', '🛼', '🟠', '🟡', '🟢', '🟣', '🟤', '🟥', '🟦', '🟧', '🟨', '🟪', '🟰', '🤌', '🤍', '🤎', '🤏', '🤐', '🤑', '🤒', '🤓', '🤔', '🤕', '🤖', '🤗', '🤘', '🤙', '🤚', '🤛', '🤜', '🤞', '🤟', '🤠', '🤡', '🤢', '🤣', '🤤', '🤥', '🤦', '🤧', '🤨', '🤩', '🤪', '🤫', '🤬', '🤭', '🤮', '🤯', '🤰', '🤱', '🤲', '🤳', '🤴', '🤵', '🤶', '🤷', '🤸', '🤹', '🤺', '🤼', '🤽', '🤾', '🤿', '🥀', '🥁', '🥂', '🥃', '🥄', '🥅', '🥇', '🥈', '🥉', '🥊', '🥋', '🥌', '🥍', '🥎', '🥏', '🥐', '🥑', '🥒', '🥓', '🥔', '🥕', '🥖', '🥗', '🥘', '🥙', '🥚', '🥛', '🥜', '🥝', '🥞', '🥟', '🥠', '🥡', '🥢', '🥣', '🥤', '🥥', '🥦', '🥧', '🥨', '🥩', '🥪', '🥫', '🥬', '🥭', '🥮', '🥯', '🥰', '🥱', '🥲', '🥳', '🥴', '🥵', '🥶', '🥷', '🥸', '🥹', '🥺', '🥻', '🥼', '🥽', '🥾', '🥿', '🦀', '🦁', '🦂', '🦃', '🦄', '🦅', '🦆', '🦇', '🦈', '🦉', '🦊', '🦋', '🦌', '🦍', '🦎', '🦏', '🦐', '🦑', '🦒', '🦓', '🦔', '🦕', '🦖', '🦗', '🦘', '🦙', '🦚', '🦛', '🦜', '🦝', '🦞', '🦟', '🦠', '🦡', '🦢', '🦣', '🦤', '🦥', '🦦', '🦧', '🦨', '🦩', '🦪', '🦫', '🦬', '🦭', '🦮', '🦴', '🦵', '🦶', '🦷', '🦸', '🦹', '🦻', '🦾', '🦿', '🧀', '🧁', '🧂', '🧃', '🧄', '🧅', '🧆', '🧇', '🧈', '🧉', '🧊', '🧋', '🧌', '🧍', '🧎', '🧏', '🧐', '🧑', '🩰', '🧒', '🧓', '🧔', '🧕', '🧖', '🧗', '🧘', '🧙', '🧚', '🧛', '🧜', '🧝', '🧞', '🧟', '🧠', '🧡', '🧢', '🧣', '🧤', '🧥', '🧦', '🧧', '🧨', '🧩', '🧪', '🧫', '🧬', '🧭', '🧮', '🧯', '🧰', '🧱', '🧲', '🧳', '🧴', '🧵', '🧶', '🧷', '🧸', '🧹', '🧺', '🧻', '🧼', '🧽', '🧾', '🧿', '🩱', '🩲', '🩳', '🩴', '🩵', '🩶', '🩷', '🩸', '🩹', '🩺', '🩻', '🩼', '🪀', '🪁', '🪂', '🪃', '🪄', '🪅', '🪆', '🪇', '🪈', '', '', '', '', '🪐', '🪑', '🪒', '🪓', '🪔', '🪕', '🪖', '🪗', '🪘', '🪙', '🪚', '🪛', '🪜', '🪝', '🪞', '🪟', '🪠', '🪡', '🪢', '🪣', '🪤', '🪥', '🪦', '🪧', '🪨', '🪩', '🪪', '🪫', '🪬', '🪭', '🪮', '🪯', '🪰', '🪱', '🪲', '🪳', '🪴', '🪵', '🪶', '🪷', '🪸', '🪹', '🪺', '🪻', '🪼', '🪽', '', '🪿', '🫀', '🫁', '🫂', '🫃', '🫄', '🫅', '', '', '', '🫎', '🫏', '🫐', '🫑', '🫒', '🫓', '🫔', '🫕', '🫖', '🫗', '🫘', '🫙', '🫚', '🫛', '', '', '🫠', '🫡', '🫢', '🫣', '🫤', '🫥', '🫦', '🫧', '🫨', '', '', '🫰', '🫱', '🫲', '🫳', '🫴', '🫵', '🫶', '🫷', '🫸', '‼', '⁉', '™', 'ℹ', '↖', '↗', '↘', '↙', '↩', '↪', '⃣', '⌚', '⌛', '⌨', '⏏', '⏩', '⏪', '⏫', '⏬', '⏭', '⏮', '⏯', '⏰', '⏱', '⏲', '⏳', '⏸', '⏹', '⏺', 'Ⓜ', '▪', '▫', '▶', '◀', '◻', '◼', '◽', '◾', '☀', '☁', '☂', '☃', '☄', '☎', '☑', '☔', '☕', '☘', '☝', '☢', '☣', '☦', '☪', '☮', '☯', '☸', '☹', '☺', '♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓', '♟', '♠', '♣', '♥', '♦', '♨', '♻', '♾', '♿', '⚒', '⚓', '⚔', '⚗', '⚙', '⚛', '⚜', '⚠', '⚡', '⚪', '⚫', '⚰', '⚱', '⚽', '⚾', '⛄', '⛅', '⛈', '⛎', '⛏', '⛑', '⛓', '⛔', '⛩', '⛪', '⛰', '⛱', '⛲', '⛳', '⛴', '⛵', '⛷', '⛸', '⛹', '⛺', '⛽', '✂', '✅', '✉', '✊', '✋', '✌', '✍', '✏', '✒', '✔', '✖', '✝', '✡', '✨', '✳', '✴', '❇', '❌', '❎', '❓', '❔', '❕', '❗', '❣', '➕', '➖', '➗', '➰', '➿', '⤴', '⤵', '⬅', '⬆', '⬇', '⬜', '⭐', '⭕', '〰', '〽', '㊗', '㊙', '' ); // END: emoji arrays if ( 'entities' === $type ) { return $entities; } return $partials; } /** * Shortens a URL, to be used as link text. * * @since 1.2.0 * @since 4.4.0 Moved to wp-includes/formatting.php from wp-admin/includes/misc.php and added $length param. * * @param string $url URL to shorten. * @param int $length Optional. Maximum length of the shortened URL. Default 35 characters. * @return string Shortened URL. */ function url_shorten( $url, $length = 35 ) { $stripped = str_replace( array( 'https://', 'http://', 'www.' ), '', $url ); $short_url = untrailingslashit( $stripped ); if ( strlen( $short_url ) > $length ) { $short_url = substr( $short_url, 0, $length - 3 ) . '…'; } return $short_url; } /** * Sanitizes a hex color. * * Returns either '', a 3 or 6 digit hex color (with #), or nothing. * For sanitizing values without a #, see sanitize_hex_color_no_hash(). * * @since 3.4.0 * * @param string $color * @return string|null The sanitized hex color, or null if invalid. */ function sanitize_hex_color( $color ) { if ( '' === $color ) { return ''; } // 3 or 6 hex digits, or the empty string. if ( preg_match( '|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) { return $color; } return null; } /** * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible. * * Saving hex colors without a hash puts the burden of adding the hash on the * UI, which makes it difficult to use or upgrade to other color types such as * rgba, hsl, rgb, and HTML color names. * * Returns either '', a 3 or 6 digit hex color (without a #), or null. * * @since 3.4.0 * * @param string $color The color value to sanitize. Can be with or without a #. * @return string|null The sanitized hex color without the hash prefix, * empty string if input is empty, or null if invalid. */ function sanitize_hex_color_no_hash( $color ) { $color = ltrim( $color, '#' ); if ( '' === $color ) { return ''; } return sanitize_hex_color( '#' . $color ) ? $color : null; } /** * Ensures that any hex color is properly hashed. * Otherwise, returns value untouched. * * This method should only be necessary if using sanitize_hex_color_no_hash(). * * @since 3.4.0 * * @param string $color The color value to add the hash prefix to. Can be with or without a #. * @return string The color with the hash prefix if it's a valid hex color, * otherwise the original value. */ function maybe_hash_hex_color( $color ) { $unhashed = sanitize_hex_color_no_hash( $color ); if ( $unhashed ) { return '#' . $unhashed; } return $color; } 4000, $randomizer( 0, 0x3fff ) | 0x8000, $randomizer( 0, 0xffff ), $randomizer( 0, 0xffff ), $randomizer( 0, 0xffff ) ); } /** * Validates that a UUID is valid. * * @since 4.9.0 * * @param mixed $uuid UUID to check. * @param int $version Specify which version of UUID to check against. Default is none, * to accept any UUID version. Otherwise, only version allowed is `4`. * @return bool The string is a valid UUID or false on failure. */ function wp_is_uuid( $uuid, $version = null ) { if ( ! is_string( $uuid ) ) { return false; } if ( is_numeric( $version ) ) { if ( 4 !== (int) $version ) { _doing_it_wrong( __FUNCTION__, __( 'Only UUID V4 is supported at this time.' ), '4.9.0' ); return false; } $regex = '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/'; } else { $regex = '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/'; } return (bool) preg_match( $regex, $uuid ); } /** * Gets unique ID. * * This is a PHP implementation of Underscore's uniqueId method. A static variable * contains an integer that is incremented with each call. This number is returned * with the optional prefix. As such the returned value is not universally unique, * but it is unique across the life of the PHP process. * * @since 5.0.3 * * @param string $prefix Prefix for the returned ID. * @return string Unique ID. */ function wp_unique_id( $prefix = '' ) { static $id_counter = 0; return $prefix . (string) ++$id_counter; } /** * Generates an incremental ID that is independent per each different prefix. * * It is similar to `wp_unique_id`, but each prefix has its own internal ID * counter to make each prefix independent from each other. The ID starts at 1 * and increments on each call. The returned value is not universally unique, * but it is unique across the life of the PHP process and it's stable per * prefix. * * @since 6.4.0 * * @param string $prefix Optional. Prefix for the returned ID. Default empty string. * @return string Incremental ID per prefix. */ function wp_unique_prefixed_id( $prefix = '' ) { static $id_counters = array(); if ( ! is_string( $prefix ) ) { wp_trigger_error( __FUNCTION__, sprintf( 'The prefix must be a string. "%s" data type given.', gettype( $prefix ) ) ); $prefix = ''; } if ( ! isset( $id_counters[ $prefix ] ) ) { $id_counters[ $prefix ] = 0; } $id = ++$id_counters[ $prefix ]; return $prefix . (string) $id; } /** * Generates a unique ID based on the structure and values of a given array. * * This function serializes the array into a JSON string and generates a hash * that serves as a unique identifier. Optionally, a prefix can be added to * the generated ID for context or categorization. * * @since 6.8.0 * * @param array $data The input array to generate an ID from. * @param string $prefix Optional. A prefix to prepend to the generated ID. Default empty string. * @return string The generated unique ID for the array. */ function wp_unique_id_from_values( array $data, string $prefix = '' ): string { if ( empty( $data ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: %s: The parameter name. */ __( 'The %s parameter must not be empty.' ), '$data' ), '6.8.0' ); } $serialized = wp_json_encode( $data ); $hash = substr( md5( $serialized ), 0, 8 ); return $prefix . $hash; } /** * Gets last changed date for the specified cache group. * * @since 4.7.0 * * @param string $group Where the cache contents are grouped. * @return string UNIX timestamp with microseconds representing when the group was last changed. */ function wp_cache_get_last_changed( $group ) { $last_changed = wp_cache_get( 'last_changed', $group ); if ( $last_changed ) { return $last_changed; } return wp_cache_set_last_changed( $group ); } /** * Sets last changed date for the specified cache group to now. * * @since 6.3.0 * * @param string $group Where the cache contents are grouped. * @return string UNIX timestamp when the group was last changed. */ function wp_cache_set_last_changed( $group ) { $previous_time = wp_cache_get( 'last_changed', $group ); $time = microtime(); wp_cache_set( 'last_changed', $time, $group ); /** * Fires after a cache group `last_changed` time is updated. * This may occur multiple times per page load and registered * actions must be performant. * * @since 6.3.0 * * @param string $group The cache group name. * @param string $time The new last changed time (msec sec). * @param string|false $previous_time The previous last changed time. False if not previously set. */ do_action( 'wp_cache_set_last_changed', $group, $time, $previous_time ); return $time; } /** * Sends an email to the old site admin email address when the site admin email address changes. * * @since 4.9.0 * * @param string $old_email The old site admin email address. * @param string $new_email The new site admin email address. * @param string $option_name The relevant database option name. */ function wp_site_admin_email_change_notification( $old_email, $new_email, $option_name ) { $send = true; // Don't send the notification for an empty email address or the default 'admin_email' value. if ( empty( $old_email ) || 'you@example.com' === $old_email ) { $send = false; } /** * Filters whether to send the site admin email change notification email. * * @since 4.9.0 * * @param bool $send Whether to send the email notification. * @param string $old_email The old site admin email address. * @param string $new_email The new site admin email address. */ $send = apply_filters( 'send_site_admin_email_change_email', $send, $old_email, $new_email ); if ( ! $send ) { return; } /* translators: Do not translate OLD_EMAIL, NEW_EMAIL, SITENAME, SITEURL: those are placeholders. */ $email_change_text = __( 'Hi, This notice confirms that the admin email address was changed on ###SITENAME###. The new admin email address is ###NEW_EMAIL###. This email has been sent to ###OLD_EMAIL### Regards, All at ###SITENAME### ###SITEURL###' ); $email_change_email = array( 'to' => $old_email, /* translators: Site admin email change notification email subject. %s: Site title. */ 'subject' => __( '[%s] Admin Email Changed' ), 'message' => $email_change_text, 'headers' => '', ); // Get site name. $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); /** * Filters the contents of the email notification sent when the site admin email address is changed. * * @since 4.9.0 * * @param array $email_change_email { * Used to build wp_mail(). * * @type string $to The intended recipient. * @type string $subject The subject of the email. * @type string $message The content of the email. * The following strings have a special meaning and will get replaced dynamically: * - `###OLD_EMAIL###` The old site admin email address. * - `###NEW_EMAIL###` The new site admin email address. * - `###SITENAME###` The name of the site. * - `###SITEURL###` The URL to the site. * @type string $headers Headers. * } * @param string $old_email The old site admin email address. * @param string $new_email The new site admin email address. */ $email_change_email = apply_filters( 'site_admin_email_change_email', $email_change_email, $old_email, $new_email ); $email_change_email['message'] = str_replace( '###OLD_EMAIL###', $old_email, $email_change_email['message'] ); $email_change_email['message'] = str_replace( '###NEW_EMAIL###', $new_email, $email_change_email['message'] ); $email_change_email['message'] = str_replace( '###SITENAME###', $site_name, $email_change_email['message'] ); $email_change_email['message'] = str_replace( '###SITEURL###', home_url(), $email_change_email['message'] ); wp_mail( $email_change_email['to'], sprintf( $email_change_email['subject'], $site_name ), $email_change_email['message'], $email_change_email['headers'] ); } /** * Returns an anonymized IPv4 or IPv6 address. * * @since 4.9.6 Abstracted from `WP_Community_Events::get_unsafe_client_ip()`. * * @param string $ip_addr The IPv4 or IPv6 address to be anonymized. * @param bool $ipv6_fallback Optional. Whether to return the original IPv6 address if the needed functions * to anonymize it are not present. Default false, return `::` (unspecified address). * @return string The anonymized IP address. */ function wp_privacy_anonymize_ip( $ip_addr, $ipv6_fallback = false ) { if ( empty( $ip_addr ) ) { return '0.0.0.0'; } // Detect what kind of IP address this is. $ip_prefix = ''; $is_ipv6 = substr_count( $ip_addr, ':' ) > 1; $is_ipv4 = ( 3 === substr_count( $ip_addr, '.' ) ); if ( $is_ipv6 && $is_ipv4 ) { // IPv6 compatibility mode, temporarily strip the IPv6 part, and treat it like IPv4. $ip_prefix = '::ffff:'; $ip_addr = preg_replace( '/^\[?[0-9a-f:]*:/i', '', $ip_addr ); $ip_addr = str_replace( ']', '', $ip_addr ); $is_ipv6 = false; } if ( $is_ipv6 ) { // IPv6 addresses will always be enclosed in [] if there's a port. $left_bracket = strpos( $ip_addr, '[' ); $right_bracket = strpos( $ip_addr, ']' ); $percent = strpos( $ip_addr, '%' ); $netmask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; // Strip the port (and [] from IPv6 addresses), if they exist. if ( false !== $left_bracket && false !== $right_bracket ) { $ip_addr = substr( $ip_addr, $left_bracket + 1, $right_bracket - $left_bracket - 1 ); } elseif ( false !== $left_bracket || false !== $right_bracket ) { // The IP has one bracket, but not both, so it's malformed. return '::'; } // Strip the reachability scope. if ( false !== $percent ) { $ip_addr = substr( $ip_addr, 0, $percent ); } // No invalid characters should be left. if ( preg_match( '/[^0-9a-f:]/i', $ip_addr ) ) { return '::'; } // Partially anonymize the IP by reducing it to the corresponding network ID. if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) { $ip_addr = inet_ntop( inet_pton( $ip_addr ) & inet_pton( $netmask ) ); if ( false === $ip_addr ) { return '::'; } } elseif ( ! $ipv6_fallback ) { return '::'; } } elseif ( $is_ipv4 ) { // Strip any port and partially anonymize the IP. $last_octet_position = strrpos( $ip_addr, '.' ); $ip_addr = substr( $ip_addr, 0, $last_octet_position ) . '.0'; } else { return '0.0.0.0'; } // Restore the IPv6 prefix to compatibility mode addresses. return $ip_prefix . $ip_addr; } /** * Returns uniform "anonymous" data by type. * * @since 4.9.6 * * @param string $type The type of data to be anonymized. * @param string $data Optional. The data to be anonymized. Default empty string. * @return string The anonymous data for the requested type. */ function wp_privacy_anonymize_data( $type, $data = '' ) { switch ( $type ) { case 'email': $anonymous = 'deleted@site.invalid'; break; case 'url': $anonymous = 'https://site.invalid'; break; case 'ip': $anonymous = wp_privacy_anonymize_ip( $data ); break; case 'date': $anonymous = '0000-00-00 00:00:00'; break; case 'text': /* translators: Deleted text. */ $anonymous = __( '[deleted]' ); break; case 'longtext': /* translators: Deleted long text. */ $anonymous = __( 'This content was deleted by the author.' ); break; default: $anonymous = ''; break; } /** * Filters the anonymous data for each type. * * @since 4.9.6 * * @param string $anonymous Anonymized data. * @param string $type Type of the data. * @param string $data Original data. */ return apply_filters( 'wp_privacy_anonymize_data', $anonymous, $type, $data ); } /** * Returns the directory used to store personal data export files. * * @since 4.9.6 * * @see wp_privacy_exports_url * * @return string Exports directory. */ function wp_privacy_exports_dir() { $upload_dir = wp_upload_dir(); $exports_dir = trailingslashit( $upload_dir['basedir'] ) . 'wp-personal-data-exports/'; /** * Filters the directory used to store personal data export files. * * @since 4.9.6 * @since 5.5.0 Exports now use relative paths, so changes to the directory * via this filter should be reflected on the server. * * @param string $exports_dir Exports directory. */ return apply_filters( 'wp_privacy_exports_dir', $exports_dir ); } /** * Returns the URL of the directory used to store personal data export files. * * @since 4.9.6 * * @see wp_privacy_exports_dir * * @return string Exports directory URL. */ function wp_privacy_exports_url() { $upload_dir = wp_upload_dir(); $exports_url = trailingslashit( $upload_dir['baseurl'] ) . 'wp-personal-data-exports/'; /** * Filters the URL of the directory used to store personal data export files. * * @since 4.9.6 * @since 5.5.0 Exports now use relative paths, so changes to the directory URL * via this filter should be reflected on the server. * * @param string $exports_url Exports directory URL. */ return apply_filters( 'wp_privacy_exports_url', $exports_url ); } /** * Schedules a `WP_Cron` job to delete expired export files. * * @since 4.9.6 */ function wp_schedule_delete_old_privacy_export_files() { if ( wp_installing() ) { return; } if ( ! wp_next_scheduled( 'wp_privacy_delete_old_export_files' ) ) { wp_schedule_event( time(), 'hourly', 'wp_privacy_delete_old_export_files' ); } } /** * Schedules a WP-Cron job to clean up personal data requests. * * @since 7.1.0 * * @see wp_privacy_personal_data_cleanup_requests() */ function wp_schedule_personal_data_cleanup_requests(): void { if ( wp_installing() ) { return; } if ( ! wp_next_scheduled( 'wp_privacy_personal_data_cleanup_requests' ) ) { wp_schedule_event( time(), 'daily', 'wp_privacy_personal_data_cleanup_requests' ); } } /** * Fires the personal data cleanup requests handler during cron. * * Loads the admin privacy tools file if needed (e.g. during cron, where * wp-admin/includes/privacy-tools.php is not loaded automatically). * * @since 7.1.0 */ function wp_privacy_personal_data_cleanup_requests(): void { if ( ! function_exists( '_wp_personal_data_cleanup_requests' ) ) { require_once ABSPATH . 'wp-admin/includes/privacy-tools.php'; } _wp_personal_data_cleanup_requests(); } /** * Cleans up export files older than three days old. * * The export files are stored in `wp-content/uploads`, and are therefore publicly * accessible. A CSPRN is appended to the filename to mitigate the risk of an * unauthorized person downloading the file, but it is still possible. Deleting * the file after the data subject has had a chance to delete it adds an additional * layer of protection. * * @since 4.9.6 */ function wp_privacy_delete_old_export_files() { $exports_dir = wp_privacy_exports_dir(); if ( ! is_dir( $exports_dir ) ) { return; } require_once ABSPATH . 'wp-admin/includes/file.php'; $export_files = list_files( $exports_dir, 100, array( 'index.php' ) ); /** * Filters the lifetime, in seconds, of a personal data export file. * * By default, the lifetime is 3 days. Once the file reaches that age, it will automatically * be deleted by a cron job. * * @since 4.9.6 * * @param int $expiration The expiration age of the export, in seconds. */ $expiration = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS ); foreach ( (array) $export_files as $export_file ) { $file_age_in_seconds = time() - filemtime( $export_file ); if ( $expiration < $file_age_in_seconds ) { unlink( $export_file ); } } } /** * Gets the URL to learn more about updating the PHP version the site is running on. * * This URL can be overridden by specifying an environment variable `WP_UPDATE_PHP_URL` or by using the * {@see 'wp_update_php_url'} filter. Providing an empty string is not allowed and will result in the * default URL being used. Furthermore the page the URL links to should preferably be localized in the * site language. * * @since 5.1.0 * * @return string URL to learn more about updating PHP. */ function wp_get_update_php_url() { $default_url = wp_get_default_update_php_url(); $update_url = $default_url; if ( false !== getenv( 'WP_UPDATE_PHP_URL' ) ) { $update_url = getenv( 'WP_UPDATE_PHP_URL' ); } /** * Filters the URL to learn more about updating the PHP version the site is running on. * * Providing an empty string is not allowed and will result in the default URL being used. Furthermore * the page the URL links to should preferably be localized in the site language. * * @since 5.1.0 * * @param string $update_url URL to learn more about updating PHP. */ $update_url = apply_filters( 'wp_update_php_url', $update_url ); if ( empty( $update_url ) ) { $update_url = $default_url; } return $update_url; } /** * Gets the default URL to learn more about updating the PHP version the site is running on. * * Do not use this function to retrieve this URL. Instead, use {@see wp_get_update_php_url()} when relying on the URL. * This function does not allow modifying the returned URL, and is only used to compare the actually used URL with the * default one. * * @since 5.1.0 * @access private * * @return string Default URL to learn more about updating PHP. */ function wp_get_default_update_php_url() { return _x( 'https://wordpress.org/support/update-php/', 'localized PHP upgrade information page' ); } /** * Prints the default annotation for the web host altering the "Update PHP" page URL. * * This function is to be used after {@see wp_get_update_php_url()} to display a consistent * annotation if the web host has altered the default "Update PHP" page URL. * * @since 5.1.0 * @since 5.2.0 Added the `$before` and `$after` parameters. * @since 6.4.0 Added the `$display` parameter. * * @param string $before Markup to output before the annotation. Default `
`. * @param string $after Markup to output after the annotation. Default `
`. * @param bool $display Whether to echo or return the markup. Default `true` for echo. * * @return string|null Update PHP page annotation if available and $display is false, null otherwise. */ function wp_update_php_annotation( $before = '', $after = '
', $display = true ) { $annotation = wp_get_update_php_annotation(); if ( $annotation ) { if ( $display ) { echo $before . $annotation . $after; } else { return $before . $annotation . $after; } } return null; } /** * Returns the default annotation for the web hosting altering the "Update PHP" page URL. * * This function is to be used after {@see wp_get_update_php_url()} to return a consistent * annotation if the web host has altered the default "Update PHP" page URL. * * @since 5.2.0 * * @return string Update PHP page annotation. An empty string if no custom URLs are provided. */ function wp_get_update_php_annotation() { $update_url = wp_get_update_php_url(); $default_url = wp_get_default_update_php_url(); if ( $update_url === $default_url ) { return ''; } $annotation = sprintf( /* translators: %s: Default Update PHP page URL. */ __( 'This resource is provided by your web host, and is specific to your site. For more information, see the official WordPress documentation.' ), esc_url( $default_url ) ); return $annotation; } /** * Gets the URL for directly updating the PHP version the site is running on. * * A URL will only be returned if the `WP_DIRECT_UPDATE_PHP_URL` environment variable is specified or * by using the {@see 'wp_direct_php_update_url'} filter. This allows hosts to send users directly to * the page where they can update PHP to a newer version. * * @since 5.1.1 * * @return string URL for directly updating PHP or empty string. */ function wp_get_direct_php_update_url() { $direct_update_url = ''; if ( false !== getenv( 'WP_DIRECT_UPDATE_PHP_URL' ) ) { $direct_update_url = getenv( 'WP_DIRECT_UPDATE_PHP_URL' ); } /** * Filters the URL for directly updating the PHP version the site is running on from the host. * * @since 5.1.1 * * @param string $direct_update_url URL for directly updating PHP. */ $direct_update_url = apply_filters( 'wp_direct_php_update_url', $direct_update_url ); return $direct_update_url; } /** * Displays a button directly linking to a PHP update process. * * This provides hosts with a way for users to be sent directly to their PHP update process. * * The button is only displayed if a URL is returned by `wp_get_direct_php_update_url()`. * * @since 5.1.1 */ function wp_direct_php_update_button() { $direct_update_url = wp_get_direct_php_update_url(); if ( empty( $direct_update_url ) ) { return; } echo ''; } /** * Gets the URL to learn more about updating the site to use HTTPS. * * This URL can be overridden by specifying an environment variable `WP_UPDATE_HTTPS_URL` or by using the * {@see 'wp_update_https_url'} filter. Providing an empty string is not allowed and will result in the * default URL being used. Furthermore the page the URL links to should preferably be localized in the * site language. * * @since 5.7.0 * * @return string URL to learn more about updating to HTTPS. */ function wp_get_update_https_url() { $default_url = wp_get_default_update_https_url(); $update_url = $default_url; if ( false !== getenv( 'WP_UPDATE_HTTPS_URL' ) ) { $update_url = getenv( 'WP_UPDATE_HTTPS_URL' ); } /** * Filters the URL to learn more about updating the HTTPS version the site is running on. * * Providing an empty string is not allowed and will result in the default URL being used. Furthermore * the page the URL links to should preferably be localized in the site language. * * @since 5.7.0 * * @param string $update_url URL to learn more about updating HTTPS. */ $update_url = apply_filters( 'wp_update_https_url', $update_url ); if ( empty( $update_url ) ) { $update_url = $default_url; } return $update_url; } /** * Gets the default URL to learn more about updating the site to use HTTPS. * * Do not use this function to retrieve this URL. Instead, use {@see wp_get_update_https_url()} when relying on the URL. * This function does not allow modifying the returned URL, and is only used to compare the actually used URL with the * default one. * * @since 5.7.0 * @access private * * @return string Default URL to learn more about updating to HTTPS. */ function wp_get_default_update_https_url() { /* translators: Documentation explaining HTTPS and why it should be used. */ return __( 'https://developer.wordpress.org/advanced-administration/security/https/' ); } /** * Gets the URL for directly updating the site to use HTTPS. * * A URL will only be returned if the `WP_DIRECT_UPDATE_HTTPS_URL` environment variable is specified or * by using the {@see 'wp_direct_update_https_url'} filter. This allows hosts to send users directly to * the page where they can update their site to use HTTPS. * * @since 5.7.0 * * @return string URL for directly updating to HTTPS or empty string. */ function wp_get_direct_update_https_url() { $direct_update_url = ''; if ( false !== getenv( 'WP_DIRECT_UPDATE_HTTPS_URL' ) ) { $direct_update_url = getenv( 'WP_DIRECT_UPDATE_HTTPS_URL' ); } /** * Filters the URL for directly updating the PHP version the site is running on from the host. * * @since 5.7.0 * * @param string $direct_update_url URL for directly updating PHP. */ $direct_update_url = apply_filters( 'wp_direct_update_https_url', $direct_update_url ); return $direct_update_url; } /** * Gets the size of a directory. * * A helper function that is used primarily to check whether * a blog has exceeded its allowed upload space. * * @since MU (3.0.0) * @since 5.2.0 $max_execution_time parameter added. * * @param string $directory Full path of a directory. * @param int $max_execution_time Maximum time to run before giving up. In seconds. * The timeout is global and is measured from the moment WordPress started to load. * @return int|false|null Size in bytes if a valid directory. False if not. Null if timeout. */ function get_dirsize( $directory, $max_execution_time = null ) { /* * Exclude individual site directories from the total when checking the main site of a network, * as they are subdirectories and should not be counted. */ if ( is_multisite() && is_main_site() ) { $size = recurse_dirsize( $directory, $directory . '/sites', $max_execution_time ); } else { $size = recurse_dirsize( $directory, null, $max_execution_time ); } return $size; } /** * Gets the size of a directory recursively. * * Used by get_dirsize() to get a directory size when it contains other directories. * * @since MU (3.0.0) * @since 4.3.0 The `$exclude` parameter was added. * @since 5.2.0 The `$max_execution_time` parameter was added. * @since 5.6.0 The `$directory_cache` parameter was added. * * @param string $directory Full path of a directory. * @param string|string[] $exclude Optional. Full path of a subdirectory to exclude from the total, * or array of paths. Expected without trailing slash(es). * Default null. * @param int $max_execution_time Optional. Maximum time to run before giving up. In seconds. * The timeout is global and is measured from the moment * WordPress started to load. Defaults to the value of * `max_execution_time` PHP setting. * @param array $directory_cache Optional. Array of cached directory paths. * Defaults to the value of `dirsize_cache` transient. * @return int|false|null Size in bytes if a valid directory. False if not. Null if timeout. */ function recurse_dirsize( $directory, $exclude = null, $max_execution_time = null, &$directory_cache = null ) { $directory = untrailingslashit( $directory ); $save_cache = false; if ( ! isset( $directory_cache ) ) { $directory_cache = get_transient( 'dirsize_cache' ); $save_cache = true; } if ( isset( $directory_cache[ $directory ] ) && is_int( $directory_cache[ $directory ] ) ) { return $directory_cache[ $directory ]; } if ( ! file_exists( $directory ) || ! is_dir( $directory ) || ! is_readable( $directory ) ) { return false; } if ( ( is_string( $exclude ) && $directory === $exclude ) || ( is_array( $exclude ) && in_array( $directory, $exclude, true ) ) ) { return false; } if ( null === $max_execution_time ) { // Keep the previous behavior but attempt to prevent fatal errors from timeout if possible. $max_execution_time = ini_get( 'max_execution_time' ); // Leave 1 second "buffer" for other operations if $max_execution_time has reasonable value. if ( $max_execution_time > 10 ) { $max_execution_time -= 1; } } /** * Filters the amount of storage space used by one directory and all its children, in megabytes. * * Return the actual used space to short-circuit the recursive PHP file size calculation * and use something else, like a CDN API or native operating system tools for better performance. * * @since 5.6.0 * * @param int|false $space_used The amount of used space, in bytes. Default false. * @param string $directory Full path of a directory. * @param string|string[]|null $exclude Full path of a subdirectory to exclude from the total, * or array of paths. * @param int $max_execution_time Maximum time to run before giving up. In seconds. * @param array $directory_cache Array of cached directory paths. */ $size = apply_filters( 'pre_recurse_dirsize', false, $directory, $exclude, $max_execution_time, $directory_cache ); if ( false === $size ) { $size = 0; $handle = opendir( $directory ); if ( $handle ) { while ( ( $file = readdir( $handle ) ) !== false ) { $path = $directory . '/' . $file; if ( '.' !== $file && '..' !== $file ) { if ( is_file( $path ) ) { $size += filesize( $path ); } elseif ( is_dir( $path ) ) { $handlesize = recurse_dirsize( $path, $exclude, $max_execution_time, $directory_cache ); if ( $handlesize > 0 ) { $size += $handlesize; } } if ( $max_execution_time > 0 && ( microtime( true ) - WP_START_TIMESTAMP ) > $max_execution_time ) { // Time exceeded. Give up instead of risking a fatal timeout. $size = null; break; } } } closedir( $handle ); } } if ( ! is_array( $directory_cache ) ) { $directory_cache = array(); } $directory_cache[ $directory ] = $size; // Only write the transient on the top level call and not on recursive calls. if ( $save_cache ) { $expiration = ( wp_using_ext_object_cache() ) ? 0 : 10 * YEAR_IN_SECONDS; set_transient( 'dirsize_cache', $directory_cache, $expiration ); } return $size; } /** * Cleans directory size cache used by recurse_dirsize(). * * Removes the current directory and all parent directories from the `dirsize_cache` transient. * * @since 5.6.0 * @since 5.9.0 Added input validation with a notice for invalid input. * * @param string $path Full path of a directory or file. */ function clean_dirsize_cache( $path ) { if ( ! is_string( $path ) || empty( $path ) ) { wp_trigger_error( '', sprintf( /* translators: 1: Function name, 2: A variable type, like "boolean" or "integer". */ __( '%1$s only accepts a non-empty path string, received %2$s.' ), 'clean_dirsize_cache()',
'' . gettype( $path ) . ''
)
);
return;
}
$directory_cache = get_transient( 'dirsize_cache' );
if ( empty( $directory_cache ) ) {
return;
}
$expiration = ( wp_using_ext_object_cache() ) ? 0 : 10 * YEAR_IN_SECONDS;
if (
! str_contains( $path, '/' ) &&
! str_contains( $path, '\\' )
) {
unset( $directory_cache[ $path ] );
set_transient( 'dirsize_cache', $directory_cache, $expiration );
return;
}
$last_path = null;
$path = untrailingslashit( $path );
unset( $directory_cache[ $path ] );
while (
$last_path !== $path &&
DIRECTORY_SEPARATOR !== $path &&
'.' !== $path &&
'..' !== $path
) {
$last_path = $path;
$path = dirname( $path );
unset( $directory_cache[ $path ] );
}
set_transient( 'dirsize_cache', $directory_cache, $expiration );
}
/**
* Returns the current WordPress version.
*
* Returns an unmodified value of `$wp_version`. Some plugins modify the global
* in an attempt to improve security through obscurity. This practice can cause
* errors in WordPress, so the ability to get an unmodified version is needed.
*
* @since 6.7.0
*
* @return string The current WordPress version.
*/
function wp_get_wp_version() {
static $wp_version;
if ( ! isset( $wp_version ) ) {
require ABSPATH . WPINC . '/version.php';
}
return $wp_version;
}
/**
* Checks compatibility with the current WordPress version.
*
* @since 5.2.0
*
* @global string $_wp_tests_wp_version The WordPress version string. Used only in Core tests.
*
* @param string $required Minimum required WordPress version.
* @return bool True if required version is compatible or empty, false if not.
*/
function is_wp_version_compatible( $required ) {
if (
defined( 'WP_RUN_CORE_TESTS' )
&& WP_RUN_CORE_TESTS
&& isset( $GLOBALS['_wp_tests_wp_version'] )
) {
$wp_version = $GLOBALS['_wp_tests_wp_version'];
} else {
$wp_version = wp_get_wp_version();
}
// Strip off any -alpha, -RC, -beta, -src suffixes.
list( $version ) = explode( '-', $wp_version );
if ( is_string( $required ) ) {
$trimmed = trim( $required );
if ( substr_count( $trimmed, '.' ) > 1 && str_ends_with( $trimmed, '.0' ) ) {
$required = substr( $trimmed, 0, -2 );
}
}
return empty( $required ) || version_compare( $version, $required, '>=' );
}
/**
* Checks compatibility with the current PHP version.
*
* @since 5.2.0
*
* @param string $required Minimum required PHP version.
* @return bool True if required version is compatible or empty, false if not.
*/
function is_php_version_compatible( $required ) {
return empty( $required ) || version_compare( PHP_VERSION, $required, '>=' );
}
/**
* Checks if two numbers are nearly the same.
*
* This is similar to using `round()` but the precision is more fine-grained.
*
* @since 5.3.0
*
* @param int|float $expected The expected value.
* @param int|float $actual The actual number.
* @param int|float $precision Optional. The allowed variation. Default 1.
* @return bool Whether the numbers match within the specified precision.
*/
function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) {
return abs( (float) $expected - (float) $actual ) <= $precision;
}
/**
* Creates and returns the markup for an admin notice.
*
* @since 6.4.0
*
* @param string $message The message.
* @param array $args {
* Optional. An array of arguments for the admin notice. Default empty array.
*
* @type string $type Optional. The type of admin notice.
* For example, 'error', 'success', 'warning', 'info'.
* Default empty string.
* @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false.
* @type string $id Optional. The value of the admin notice's ID attribute. Default empty string.
* @type string[] $additional_classes Optional. A string array of class names. Default empty array.
* @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array.
* @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true.
* }
* @return string The markup for an admin notice.
*/
function wp_get_admin_notice( $message, $args = array() ) {
$defaults = array(
'type' => '',
'dismissible' => false,
'id' => '',
'additional_classes' => array(),
'attributes' => array(),
'paragraph_wrap' => true,
);
$args = wp_parse_args( $args, $defaults );
/**
* Filters the arguments for an admin notice.
*
* @since 6.4.0
*
* @param array $args The arguments for the admin notice.
* @param string $message The message for the admin notice.
*/
$args = apply_filters( 'wp_admin_notice_args', $args, $message );
$id = '';
$classes = 'notice';
$attributes = '';
if ( is_string( $args['id'] ) ) {
$trimmed_id = trim( $args['id'] );
if ( '' !== $trimmed_id ) {
$id = 'id="' . $trimmed_id . '" ';
}
}
if ( is_string( $args['type'] ) ) {
$type = trim( $args['type'] );
if ( str_contains( $type, ' ' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: %s: The "type" key. */
__( 'The %s key must be a string without spaces.' ),
'type'
),
'6.4.0'
);
}
if ( '' !== $type ) {
$classes .= ' notice-' . $type;
}
}
if ( true === $args['dismissible'] ) {
$classes .= ' is-dismissible';
}
if ( is_array( $args['additional_classes'] ) && ! empty( $args['additional_classes'] ) ) {
$classes .= ' ' . implode( ' ', $args['additional_classes'] );
}
if ( is_array( $args['attributes'] ) && ! empty( $args['attributes'] ) ) {
$attributes = '';
foreach ( $args['attributes'] as $attr => $val ) {
if ( is_bool( $val ) ) {
$attributes .= $val ? ' ' . $attr : '';
} elseif ( is_int( $attr ) ) {
$attributes .= ' ' . esc_attr( trim( $val ) );
} elseif ( $val ) {
$attributes .= ' ' . $attr . '="' . esc_attr( trim( $val ) ) . '"';
}
}
}
if ( false !== $args['paragraph_wrap'] ) {
$message = "$message
"; } $markup = sprintf( '