$filtered_limit_int = wp_convert_hr_to_bytes( $filtered_limit );
if ( -1 === $filtered_limit_int || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) {
if ( false !== ini_set( 'memory_limit', $filtered_limit ) ) {
} elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) {
if ( false !== ini_set( 'memory_limit', $wp_max_limit ) ) {
* Generate a random UUID (version 4).
function wp_generate_uuid4() {
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand( 0, 0x0fff ) | 0x4000,
mt_rand( 0, 0x3fff ) | 0x8000,
* Validates that a UUID is valid.
* @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 ) ) {
if ( is_numeric( $version ) ) {
if ( 4 !== (int) $version ) {
_doing_it_wrong( __FUNCTION__, __( 'Only UUID V4 is supported at this time.' ), '4.9.0' );
$regex = '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/';
$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 );
* 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.
* @param string $prefix Prefix for the returned ID.
* @return string Unique ID.
function wp_unique_id( $prefix = '' ) {
return $prefix . (string) ++$id_counter;
* Gets last changed date for the specified cache group.
* @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 );
$last_changed = microtime();
wp_cache_set( 'last_changed', $last_changed, $group );
* Send an email to the old site admin email address when the site admin email address changes.
* @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 ) {
// Don't send the notification to the default 'admin_email' value.
if ( 'you@example.com' === $old_email ) {
* Filters whether to send the site admin email change notification email.
* @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 );
/* translators: Do not translate OLD_EMAIL, NEW_EMAIL, SITENAME, SITEURL: those are placeholders. */
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###
$email_change_email = array(
/* translators: Site admin email change notification email subject. %s: Site title. */
'subject' => __( '[%s] Admin Email Changed' ),
'message' => $email_change_text,
$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.
* @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'] );
$email_change_email['to'],
$email_change_email['subject'],
$email_change_email['message'],
$email_change_email['headers']
* Return 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 ) {
// Detect what kind of IP address this is.
$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_addr = preg_replace( '/^\[?[0-9a-f:]*:/i', '', $ip_addr );
$ip_addr = str_replace( ']', '', $ip_addr );
// 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.
// 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 ) ) {
// 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 ) {
} elseif ( ! $ipv6_fallback ) {
// Strip any port and partially anonymize the IP.
$last_octet_position = strrpos( $ip_addr, '.' );
$ip_addr = substr( $ip_addr, 0, $last_octet_position ) . '.0';
// Restore the IPv6 prefix to compatibility mode addresses.
return $ip_prefix . $ip_addr;
* Return uniform "anonymous" data by type.
* @param string $type The type of data to be anonymized.
* @param string $data Optional The data to be anonymized.
* @return string The anonymous data for the requested type.
function wp_privacy_anonymize_data( $type, $data = '' ) {
$anonymous = 'deleted@site.invalid';
$anonymous = 'https://site.invalid';
$anonymous = wp_privacy_anonymize_ip( $data );
$anonymous = '0000-00-00 00:00:00';
/* translators: Deleted text. */
$anonymous = __( '[deleted]' );
/* translators: Deleted long text. */
$anonymous = __( 'This content was deleted by the author.' );
* Filters the anonymous data for each type.
* @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.
* @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 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.
* @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 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 );
* Schedule a `WP_Cron` job to delete expired export files.
function wp_schedule_delete_old_privacy_export_files() {
if ( ! wp_next_scheduled( 'wp_privacy_delete_old_export_files' ) ) {
wp_schedule_event( time(), 'hourly', 'wp_privacy_delete_old_export_files' );
* 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
function wp_privacy_delete_old_export_files() {
$exports_dir = wp_privacy_exports_dir();
if ( ! is_dir( $exports_dir ) ) {
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.
* @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 ) {
* 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
* @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.
* @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;
* 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
* @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.2.0 Added the `$before` and `$after` parameters.
* @param string $before Markup to output before the annotation. Default `<p class="description">`.
* @param string $after Markup to output after the annotation. Default `</p>`.
function wp_update_php_annotation( $before = '<p class="description">', $after = '</p>' ) {
$annotation = wp_get_update_php_annotation();
echo $before . $annotation . $after;