akes the original `memory_limit` into account. * * @param int|string $filtered_limit Maximum memory limit to allocate for image processing. * Default `WP_MAX_MEMORY_LIMIT` or the original * php.ini `memory_limit`, whichever is higher. * Accepts an integer (bytes), or a shorthand string * notation, such as '256M'. */ $filtered_limit = apply_filters( 'image_memory_limit', $filtered_limit ); break; case 'cron': /** * Filters the memory limit allocated for WP-Cron event processing. * * @since 6.3.0 * * @param int|string $filtered_limit Maximum memory limit to allocate for WP-Cron. * Default `WP_MAX_MEMORY_LIMIT` or the original * php.ini `memory_limit`, whichever is higher. * Accepts an integer (bytes), or a shorthand string * notation, such as '256M'. */ $filtered_limit = apply_filters( 'cron_memory_limit', $filtered_limit ); break; default: /** * Filters the memory limit allocated for an arbitrary context. * * The dynamic portion of the hook name, `$context`, refers to an arbitrary * context passed on calling the function. This allows for plugins to define * their own contexts for raising the memory limit. * * @since 4.6.0 * * @param int|string $filtered_limit Maximum memory limit to allocate for this context. * Default WP_MAX_MEMORY_LIMIT` or the original php.ini `memory_limit`, * whichever is higher. Accepts an integer (bytes), or a * shorthand string notation, such as '256M'. */ $filtered_limit = apply_filters( "{$context}_memory_limit", $filtered_limit ); break; } $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 ) ) { return $filtered_limit; } else { return false; } } elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) { if ( false !== ini_set( 'memory_limit', $wp_max_limit ) ) { return $wp_max_limit; } else { return false; } } return false; } /** * Generates a random UUID (version 4). * * @since 4.7.0 * * @return string UUID. */ function wp_generate_uuid4() { return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0x0fff ) | 0x4000, mt_rand( 0, 0x3fff ) | 0x8000, mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 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; } /** * 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 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 ); 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 to the default 'admin_email' value. if ( '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' ); } } /** * 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|void */ 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; } } } /** * 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 '

'; printf( '%2$s %3$s', esc_url( $direct_update_url ), __( 'Update PHP' ), /* translators: Hidden accessibility text. */ __( '(opens in a new tab)' ) ); 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. if ( function_exists( 'ini_get' ) ) { $max_execution_time = ini_get( 'max_execution_time' ); } else { // Disable... $max_execution_time = 0; } // 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 ); } /** * Checks compatibility with the current WordPress version. * * @since 5.2.0 * * @global string $wp_version The WordPress version string. * * @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 ) { global $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( '
%4$s
', $id, $classes, $attributes, $message ); /** * Filters the markup for an admin notice. * * @since 6.4.0 * * @param string $markup The HTML markup for the admin notice. * @param string $message The message for the admin notice. * @param array $args The arguments for the admin notice. */ return apply_filters( 'wp_admin_notice_markup', $markup, $message, $args ); } /** * Outputs an admin notice. * * @since 6.4.0 * * @param string $message The message to output. * @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. * } */ function wp_admin_notice( $message, $args = array() ) { /** * Fires before an admin notice is output. * * @since 6.4.0 * * @param string $message The message for the admin notice. * @param array $args The arguments for the admin notice. */ do_action( 'wp_admin_notice', $message, $args ); echo wp_kses_post( wp_get_admin_notice( $message, $args ) ); }