: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
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.
* @param string $group The cache group name.
* @param int $time The new last changed time.
* @param int|false $previous_time The previous last changed time. False if not previously set.
do_action( 'wp_cache_set_last_changed', $group, $time, $previous_time );
* Sends 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']
* 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 ) ) {
// 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;
* Returns uniform "anonymous" data by type.
* @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 = '' ) {
$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 );
* Schedules 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.
* @since 6.4.0 Added the `$display` parameter.
* @param string $before Markup to output before the annotation. Default `<p class="description">`.
* @param string $after Markup to output after the annotation. Default `</p>`.
* @param bool $display Whether to echo or return the markup. Default `true` for echo.
function wp_update_php_annotation( $before = '<p class="description">', $after = '</p>', $display = true ) {
$annotation = wp_get_update_php_annotation();
echo $before . $annotation . $after;
return $before . $annotation . $after;
* 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.
* @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 ) {
/* translators: %s: Default Update PHP page URL. */
__( '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>.' ),
* 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.
* @return string URL for directly updating PHP or empty string.
function wp_get_direct_php_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.
* @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()`.
function wp_direct_php_update_button() {
$direct_update_url = wp_get_direct_php_update_url();
if ( empty( $direct_update_url ) ) {