File manager - Edit - /home/matthif/www/wp-content/languages/plugins/customize.tar
Back
https-migration-20260606015754.php 0000666 00000011205 15226762357 0012021 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260605080452.php 0000666 00000013341 15226762357 0012005 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260605082153.php 0000666 00000011205 15226762357 0012015 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } abilities.php 0000666 00000017457 15226762357 0007263 0 ustar 00 <?php /** * Core Abilities registration. * * @package WordPress * @subpackage Abilities_API * @since 6.9.0 */ declare( strict_types = 1 ); /** * Registers the core ability categories. * * @since 6.9.0 * * @return void */ function wp_register_core_ability_categories(): void { wp_register_ability_category( 'site', array( 'label' => __( 'Site' ), 'description' => __( 'Abilities that retrieve or modify site information and settings.' ), ) ); wp_register_ability_category( 'user', array( 'label' => __( 'User' ), 'description' => __( 'Abilities that retrieve or modify user information and settings.' ), ) ); } /** * Registers the default core abilities. * * @since 6.9.0 * * @return void */ function wp_register_core_abilities(): void { $category_site = 'site'; $category_user = 'user'; $site_info_properties = array( 'name' => array( 'type' => 'string', 'description' => __( 'The site title.' ), ), 'description' => array( 'type' => 'string', 'description' => __( 'The site tagline.' ), ), 'url' => array( 'type' => 'string', 'description' => __( 'The site home URL.' ), ), 'wpurl' => array( 'type' => 'string', 'description' => __( 'The WordPress installation URL.' ), ), 'admin_email' => array( 'type' => 'string', 'description' => __( 'The site administrator email address.' ), ), 'charset' => array( 'type' => 'string', 'description' => __( 'The site character encoding.' ), ), 'language' => array( 'type' => 'string', 'description' => __( 'The site language locale code.' ), ), 'version' => array( 'type' => 'string', 'description' => __( 'The WordPress version.' ), ), ); $site_info_fields = array_keys( $site_info_properties ); wp_register_ability( 'core/get-site-info', array( 'label' => __( 'Get Site Information' ), 'description' => __( 'Returns site information configured in WordPress. By default returns all fields, or optionally a filtered subset.' ), 'category' => $category_site, 'input_schema' => array( 'type' => 'object', 'properties' => array( 'fields' => array( 'type' => 'array', 'items' => array( 'type' => 'string', 'enum' => $site_info_fields, ), 'description' => __( 'Optional: Limit response to specific fields. If omitted, all fields are returned.' ), ), ), 'additionalProperties' => false, 'default' => array(), ), 'output_schema' => array( 'type' => 'object', 'properties' => $site_info_properties, 'additionalProperties' => false, ), 'execute_callback' => static function ( $input = array() ) use ( $site_info_fields ): array { $input = is_array( $input ) ? $input : array(); $requested_fields = ! empty( $input['fields'] ) ? $input['fields'] : $site_info_fields; $result = array(); foreach ( $requested_fields as $field ) { $result[ $field ] = get_bloginfo( $field ); } return $result; }, 'permission_callback' => static function (): bool { return current_user_can( 'manage_options' ); }, 'meta' => array( 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true, ), 'show_in_rest' => true, ), ) ); wp_register_ability( 'core/get-user-info', array( 'label' => __( 'Get User Information' ), 'description' => __( 'Returns basic profile details for the current authenticated user to support personalization, auditing, and access-aware behavior.' ), 'category' => $category_user, 'output_schema' => array( 'type' => 'object', 'required' => array( 'id', 'display_name', 'user_nicename', 'user_login', 'roles', 'locale' ), 'properties' => array( 'id' => array( 'type' => 'integer', 'description' => __( 'The user ID.' ), ), 'display_name' => array( 'type' => 'string', 'description' => __( 'The display name of the user.' ), ), 'user_nicename' => array( 'type' => 'string', 'description' => __( 'The URL-friendly name for the user.' ), ), 'user_login' => array( 'type' => 'string', 'description' => __( 'The login username for the user.' ), ), 'roles' => array( 'type' => 'array', 'description' => __( 'The roles assigned to the user.' ), 'items' => array( 'type' => 'string', ), ), 'locale' => array( 'type' => 'string', 'description' => __( 'The locale string for the user, such as en_US.' ), ), ), 'additionalProperties' => false, ), 'execute_callback' => static function (): array { $current_user = wp_get_current_user(); return array( 'id' => $current_user->ID, 'display_name' => $current_user->display_name, 'user_nicename' => $current_user->user_nicename, 'user_login' => $current_user->user_login, 'roles' => $current_user->roles, 'locale' => get_user_locale( $current_user ), ); }, 'permission_callback' => static function (): bool { return is_user_logged_in(); }, 'meta' => array( 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true, ), 'show_in_rest' => false, ), ) ); wp_register_ability( 'core/get-environment-info', array( 'label' => __( 'Get Environment Info' ), 'description' => __( 'Returns core details about the site\'s runtime context for diagnostics and compatibility (environment, PHP runtime, database server info, WordPress version).' ), 'category' => $category_site, 'output_schema' => array( 'type' => 'object', 'required' => array( 'environment', 'php_version', 'db_server_info', 'wp_version' ), 'properties' => array( 'environment' => array( 'type' => 'string', 'description' => __( 'The site\'s runtime environment classification (can be one of these: production, staging, development, local).' ), 'enum' => array( 'production', 'staging', 'development', 'local' ), ), 'php_version' => array( 'type' => 'string', 'description' => __( 'The PHP runtime version executing WordPress.' ), ), 'db_server_info' => array( 'type' => 'string', 'description' => __( 'The database server vendor and version string reported by the driver.' ), ), 'wp_version' => array( 'type' => 'string', 'description' => __( 'The WordPress core version running on this site.' ), ), ), 'additionalProperties' => false, ), 'execute_callback' => static function (): array { global $wpdb; $env = wp_get_environment_type(); $php_version = phpversion(); $db_server_info = ''; if ( method_exists( $wpdb, 'db_server_info' ) ) { $db_server_info = $wpdb->db_server_info() ?? ''; } $wp_version = get_bloginfo( 'version' ); return array( 'environment' => $env, 'php_version' => $php_version, 'db_server_info' => $db_server_info, 'wp_version' => $wp_version, ); }, 'permission_callback' => static function (): bool { return current_user_can( 'manage_options' ); }, 'meta' => array( 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true, ), 'show_in_rest' => true, ), ) ); } class-wp-http-20260621194056.php 0000666 00000121142 15226762357 0011400 0 ustar 00 <?php /** * HTTP API: WP_Http class * * @package WordPress * @subpackage HTTP * @since 2.7.0 */ // Don't load directly. if ( ! defined( 'ABSPATH' ) ) { die( '-1' ); } if ( ! class_exists( 'WpOrg\Requests\Autoload' ) ) { require ABSPATH . WPINC . '/Requests/src/Autoload.php'; WpOrg\Requests\Autoload::register(); WpOrg\Requests\Requests::set_certificate_path( ABSPATH . WPINC . '/certificates/ca-bundle.crt' ); } /** * Core class used for managing HTTP transports and making HTTP requests. * * This class is used to consistently make outgoing HTTP requests easy for developers * while still being compatible with the many PHP configurations under which * WordPress runs. * * Debugging includes several actions, which pass different variables for debugging the HTTP API. * * @since 2.7.0 */ #[AllowDynamicProperties] class WP_Http { // Aliases for HTTP response codes. const HTTP_CONTINUE = 100; const SWITCHING_PROTOCOLS = 101; const PROCESSING = 102; const EARLY_HINTS = 103; const OK = 200; const CREATED = 201; const ACCEPTED = 202; const NON_AUTHORITATIVE_INFORMATION = 203; const NO_CONTENT = 204; const RESET_CONTENT = 205; const PARTIAL_CONTENT = 206; const MULTI_STATUS = 207; const IM_USED = 226; const MULTIPLE_CHOICES = 300; const MOVED_PERMANENTLY = 301; const FOUND = 302; const SEE_OTHER = 303; const NOT_MODIFIED = 304; const USE_PROXY = 305; const RESERVED = 306; const TEMPORARY_REDIRECT = 307; const PERMANENT_REDIRECT = 308; const BAD_REQUEST = 400; const UNAUTHORIZED = 401; const PAYMENT_REQUIRED = 402; const FORBIDDEN = 403; const NOT_FOUND = 404; const METHOD_NOT_ALLOWED = 405; const NOT_ACCEPTABLE = 406; const PROXY_AUTHENTICATION_REQUIRED = 407; const REQUEST_TIMEOUT = 408; const CONFLICT = 409; const GONE = 410; const LENGTH_REQUIRED = 411; const PRECONDITION_FAILED = 412; const REQUEST_ENTITY_TOO_LARGE = 413; const REQUEST_URI_TOO_LONG = 414; const UNSUPPORTED_MEDIA_TYPE = 415; const REQUESTED_RANGE_NOT_SATISFIABLE = 416; const EXPECTATION_FAILED = 417; const IM_A_TEAPOT = 418; const MISDIRECTED_REQUEST = 421; const UNPROCESSABLE_ENTITY = 422; const LOCKED = 423; const FAILED_DEPENDENCY = 424; const TOO_EARLY = 425; const UPGRADE_REQUIRED = 426; const PRECONDITION_REQUIRED = 428; const TOO_MANY_REQUESTS = 429; const REQUEST_HEADER_FIELDS_TOO_LARGE = 431; const UNAVAILABLE_FOR_LEGAL_REASONS = 451; const INTERNAL_SERVER_ERROR = 500; const NOT_IMPLEMENTED = 501; const BAD_GATEWAY = 502; const SERVICE_UNAVAILABLE = 503; const GATEWAY_TIMEOUT = 504; const HTTP_VERSION_NOT_SUPPORTED = 505; const VARIANT_ALSO_NEGOTIATES = 506; const INSUFFICIENT_STORAGE = 507; const NOT_EXTENDED = 510; const NETWORK_AUTHENTICATION_REQUIRED = 511; /** * Send an HTTP request to a URI. * * Please note: The only URI that are supported in the HTTP Transport implementation * are the HTTP and HTTPS protocols. * * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args { * Optional. Array or string of HTTP request arguments. * * @type string $method Request method. Accepts 'GET', 'POST', 'HEAD', 'PUT', 'DELETE', * 'TRACE', 'OPTIONS', or 'PATCH'. * Some transports technically allow others, but should not be * assumed. Default 'GET'. * @type float $timeout How long the connection should stay open in seconds. Default 5. * @type int $redirection Number of allowed redirects. Not supported by all transports. * Default 5. * @type string $httpversion Version of the HTTP protocol to use. Accepts '1.0' and '1.1'. * Default '1.0'. * @type string $user-agent User-agent value sent. * Default 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ). * @type bool $reject_unsafe_urls Whether to pass URLs through wp_http_validate_url(). * Default false. * @type bool $blocking Whether the calling code requires the result of the request. * If set to false, the request will be sent to the remote server, * and processing returned to the calling code immediately, the caller * will know if the request succeeded or failed, but will not receive * any response from the remote server. Default true. * @type string|array $headers Array or string of headers to send with the request. * Default empty array. * @type array $cookies List of cookies to send with the request. Default empty array. * @type string|array $body Body to send with the request. Default null. * @type bool $compress Whether to compress the $body when sending the request. * Default false. * @type bool $decompress Whether to decompress a compressed response. If set to false and * compressed content is returned in the response anyway, it will * need to be separately decompressed. Default true. * @type bool $sslverify Whether to verify SSL for the request. Default true. * @type string $sslcertificates Absolute path to an SSL certificate .crt file. * Default ABSPATH . WPINC . '/certificates/ca-bundle.crt'. * @type bool $stream Whether to stream to a file. If set to true and no filename was * given, it will be dropped it in the WP temp dir and its name will * be set using the basename of the URL. Default false. * @type string $filename Filename of the file to write to when streaming. $stream must be * set to true. Default null. * @type int $limit_response_size Size in bytes to limit the response to. Default null. * * } * @return array|WP_Error { * Array of response data, or a WP_Error instance upon error. * * @type \WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers Response headers keyed by name. * @type string $body Response body. * @type array $response { * Array of HTTP response data. * * @type int|false $code HTTP response status code. * @type string|false $message HTTP response message. * } * @type WP_HTTP_Cookie[] $cookies Array of cookies set by the server. * @type string|null $filename Optional. Filename of the response. * @type WP_HTTP_Requests_Response|null $http_response Response object. * } */ public function request( $url, $args = array() ) { $defaults = array( 'method' => 'GET', /** * Filters the timeout value for an HTTP request. * * @since 2.7.0 * @since 5.1.0 The `$url` parameter was added. * * @param float $timeout_value Time in seconds until a request times out. Default 5. * @param string $url The request URL. */ 'timeout' => apply_filters( 'http_request_timeout', 5, $url ), /** * Filters the number of redirects allowed during an HTTP request. * * @since 2.7.0 * @since 5.1.0 The `$url` parameter was added. * * @param int $redirect_count Number of redirects allowed. Default 5. * @param string $url The request URL. */ 'redirection' => apply_filters( 'http_request_redirection_count', 5, $url ), /** * Filters the version of the HTTP protocol used in a request. * * @since 2.7.0 * @since 5.1.0 The `$url` parameter was added. * * @param string $version Version of HTTP used. Accepts '1.0' and '1.1'. Default '1.0'. * @param string $url The request URL. */ 'httpversion' => apply_filters( 'http_request_version', '1.0', $url ), /** * Filters the user agent value sent with an HTTP request. * * @since 2.7.0 * @since 5.1.0 The `$url` parameter was added. * * @param string $user_agent WordPress user agent string. * @param string $url The request URL. */ 'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), $url ), /** * Filters whether to pass URLs through wp_http_validate_url() in an HTTP request. * * @since 3.6.0 * @since 5.1.0 The `$url` parameter was added. * * @param bool $pass_url Whether to pass URLs through wp_http_validate_url(). Default false. * @param string $url The request URL. */ 'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false, $url ), 'blocking' => true, 'headers' => array(), 'cookies' => array(), 'body' => null, 'compress' => false, 'decompress' => true, 'sslverify' => true, 'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt', 'stream' => false, 'filename' => null, 'limit_response_size' => null, ); // Pre-parse for the HEAD checks. $args = wp_parse_args( $args ); // By default, HEAD requests do not cause redirections. if ( isset( $args['method'] ) && 'HEAD' === $args['method'] ) { $defaults['redirection'] = 0; } $parsed_args = wp_parse_args( $args, $defaults ); /** * Filters the arguments used in an HTTP request. * * @since 2.7.0 * * @param array $parsed_args An array of HTTP request arguments. * @param string $url The request URL. */ $parsed_args = apply_filters( 'http_request_args', $parsed_args, $url ); // The transports decrement this, store a copy of the original value for loop purposes. if ( ! isset( $parsed_args['_redirection'] ) ) { $parsed_args['_redirection'] = $parsed_args['redirection']; } /** * Filters the preemptive return value of an HTTP request. * * Returning a non-false value from the filter will short-circuit the HTTP request and return * early with that value. A filter should return one of: * * - An array containing 'headers', 'body', 'response', 'cookies', and 'filename' elements * - A WP_Error instance * - Boolean false to avoid short-circuiting the response * * Returning any other value may result in unexpected behavior. * * @since 2.9.0 * * @param false|array|WP_Error $response A preemptive return value of an HTTP request. Default false. * @param array $parsed_args HTTP request arguments. * @param string $url The request URL. */ $pre = apply_filters( 'pre_http_request', false, $parsed_args, $url ); if ( false !== $pre ) { return $pre; } if ( function_exists( 'wp_kses_bad_protocol' ) ) { if ( $parsed_args['reject_unsafe_urls'] ) { $url = wp_http_validate_url( $url ); } if ( $url ) { $url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) ); } } $parsed_url = parse_url( $url ); if ( empty( $url ) || empty( $parsed_url['scheme'] ) ) { $response = new WP_Error( 'http_request_failed', __( 'A valid URL was not provided.' ) ); /** This action is documented in wp-includes/class-wp-http.php */ do_action( 'http_api_debug', $response, 'response', 'WpOrg\Requests\Requests', $parsed_args, $url ); return $response; } if ( $this->block_request( $url ) ) { $response = new WP_Error( 'http_request_not_executed', __( 'User has blocked requests through HTTP.' ) ); /** This action is documented in wp-includes/class-wp-http.php */ do_action( 'http_api_debug', $response, 'response', 'WpOrg\Requests\Requests', $parsed_args, $url ); return $response; } // If we are streaming to a file but no filename was given drop it in the WP temp dir // and pick its name using the basename of the $url. if ( $parsed_args['stream'] ) { if ( empty( $parsed_args['filename'] ) ) { $parsed_args['filename'] = get_temp_dir() . basename( $url ); } // Force some settings if we are streaming to a file and check for existence // and perms of destination directory. $parsed_args['blocking'] = true; if ( ! wp_is_writable( dirname( $parsed_args['filename'] ) ) ) { $response = new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) ); /** This action is documented in wp-includes/class-wp-http.php */ do_action( 'http_api_debug', $response, 'response', 'WpOrg\Requests\Requests', $parsed_args, $url ); return $response; } } if ( is_null( $parsed_args['headers'] ) ) { $parsed_args['headers'] = array(); } // WP allows passing in headers as a string, weirdly. if ( ! is_array( $parsed_args['headers'] ) ) { $processed_headers = self::processHeaders( $parsed_args['headers'] ); $parsed_args['headers'] = $processed_headers['headers']; } // Setup arguments. $headers = $parsed_args['headers']; $data = $parsed_args['body']; $type = $parsed_args['method']; $options = array( 'timeout' => $parsed_args['timeout'], 'useragent' => $parsed_args['user-agent'], 'blocking' => $parsed_args['blocking'], 'hooks' => new WP_HTTP_Requests_Hooks( $url, $parsed_args ), ); // Ensure redirects follow browser behavior. $options['hooks']->register( 'requests.before_redirect', array( static::class, 'browser_redirect_compatibility' ) ); // Validate redirected URLs. if ( function_exists( 'wp_kses_bad_protocol' ) && $parsed_args['reject_unsafe_urls'] ) { $options['hooks']->register( 'requests.before_redirect', array( static::class, 'validate_redirects' ) ); } if ( $parsed_args['stream'] ) { $options['filename'] = $parsed_args['filename']; } if ( empty( $parsed_args['redirection'] ) ) { $options['follow_redirects'] = false; } else { $options['redirects'] = $parsed_args['redirection']; } // Use byte limit, if we can. if ( isset( $parsed_args['limit_response_size'] ) ) { $options['max_bytes'] = $parsed_args['limit_response_size']; } // If we've got cookies, use and convert them to WpOrg\Requests\Cookie. if ( ! empty( $parsed_args['cookies'] ) ) { $options['cookies'] = self::normalize_cookies( $parsed_args['cookies'] ); } // SSL certificate handling. if ( ! $parsed_args['sslverify'] ) { $options['verify'] = false; $options['verifyname'] = false; } else { $options['verify'] = $parsed_args['sslcertificates']; } // All non-GET/HEAD requests should put the arguments in the form body. if ( 'HEAD' !== $type && 'GET' !== $type ) { $options['data_format'] = 'body'; } /** * Filters whether SSL should be verified for non-local requests. * * @since 2.8.0 * @since 5.1.0 The `$url` parameter was added. * * @param bool|string $ssl_verify Boolean to control whether to verify the SSL connection * or path to an SSL certificate. * @param string $url The request URL. */ $options['verify'] = apply_filters( 'https_ssl_verify', $options['verify'], $url ); // Check for proxies. $proxy = new WP_HTTP_Proxy(); if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) { $options['proxy'] = new WpOrg\Requests\Proxy\Http( $proxy->host() . ':' . $proxy->port() ); if ( $proxy->use_authentication() ) { $options['proxy']->use_authentication = true; $options['proxy']->user = $proxy->username(); $options['proxy']->pass = $proxy->password(); } } // Avoid issues where mbstring.func_overload is enabled. mbstring_binary_safe_encoding(); try { $requests_response = WpOrg\Requests\Requests::request( $url, $headers, $data, $type, $options ); // Convert the response into an array. $http_response = new WP_HTTP_Requests_Response( $requests_response, $parsed_args['filename'] ); $response = $http_response->to_array(); // Add the original object to the array. $response['http_response'] = $http_response; } catch ( WpOrg\Requests\Exception $e ) { $response = new WP_Error( 'http_request_failed', $e->getMessage() ); } reset_mbstring_encoding(); /** * Fires after an HTTP API response is received and before the response is returned. * * @since 2.8.0 * * @param array|WP_Error $response HTTP response or WP_Error object. * @param string $context Context under which the hook is fired. * @param string $class HTTP transport used. * @param array $parsed_args HTTP request arguments. * @param string $url The request URL. */ do_action( 'http_api_debug', $response, 'response', 'WpOrg\Requests\Requests', $parsed_args, $url ); if ( is_wp_error( $response ) ) { return $response; } if ( ! $parsed_args['blocking'] ) { return array( 'headers' => array(), 'body' => '', 'response' => array( 'code' => false, 'message' => false, ), 'cookies' => array(), 'http_response' => null, ); } /** * Filters a successful HTTP API response immediately before the response is returned. * * @since 2.9.0 * * @param array $response HTTP response. * @param array $parsed_args HTTP request arguments. * @param string $url The request URL. */ return apply_filters( 'http_response', $response, $parsed_args, $url ); } /** * Normalizes cookies for using in Requests. * * @since 4.6.0 * * @param array $cookies Array of cookies to send with the request. * @return WpOrg\Requests\Cookie\Jar Cookie holder object. */ public static function normalize_cookies( $cookies ) { $cookie_jar = new WpOrg\Requests\Cookie\Jar(); foreach ( $cookies as $name => $value ) { if ( $value instanceof WP_Http_Cookie ) { $attributes = array_filter( $value->get_attributes(), static function ( $attr ) { return null !== $attr; } ); $cookie_jar[ $value->name ] = new WpOrg\Requests\Cookie( (string) $value->name, $value->value, $attributes, array( 'host-only' => $value->host_only ) ); } elseif ( is_scalar( $value ) ) { $cookie_jar[ $name ] = new WpOrg\Requests\Cookie( (string) $name, (string) $value ); } } return $cookie_jar; } /** * Match redirect behavior to browser handling. * * Changes 302 redirects from POST to GET to match browser handling. Per * RFC 7231, user agents can deviate from the strict reading of the * specification for compatibility purposes. * * @since 4.6.0 * * @param string $location URL to redirect to. * @param array $headers Headers for the redirect. * @param string|array $data Body to send with the request. * @param array $options Redirect request options. * @param WpOrg\Requests\Response $original Response object. */ public static function browser_redirect_compatibility( $location, $headers, $data, &$options, $original ) { // Browser compatibility. if ( 302 === $original->status_code ) { $options['type'] = WpOrg\Requests\Requests::GET; } } /** * Validate redirected URLs. * * @since 4.7.5 * * @throws WpOrg\Requests\Exception On unsuccessful URL validation. * @param string $location URL to redirect to. */ public static function validate_redirects( $location ) { if ( ! wp_http_validate_url( $location ) ) { throw new WpOrg\Requests\Exception( __( 'A valid URL was not provided.' ), 'wp_http.redirect_failed_validation' ); } } /** * Tests which transports are capable of supporting the request. * * @since 3.2.0 * @deprecated 6.4.0 Use WpOrg\Requests\Requests::get_transport_class() * @see WpOrg\Requests\Requests::get_transport_class() * * @param array $args Request arguments. * @param string $url URL to request. * @return string|false Class name for the first transport that claims to support the request. * False if no transport claims to support the request. */ public function _get_first_available_transport( $args, $url = null ) { $transports = array( 'curl', 'streams' ); /** * Filters which HTTP transports are available and in what order. * * @since 3.7.0 * @deprecated 6.4.0 Use WpOrg\Requests\Requests::get_transport_class() * * @param string[] $transports Array of HTTP transports to check. Default array contains * 'curl' and 'streams', in that order. * @param array $args HTTP request arguments. * @param string $url The URL to request. */ $request_order = apply_filters_deprecated( 'http_api_transports', array( $transports, $args, $url ), '6.4.0' ); // Loop over each transport on each HTTP request looking for one which will serve this request's needs. foreach ( $request_order as $transport ) { if ( in_array( $transport, $transports, true ) ) { $transport = ucfirst( $transport ); } $class = 'WP_Http_' . $transport; // Check to see if this transport is a possibility, calls the transport statically. if ( ! call_user_func( array( $class, 'test' ), $args, $url ) ) { continue; } return $class; } return false; } /** * Dispatches a HTTP request to a supporting transport. * * Tests each transport in order to find a transport which matches the request arguments. * Also caches the transport instance to be used later. * * The order for requests is cURL, and then PHP Streams. * * @since 3.2.0 * @deprecated 5.1.0 Use WP_Http::request() * @see WP_Http::request() * * @param string $url URL to request. * @param array $args Request arguments. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. * A WP_Error instance upon error. */ private function _dispatch_request( $url, $args ) { static $transports = array(); $class = $this->_get_first_available_transport( $args, $url ); if ( ! $class ) { return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) ); } // Transport claims to support request, instantiate it and give it a whirl. if ( empty( $transports[ $class ] ) ) { $transports[ $class ] = new $class(); } $response = $transports[ $class ]->request( $url, $args ); /** This action is documented in wp-includes/class-wp-http.php */ do_action( 'http_api_debug', $response, 'response', $class, $args, $url ); if ( is_wp_error( $response ) ) { return $response; } /** This filter is documented in wp-includes/class-wp-http.php */ return apply_filters( 'http_response', $response, $args, $url ); } /** * Uses the POST HTTP method. * * Used for sending data that is expected to be in the body. * * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. * A WP_Error instance upon error. See WP_Http::response() for details. */ public function post( $url, $args = array() ) { $defaults = array( 'method' => 'POST' ); $parsed_args = wp_parse_args( $args, $defaults ); return $this->request( $url, $parsed_args ); } /** * Uses the GET HTTP method. * * Used for sending data that is expected to be in the body. * * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. * A WP_Error instance upon error. See WP_Http::response() for details. */ public function get( $url, $args = array() ) { $defaults = array( 'method' => 'GET' ); $parsed_args = wp_parse_args( $args, $defaults ); return $this->request( $url, $parsed_args ); } /** * Uses the HEAD HTTP method. * * Used for sending data that is expected to be in the body. * * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. * A WP_Error instance upon error. See WP_Http::response() for details. */ public function head( $url, $args = array() ) { $defaults = array( 'method' => 'HEAD' ); $parsed_args = wp_parse_args( $args, $defaults ); return $this->request( $url, $parsed_args ); } /** * Parses the responses and splits the parts into headers and body. * * @since 2.7.0 * * @param string $response The full response string. * @return array { * Array with response headers and body. * * @type string $headers HTTP response headers. * @type string $body HTTP response body. * } */ public static function processResponse( $response ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid $response = explode( "\r\n\r\n", $response, 2 ); return array( 'headers' => $response[0], 'body' => isset( $response[1] ) ? $response[1] : '', ); } /** * Transforms header string into an array. * * @since 2.7.0 * * @param string|array $headers The original headers. If a string is passed, it will be converted * to an array. If an array is passed, then it is assumed to be * raw header data with numeric keys with the headers as the values. * No headers must be passed that were already processed. * @param string $url Optional. The URL that was requested. Default empty. * @return array { * Processed string headers. If duplicate headers are encountered, * then a numbered array is returned as the value of that header-key. * * @type array $response { * @type int $code The response status code. Default 0. * @type string $message The response message. Default empty. * } * @type array $newheaders The processed header data as a multidimensional array. * @type WP_Http_Cookie[] $cookies If the original headers contain the 'Set-Cookie' key, * an array containing `WP_Http_Cookie` objects is returned. * } */ public static function processHeaders( $headers, $url = '' ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid // Split headers, one per array element. if ( is_string( $headers ) ) { // Tolerate line terminator: CRLF = LF (RFC 2616 19.3). $headers = str_replace( "\r\n", "\n", $headers ); /* * Unfold folded header fields. LWS = [CRLF] 1*( SP | HT ) <US-ASCII SP, space (32)>, * <US-ASCII HT, horizontal-tab (9)> (RFC 2616 2.2). */ $headers = preg_replace( '/\n[ \t]/', ' ', $headers ); // Create the headers array. $headers = explode( "\n", $headers ); } $response = array( 'code' => 0, 'message' => '', ); /* * If a redirection has taken place, The headers for each page request may have been passed. * In this case, determine the final HTTP header and parse from there. */ for ( $i = count( $headers ) - 1; $i >= 0; $i-- ) { if ( ! empty( $headers[ $i ] ) && ! str_contains( $headers[ $i ], ':' ) ) { $headers = array_splice( $headers, $i ); break; } } $cookies = array(); $newheaders = array(); foreach ( (array) $headers as $tempheader ) { if ( empty( $tempheader ) ) { continue; } if ( ! str_contains( $tempheader, ':' ) ) { $stack = explode( ' ', $tempheader, 3 ); $stack[] = ''; list( , $response['code'], $response['message']) = $stack; continue; } list($key, $value) = explode( ':', $tempheader, 2 ); $key = strtolower( $key ); $value = trim( $value ); if ( isset( $newheaders[ $key ] ) ) { if ( ! is_array( $newheaders[ $key ] ) ) { $newheaders[ $key ] = array( $newheaders[ $key ] ); } $newheaders[ $key ][] = $value; } else { $newheaders[ $key ] = $value; } if ( 'set-cookie' === $key ) { $cookies[] = new WP_Http_Cookie( $value, $url ); } } // Cast the Response Code to an int. $response['code'] = (int) $response['code']; return array( 'response' => $response, 'headers' => $newheaders, 'cookies' => $cookies, ); } /** * Takes the arguments for a ::request() and checks for the cookie array. * * If it's found, then it upgrades any basic name => value pairs to WP_Http_Cookie instances, * which are each parsed into strings and added to the Cookie: header (within the arguments array). * Edits the array by reference. * * @since 2.8.0 * * @param array $r Full array of args passed into ::request() */ public static function buildCookieHeader( &$r ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid if ( ! empty( $r['cookies'] ) ) { // Upgrade any name => value cookie pairs to WP_HTTP_Cookie instances. foreach ( $r['cookies'] as $name => $value ) { if ( ! is_object( $value ) ) { $r['cookies'][ $name ] = new WP_Http_Cookie( array( 'name' => $name, 'value' => $value, ) ); } } $cookies_header = ''; foreach ( (array) $r['cookies'] as $cookie ) { $cookies_header .= $cookie->getHeaderValue() . '; '; } $cookies_header = substr( $cookies_header, 0, -2 ); $r['headers']['cookie'] = $cookies_header; } } /** * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification. * * Based off the HTTP http_encoding_dechunk function. * * @link https://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding. * * @since 2.7.0 * * @param string $body Body content. * @return string Chunked decoded body on success or raw body on failure. */ public static function chunkTransferDecode( $body ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid // The body is not chunked encoded or is malformed. if ( ! preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', trim( $body ) ) ) { return $body; } $parsed_body = ''; // We'll be altering $body, so need a backup in case of error. $body_original = $body; while ( true ) { $has_chunk = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $body, $match ); if ( ! $has_chunk || empty( $match[1] ) ) { return $body_original; } $length = hexdec( $match[1] ); $chunk_length = strlen( $match[0] ); // Parse out the chunk of data. $parsed_body .= substr( $body, $chunk_length, $length ); // Remove the chunk from the raw data. $body = substr( $body, $length + $chunk_length ); // End of the document. if ( '0' === trim( $body ) ) { return $parsed_body; } } } /** * Determines whether an HTTP API request to the given URL should be blocked. * * Those who are behind a proxy and want to prevent access to certain hosts may do so. This will * prevent plugins from working and core functionality, if you don't include `api.wordpress.org`. * * You block external URL requests by defining `WP_HTTP_BLOCK_EXTERNAL` as true in your `wp-config.php` * file and this will only allow localhost and your site to make requests. The constant * `WP_ACCESSIBLE_HOSTS` will allow additional hosts to go through for requests. The format of the * `WP_ACCESSIBLE_HOSTS` constant is a comma separated list of hostnames to allow, wildcard domains * are supported, eg `*.wordpress.org` will allow for all subdomains of `wordpress.org` to be contacted. * * @since 2.8.0 * * @link https://core.trac.wordpress.org/ticket/8927 Allow preventing external requests. * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_ACCESSIBLE_HOSTS * * @param string $uri URI of url. * @return bool True to block, false to allow. */ public function block_request( $uri ) { // We don't need to block requests, because nothing is blocked. if ( ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ! WP_HTTP_BLOCK_EXTERNAL ) { return false; } $check = parse_url( $uri ); if ( ! $check ) { return true; } $home = parse_url( get_option( 'siteurl' ) ); // Don't block requests back to ourselves by default. if ( 'localhost' === $check['host'] || ( isset( $home['host'] ) && $home['host'] === $check['host'] ) ) { /** * Filters whether to block local HTTP API requests. * * A local request is one to `localhost` or to the same host as the site itself. * * @since 2.8.0 * * @param bool $block Whether to block local requests. Default false. */ return apply_filters( 'block_local_requests', false ); } if ( ! defined( 'WP_ACCESSIBLE_HOSTS' ) ) { return true; } static $accessible_hosts = null; static $wildcard_regex = array(); if ( null === $accessible_hosts ) { $accessible_hosts = preg_split( '|,\s*|', WP_ACCESSIBLE_HOSTS ); if ( str_contains( WP_ACCESSIBLE_HOSTS, '*' ) ) { $wildcard_regex = array(); foreach ( $accessible_hosts as $host ) { $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) ); } $wildcard_regex = '/^(' . implode( '|', $wildcard_regex ) . ')$/i'; } } if ( ! empty( $wildcard_regex ) ) { return ! preg_match( $wildcard_regex, $check['host'] ); } else { return ! in_array( $check['host'], $accessible_hosts, true ); // Inverse logic, if it's in the array, then don't block it. } } /** * Used as a wrapper for PHP's parse_url() function that handles edgecases in < PHP 5.4.7. * * @deprecated 4.4.0 Use wp_parse_url() * @see wp_parse_url() * * @param string $url The URL to parse. * @return bool|array False on failure; Array of URL components on success; * See parse_url()'s return values. */ protected static function parse_url( $url ) { _deprecated_function( __METHOD__, '4.4.0', 'wp_parse_url()' ); return wp_parse_url( $url ); } /** * Converts a relative URL to an absolute URL relative to a given URL. * * If an Absolute URL is provided, no processing of that URL is done. * * @since 3.4.0 * * @param string $maybe_relative_path The URL which might be relative. * @param string $url The URL which $maybe_relative_path is relative to. * @return string An Absolute URL, in a failure condition where the URL cannot be parsed, the relative URL will be returned. */ public static function make_absolute_url( $maybe_relative_path, $url ) { if ( empty( $url ) ) { return $maybe_relative_path; } $url_parts = wp_parse_url( $url ); if ( ! $url_parts ) { return $maybe_relative_path; } $relative_url_parts = wp_parse_url( $maybe_relative_path ); if ( ! $relative_url_parts ) { return $maybe_relative_path; } // Check for a scheme on the 'relative' URL. if ( ! empty( $relative_url_parts['scheme'] ) ) { return $maybe_relative_path; } $absolute_path = $url_parts['scheme'] . '://'; // Schemeless URLs will make it this far, so we check for a host in the relative URL // and convert it to a protocol-URL. if ( isset( $relative_url_parts['host'] ) ) { $absolute_path .= $relative_url_parts['host']; if ( isset( $relative_url_parts['port'] ) ) { $absolute_path .= ':' . $relative_url_parts['port']; } } else { $absolute_path .= $url_parts['host']; if ( isset( $url_parts['port'] ) ) { $absolute_path .= ':' . $url_parts['port']; } } // Start off with the absolute URL path. $path = ! empty( $url_parts['path'] ) ? $url_parts['path'] : '/'; // If it's a root-relative path, then great. if ( ! empty( $relative_url_parts['path'] ) && '/' === $relative_url_parts['path'][0] ) { $path = $relative_url_parts['path']; // Else it's a relative path. } elseif ( ! empty( $relative_url_parts['path'] ) ) { // Strip off any file components from the absolute path. $path = substr( $path, 0, strrpos( $path, '/' ) + 1 ); // Build the new path. $path .= $relative_url_parts['path']; // Strip all /path/../ out of the path. while ( strpos( $path, '../' ) > 1 ) { $path = preg_replace( '![^/]+/\.\./!', '', $path ); } // Strip any final leading ../ from the path. $path = preg_replace( '!^/(\.\./)+!', '', $path ); } // Add the query string. if ( ! empty( $relative_url_parts['query'] ) ) { $path .= '?' . $relative_url_parts['query']; } // Add the fragment. if ( ! empty( $relative_url_parts['fragment'] ) ) { $path .= '#' . $relative_url_parts['fragment']; } return $absolute_path . '/' . ltrim( $path, '/' ); } /** * Handles an HTTP redirect and follows it if appropriate. * * @since 3.7.0 * * @param string $url The URL which was requested. * @param array $args The arguments which were used to make the request. * @param array $response The response of the HTTP request. * @return array|false|WP_Error An HTTP API response array if the redirect is successfully followed, * false if no redirect is present, or a WP_Error object if there's an error. */ public static function handle_redirects( $url, $args, $response ) { // If no redirects are present, or, redirects were not requested, perform no action. if ( ! isset( $response['headers']['location'] ) || 0 === $args['_redirection'] ) { return false; } // Only perform redirections on redirection http codes. if ( $response['response']['code'] > 399 || $response['response']['code'] < 300 ) { return false; } // Don't redirect if we've run out of redirects. if ( $args['redirection']-- <= 0 ) { return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); } $redirect_location = $response['headers']['location']; // If there were multiple Location headers, use the last header specified. if ( is_array( $redirect_location ) ) { $redirect_location = array_pop( $redirect_location ); } $redirect_location = self::make_absolute_url( $redirect_location, $url ); // POST requests should not POST to a redirected location. if ( 'POST' === $args['method'] ) { if ( in_array( $response['response']['code'], array( 302, 303 ), true ) ) { $args['method'] = 'GET'; } } // Include valid cookies in the redirect process. if ( ! empty( $response['cookies'] ) ) { foreach ( $response['cookies'] as $cookie ) { if ( $cookie->test( $redirect_location ) ) { $args['cookies'][] = $cookie; } } } return wp_remote_request( $redirect_location, $args ); } /** * Determines if a specified string represents an IP address or not. * * This function also detects the type of the IP address, returning either * '4' or '6' to represent an IPv4 and IPv6 address respectively. * This does not verify if the IP is a valid IP, only that it appears to be * an IP address. * * @link http://home.deds.nl/~aeron/regex/ for IPv6 regex. * * @since 3.7.0 * * @param string $maybe_ip A suspected IP address. * @return int|false Upon success, '4' or '6' to represent an IPv4 or IPv6 address, false upon failure. */ public static function is_ip_address( $maybe_ip ) { if ( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $maybe_ip ) ) { return 4; } if ( str_contains( $maybe_ip, ':' ) && preg_match( '/^(((?=.*(::))(?!.*\3.+\3))\3?|([\dA-F]{1,4}(\3|:\b|$)|\2))(?4){5}((?4){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i', trim( $maybe_ip, ' []' ) ) ) { return 6; } return false; } } class-wp-oembed-controller.php 0000666 00000015371 15226762357 0012452 0 ustar 00 <?php /** * WP_oEmbed_Controller class, used to provide an oEmbed endpoint. * * @package WordPress * @subpackage Embeds * @since 4.4.0 */ /** * oEmbed API endpoint controller. * * Registers the REST API route and delivers the response data. * The output format (XML or JSON) is handled by the REST API. * * @since 4.4.0 */ #[AllowDynamicProperties] final class WP_oEmbed_Controller { /** * Register the oEmbed REST API route. * * @since 4.4.0 */ public function register_routes() { /** * Filters the maxwidth oEmbed parameter. * * @since 4.4.0 * * @param int $maxwidth Maximum allowed width. Default 600. */ $maxwidth = apply_filters( 'oembed_default_width', 600 ); register_rest_route( 'oembed/1.0', '/embed', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => '__return_true', 'args' => array( 'url' => array( 'description' => __( 'The URL of the resource for which to fetch oEmbed data.' ), 'required' => true, 'type' => 'string', 'format' => 'uri', ), 'format' => array( 'default' => 'json', 'sanitize_callback' => 'wp_oembed_ensure_format', ), 'maxwidth' => array( 'default' => $maxwidth, 'sanitize_callback' => 'absint', ), ), ), ) ); register_rest_route( 'oembed/1.0', '/proxy', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_proxy_item' ), 'permission_callback' => array( $this, 'get_proxy_item_permissions_check' ), 'args' => array( 'url' => array( 'description' => __( 'The URL of the resource for which to fetch oEmbed data.' ), 'required' => true, 'type' => 'string', 'format' => 'uri', ), 'format' => array( 'description' => __( 'The oEmbed format to use.' ), 'type' => 'string', 'default' => 'json', 'enum' => array( 'json', 'xml', ), ), 'maxwidth' => array( 'description' => __( 'The maximum width of the embed frame in pixels.' ), 'type' => 'integer', 'default' => $maxwidth, 'sanitize_callback' => 'absint', ), 'maxheight' => array( 'description' => __( 'The maximum height of the embed frame in pixels.' ), 'type' => 'integer', 'sanitize_callback' => 'absint', ), 'discover' => array( 'description' => __( 'Whether to perform an oEmbed discovery request for unsanctioned providers.' ), 'type' => 'boolean', 'default' => true, ), ), ), ) ); } /** * Callback for the embed API endpoint. * * Returns the JSON object for the post. * * @since 4.4.0 * * @param WP_REST_Request $request Full data about the request. * @return array|WP_Error oEmbed response data or WP_Error on failure. */ public function get_item( $request ) { $post_id = url_to_postid( $request['url'] ); /** * Filters the determined post ID. * * @since 4.4.0 * * @param int $post_id The post ID. * @param string $url The requested URL. */ $post_id = apply_filters( 'oembed_request_post_id', $post_id, $request['url'] ); $data = get_oembed_response_data( $post_id, $request['maxwidth'] ); if ( ! $data ) { return new WP_Error( 'oembed_invalid_url', get_status_header_desc( 404 ), array( 'status' => 404 ) ); } return $data; } /** * Checks if current user can make a proxy oEmbed request. * * @since 4.8.0 * * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_proxy_item_permissions_check() { if ( ! current_user_can( 'edit_posts' ) ) { return new WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to make proxied oEmbed requests.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Callback for the proxy API endpoint. * * Returns the JSON object for the proxied item. * * @since 4.8.0 * * @see WP_oEmbed::get_html() * @global WP_Embed $wp_embed WordPress Embed object. * @global WP_Scripts $wp_scripts * * @param WP_REST_Request $request Full data about the request. * @return object|WP_Error oEmbed response data or WP_Error on failure. */ public function get_proxy_item( $request ) { global $wp_embed, $wp_scripts; $args = $request->get_params(); // Serve oEmbed data from cache if set. unset( $args['_wpnonce'] ); $cache_key = 'oembed_' . md5( serialize( $args ) ); $data = get_transient( $cache_key ); if ( ! empty( $data ) ) { return $data; } $url = $request['url']; unset( $args['url'] ); // Copy maxwidth/maxheight to width/height since WP_oEmbed::fetch() uses these arg names. if ( isset( $args['maxwidth'] ) ) { $args['width'] = $args['maxwidth']; } if ( isset( $args['maxheight'] ) ) { $args['height'] = $args['maxheight']; } // Short-circuit process for URLs belonging to the current site. $data = get_oembed_response_data_for_url( $url, $args ); if ( $data ) { return $data; } $data = _wp_oembed_get_object()->get_data( $url, $args ); if ( false === $data ) { // Try using a classic embed, instead. /* @var WP_Embed $wp_embed */ $html = $wp_embed->get_embed_handler_html( $args, $url ); if ( $html ) { // Check if any scripts were enqueued by the shortcode, and include them in the response. $enqueued_scripts = array(); foreach ( $wp_scripts->queue as $script ) { $enqueued_scripts[] = $wp_scripts->registered[ $script ]->src; } return (object) array( 'provider_name' => __( 'Embed Handler' ), 'html' => $html, 'scripts' => $enqueued_scripts, ); } return new WP_Error( 'oembed_invalid_url', get_status_header_desc( 404 ), array( 'status' => 404 ) ); } /** This filter is documented in wp-includes/class-wp-oembed.php */ $data->html = apply_filters( 'oembed_result', _wp_oembed_get_object()->data2html( (object) $data, $url ), $url, $args ); /** * Filters the oEmbed TTL value (time to live). * * Similar to the {@see 'oembed_ttl'} filter, but for the REST API * oEmbed proxy endpoint. * * @since 4.8.0 * * @param int $time Time to live (in seconds). * @param string $url The attempted embed URL. * @param array $args An array of embed request arguments. */ $ttl = apply_filters( 'rest_oembed_ttl', DAY_IN_SECONDS, $url, $args ); set_transient( $cache_key, $data, $ttl ); return $data; } } https-detection-20260605081608.php 0000666 00000013341 15226762357 0012011 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260623210321.php 0000666 00000013341 15226762357 0011773 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } media.php 0000666 00000660077 15226762357 0006377 0 ustar 00 <?php /** * WordPress API for media display. * * @package WordPress * @subpackage Media */ // Don't load directly. if ( ! defined( 'ABSPATH' ) ) { die( '-1' ); } /** * Retrieves additional image sizes. * * @since 4.7.0 * * @global array $_wp_additional_image_sizes * * @return array Additional images size data. */ function wp_get_additional_image_sizes() { global $_wp_additional_image_sizes; if ( ! $_wp_additional_image_sizes ) { $_wp_additional_image_sizes = array(); } return $_wp_additional_image_sizes; } /** * Scales down the default size of an image. * * This is so that the image is a better fit for the editor and theme. * * The `$size` parameter accepts either an array or a string. The supported string * values are 'thumb' or 'thumbnail' for the given thumbnail size or defaults at * 128 width and 96 height in pixels. Also supported for the string value is * 'medium', 'medium_large' and 'full'. The 'full' isn't actually supported, but any value other * than the supported will result in the content_width size or 500 if that is * not set. * * Finally, there is a filter named {@see 'editor_max_image_size'}, that will be * called on the calculated array for width and height, respectively. * * @since 2.5.0 * * @global int $content_width * * @param int $width Width of the image in pixels. * @param int $height Height of the image in pixels. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'medium'. * @param string $context Optional. Could be 'display' (like in a theme) or 'edit' * (like inserting into an editor). Default null. * @return int[] { * An array of width and height values. * * @type int $0 The maximum width in pixels. * @type int $1 The maximum height in pixels. * } */ function image_constrain_size_for_editor( $width, $height, $size = 'medium', $context = null ) { global $content_width; $_wp_additional_image_sizes = wp_get_additional_image_sizes(); if ( ! $context ) { $context = is_admin() ? 'edit' : 'display'; } if ( is_array( $size ) ) { $max_width = $size[0]; $max_height = $size[1]; } elseif ( 'thumb' === $size || 'thumbnail' === $size ) { $max_width = (int) get_option( 'thumbnail_size_w' ); $max_height = (int) get_option( 'thumbnail_size_h' ); // Last chance thumbnail size defaults. if ( ! $max_width && ! $max_height ) { $max_width = 128; $max_height = 96; } } elseif ( 'medium' === $size ) { $max_width = (int) get_option( 'medium_size_w' ); $max_height = (int) get_option( 'medium_size_h' ); } elseif ( 'medium_large' === $size ) { $max_width = (int) get_option( 'medium_large_size_w' ); $max_height = (int) get_option( 'medium_large_size_h' ); if ( (int) $content_width > 0 ) { $max_width = min( (int) $content_width, $max_width ); } } elseif ( 'large' === $size ) { /* * We're inserting a large size image into the editor. If it's a really * big image we'll scale it down to fit reasonably within the editor * itself, and within the theme's content width if it's known. The user * can resize it in the editor if they wish. */ $max_width = (int) get_option( 'large_size_w' ); $max_height = (int) get_option( 'large_size_h' ); if ( (int) $content_width > 0 ) { $max_width = min( (int) $content_width, $max_width ); } } elseif ( ! empty( $_wp_additional_image_sizes ) && in_array( $size, array_keys( $_wp_additional_image_sizes ), true ) ) { $max_width = (int) $_wp_additional_image_sizes[ $size ]['width']; $max_height = (int) $_wp_additional_image_sizes[ $size ]['height']; // Only in admin. Assume that theme authors know what they're doing. if ( (int) $content_width > 0 && 'edit' === $context ) { $max_width = min( (int) $content_width, $max_width ); } } else { // $size === 'full' has no constraint. $max_width = $width; $max_height = $height; } /** * Filters the maximum image size dimensions for the editor. * * @since 2.5.0 * * @param int[] $max_image_size { * An array of width and height values. * * @type int $0 The maximum width in pixels. * @type int $1 The maximum height in pixels. * } * @param string|int[] $size Requested image size. Can be any registered image size name, or * an array of width and height values in pixels (in that order). * @param string $context The context the image is being resized for. * Possible values are 'display' (like in a theme) * or 'edit' (like inserting into an editor). */ list( $max_width, $max_height ) = apply_filters( 'editor_max_image_size', array( $max_width, $max_height ), $size, $context ); return wp_constrain_dimensions( $width, $height, $max_width, $max_height ); } /** * Retrieves width and height attributes using given width and height values. * * Both attributes are required in the sense that both parameters must have a * value, but are optional in that if you set them to false or null, then they * will not be added to the returned string. * * You can set the value using a string, but it will only take numeric values. * If you wish to put 'px' after the numbers, then it will be stripped out of * the return. * * @since 2.5.0 * * @param int|string $width Image width in pixels. * @param int|string $height Image height in pixels. * @return string HTML attributes for width and, or height. */ function image_hwstring( $width, $height ) { $out = ''; if ( $width ) { $out .= 'width="' . (int) $width . '" '; } if ( $height ) { $out .= 'height="' . (int) $height . '" '; } return $out; } /** * Scales an image to fit a particular size (such as 'thumb' or 'medium'). * * The URL might be the original image, or it might be a resized version. This * function won't create a new resized copy, it will just return an already * resized one if it exists. * * A plugin may use the {@see 'image_downsize'} filter to hook into and offer image * resizing services for images. The hook must return an array with the same * elements that are normally returned from the function. * * @since 2.5.0 * * @param int $id Attachment ID for image. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'medium'. * @return array|false { * Array of image data, or boolean false if no image is available. * * @type string $0 Image source URL. * @type int $1 Image width in pixels. * @type int $2 Image height in pixels. * @type bool $3 Whether the image is a resized image. * } */ function image_downsize( $id, $size = 'medium' ) { $is_image = wp_attachment_is_image( $id ); /** * Filters whether to preempt the output of image_downsize(). * * Returning a truthy value from the filter will effectively short-circuit * down-sizing the image, returning that value instead. * * @since 2.5.0 * * @param bool|array $downsize Whether to short-circuit the image downsize. * @param int $id Attachment ID for image. * @param string|int[] $size Requested image size. Can be any registered image size name, or * an array of width and height values in pixels (in that order). */ $out = apply_filters( 'image_downsize', false, $id, $size ); if ( $out ) { return $out; } $img_url = wp_get_attachment_url( $id ); $meta = wp_get_attachment_metadata( $id ); $width = 0; $height = 0; $is_intermediate = false; $img_url_basename = wp_basename( $img_url ); /* * If the file isn't an image, attempt to replace its URL with a rendered image from its meta. * Otherwise, a non-image type could be returned. */ if ( ! $is_image ) { if ( ! empty( $meta['sizes']['full'] ) ) { $img_url = str_replace( $img_url_basename, $meta['sizes']['full']['file'], $img_url ); $img_url_basename = $meta['sizes']['full']['file']; $width = $meta['sizes']['full']['width']; $height = $meta['sizes']['full']['height']; } else { return false; } } // Try for a new style intermediate size. $intermediate = image_get_intermediate_size( $id, $size ); if ( $intermediate ) { $img_url = str_replace( $img_url_basename, $intermediate['file'], $img_url ); $width = $intermediate['width']; $height = $intermediate['height']; $is_intermediate = true; } elseif ( 'thumbnail' === $size && ! empty( $meta['thumb'] ) && is_string( $meta['thumb'] ) ) { // Fall back to the old thumbnail. $imagefile = get_attached_file( $id ); $thumbfile = str_replace( wp_basename( $imagefile ), wp_basename( $meta['thumb'] ), $imagefile ); if ( file_exists( $thumbfile ) ) { $info = wp_getimagesize( $thumbfile ); if ( $info ) { $img_url = str_replace( $img_url_basename, wp_basename( $thumbfile ), $img_url ); $width = $info[0]; $height = $info[1]; $is_intermediate = true; } } } if ( ! $width && ! $height && isset( $meta['width'], $meta['height'] ) ) { // Any other type: use the real image. $width = $meta['width']; $height = $meta['height']; } if ( $img_url ) { // We have the actual image size, but might need to further constrain it if content_width is narrower. list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size ); return array( $img_url, $width, $height, $is_intermediate ); } return false; } /** * Registers a new image size. * * @since 2.9.0 * * @global array $_wp_additional_image_sizes Associative array of additional image sizes. * * @param string $name Image size identifier. * @param int $width Optional. Image width in pixels. Default 0. * @param int $height Optional. Image height in pixels. Default 0. * @param bool|array $crop { * Optional. Image cropping behavior. If false, the image will be scaled (default). * If true, image will be cropped to the specified dimensions using center positions. * If an array, the image will be cropped using the array to specify the crop location: * * @type string $0 The x crop position. Accepts 'left', 'center', or 'right'. * @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'. * } */ function add_image_size( $name, $width = 0, $height = 0, $crop = false ) { global $_wp_additional_image_sizes; $_wp_additional_image_sizes[ $name ] = array( 'width' => absint( $width ), 'height' => absint( $height ), 'crop' => $crop, ); } /** * Checks if an image size exists. * * @since 3.9.0 * * @param string $name The image size to check. * @return bool True if the image size exists, false if not. */ function has_image_size( $name ) { $sizes = wp_get_additional_image_sizes(); return isset( $sizes[ $name ] ); } /** * Removes a new image size. * * @since 3.9.0 * * @global array $_wp_additional_image_sizes * * @param string $name The image size to remove. * @return bool True if the image size was successfully removed, false on failure. */ function remove_image_size( $name ) { global $_wp_additional_image_sizes; if ( isset( $_wp_additional_image_sizes[ $name ] ) ) { unset( $_wp_additional_image_sizes[ $name ] ); return true; } return false; } /** * Registers an image size for the post thumbnail. * * @since 2.9.0 * * @see add_image_size() for details on cropping behavior. * * @param int $width Image width in pixels. * @param int $height Image height in pixels. * @param bool|array $crop { * Optional. Image cropping behavior. If false, the image will be scaled (default). * If true, image will be cropped to the specified dimensions using center positions. * If an array, the image will be cropped using the array to specify the crop location: * * @type string $0 The x crop position. Accepts 'left', 'center', or 'right'. * @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'. * } */ function set_post_thumbnail_size( $width = 0, $height = 0, $crop = false ) { add_image_size( 'post-thumbnail', $width, $height, $crop ); } /** * Gets an img tag for an image attachment, scaling it down if requested. * * The {@see 'get_image_tag_class'} filter allows for changing the class name for the * image without having to use regular expressions on the HTML content. The * parameters are: what WordPress will use for the class, the Attachment ID, * image align value, and the size the image should be. * * The second filter, {@see 'get_image_tag'}, has the HTML content, which can then be * further manipulated by a plugin to change all attribute values and even HTML * content. * * @since 2.5.0 * * @param int $id Attachment ID. * @param string $alt Image description for the alt attribute. * @param string $title Image description for the title attribute. * @param string $align Part of the class name for aligning the image. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of * width and height values in pixels (in that order). Default 'medium'. * @return string HTML IMG element for given image attachment. */ function get_image_tag( $id, $alt, $title, $align, $size = 'medium' ) { list( $img_src, $width, $height ) = image_downsize( $id, $size ); $hwstring = image_hwstring( $width, $height ); $title = $title ? 'title="' . esc_attr( $title ) . '" ' : ''; $size_class = is_array( $size ) ? implode( 'x', $size ) : $size; $class = 'align' . esc_attr( $align ) . ' size-' . esc_attr( $size_class ) . ' wp-image-' . $id; /** * Filters the value of the attachment's image tag class attribute. * * @since 2.6.0 * * @param string $class CSS class name or space-separated list of classes. * @param int $id Attachment ID. * @param string $align Part of the class name for aligning the image. * @param string|int[] $size Requested image size. Can be any registered image size name, or * an array of width and height values in pixels (in that order). */ $class = apply_filters( 'get_image_tag_class', $class, $id, $align, $size ); $html = '<img src="' . esc_url( $img_src ) . '" alt="' . esc_attr( $alt ) . '" ' . $title . $hwstring . 'class="' . $class . '" />'; /** * Filters the HTML content for the image tag. * * @since 2.6.0 * * @param string $html HTML content for the image. * @param int $id Attachment ID. * @param string $alt Image description for the alt attribute. * @param string $title Image description for the title attribute. * @param string $align Part of the class name for aligning the image. * @param string|int[] $size Requested image size. Can be any registered image size name, or * an array of width and height values in pixels (in that order). */ return apply_filters( 'get_image_tag', $html, $id, $alt, $title, $align, $size ); } /** * Calculates the new dimensions for a down-sampled image. * * If either width or height are empty, no constraint is applied on * that dimension. * * @since 2.5.0 * * @param int $current_width Current width of the image. * @param int $current_height Current height of the image. * @param int $max_width Optional. Max width in pixels to constrain to. Default 0. * @param int $max_height Optional. Max height in pixels to constrain to. Default 0. * @return int[] { * An array of width and height values. * * @type int $0 The width in pixels. * @type int $1 The height in pixels. * } */ function wp_constrain_dimensions( $current_width, $current_height, $max_width = 0, $max_height = 0 ) { if ( ! $max_width && ! $max_height ) { return array( $current_width, $current_height ); } $width_ratio = 1.0; $height_ratio = 1.0; $did_width = false; $did_height = false; if ( $max_width > 0 && $current_width > 0 && $current_width > $max_width ) { $width_ratio = $max_width / $current_width; $did_width = true; } if ( $max_height > 0 && $current_height > 0 && $current_height > $max_height ) { $height_ratio = $max_height / $current_height; $did_height = true; } // Calculate the larger/smaller ratios. $smaller_ratio = min( $width_ratio, $height_ratio ); $larger_ratio = max( $width_ratio, $height_ratio ); if ( (int) round( $current_width * $larger_ratio ) > $max_width || (int) round( $current_height * $larger_ratio ) > $max_height ) { // The larger ratio is too big. It would result in an overflow. $ratio = $smaller_ratio; } else { // The larger ratio fits, and is likely to be a more "snug" fit. $ratio = $larger_ratio; } // Very small dimensions may result in 0, 1 should be the minimum. $w = max( 1, (int) round( $current_width * $ratio ) ); $h = max( 1, (int) round( $current_height * $ratio ) ); /* * Sometimes, due to rounding, we'll end up with a result like this: * 465x700 in a 177x177 box is 117x176... a pixel short. * We also have issues with recursive calls resulting in an ever-changing result. * Constraining to the result of a constraint should yield the original result. * Thus we look for dimensions that are one pixel shy of the max value and bump them up. */ // Note: $did_width means it is possible $smaller_ratio == $width_ratio. if ( $did_width && $w === $max_width - 1 ) { $w = $max_width; // Round it up. } // Note: $did_height means it is possible $smaller_ratio == $height_ratio. if ( $did_height && $h === $max_height - 1 ) { $h = $max_height; // Round it up. } /** * Filters dimensions to constrain down-sampled images to. * * @since 4.1.0 * * @param int[] $dimensions { * An array of width and height values. * * @type int $0 The width in pixels. * @type int $1 The height in pixels. * } * @param int $current_width The current width of the image. * @param int $current_height The current height of the image. * @param int $max_width The maximum width permitted. * @param int $max_height The maximum height permitted. */ return apply_filters( 'wp_constrain_dimensions', array( $w, $h ), $current_width, $current_height, $max_width, $max_height ); } /** * Retrieves calculated resize dimensions for use in WP_Image_Editor. * * Calculates dimensions and coordinates for a resized image that fits * within a specified width and height. * * @since 2.5.0 * * @param int $orig_w Original width in pixels. * @param int $orig_h Original height in pixels. * @param int $dest_w New width in pixels. * @param int $dest_h New height in pixels. * @param bool|array $crop { * Optional. Image cropping behavior. If false, the image will be scaled (default). * If true, image will be cropped to the specified dimensions using center positions. * If an array, the image will be cropped using the array to specify the crop location: * * @type string $0 The x crop position. Accepts 'left', 'center', or 'right'. * @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'. * } * @return array|false Returned array matches parameters for `imagecopyresampled()`. False on failure. */ function image_resize_dimensions( $orig_w, $orig_h, $dest_w, $dest_h, $crop = false ) { if ( $orig_w <= 0 || $orig_h <= 0 ) { return false; } // At least one of $dest_w or $dest_h must be specific. if ( $dest_w <= 0 && $dest_h <= 0 ) { return false; } /** * Filters whether to preempt calculating the image resize dimensions. * * Returning a non-null value from the filter will effectively short-circuit * image_resize_dimensions(), returning that value instead. * * @since 3.4.0 * * @param null|mixed $null Whether to preempt output of the resize dimensions. * @param int $orig_w Original width in pixels. * @param int $orig_h Original height in pixels. * @param int $dest_w New width in pixels. * @param int $dest_h New height in pixels. * @param bool|array $crop Whether to crop image to specified width and height or resize. * An array can specify positioning of the crop area. Default false. */ $output = apply_filters( 'image_resize_dimensions', null, $orig_w, $orig_h, $dest_w, $dest_h, $crop ); if ( null !== $output ) { return $output; } // Stop if the destination size is larger than the original image dimensions. if ( empty( $dest_h ) ) { if ( $orig_w < $dest_w ) { return false; } } elseif ( empty( $dest_w ) ) { if ( $orig_h < $dest_h ) { return false; } } else { if ( $orig_w < $dest_w && $orig_h < $dest_h ) { return false; } } if ( $crop ) { /* * Crop the largest possible portion of the original image that we can size to $dest_w x $dest_h. * Note that the requested crop dimensions are used as a maximum bounding box for the original image. * If the original image's width or height is less than the requested width or height * only the greater one will be cropped. * For example when the original image is 600x300, and the requested crop dimensions are 400x400, * the resulting image will be 400x300. */ $aspect_ratio = $orig_w / $orig_h; $new_w = min( $dest_w, $orig_w ); $new_h = min( $dest_h, $orig_h ); if ( ! $new_w ) { $new_w = (int) round( $new_h * $aspect_ratio ); } if ( ! $new_h ) { $new_h = (int) round( $new_w / $aspect_ratio ); } $size_ratio = max( $new_w / $orig_w, $new_h / $orig_h ); $crop_w = round( $new_w / $size_ratio ); $crop_h = round( $new_h / $size_ratio ); if ( ! is_array( $crop ) || count( $crop ) !== 2 ) { $crop = array( 'center', 'center' ); } list( $x, $y ) = $crop; if ( 'left' === $x ) { $s_x = 0; } elseif ( 'right' === $x ) { $s_x = $orig_w - $crop_w; } else { $s_x = floor( ( $orig_w - $crop_w ) / 2 ); } if ( 'top' === $y ) { $s_y = 0; } elseif ( 'bottom' === $y ) { $s_y = $orig_h - $crop_h; } else { $s_y = floor( ( $orig_h - $crop_h ) / 2 ); } } else { // Resize using $dest_w x $dest_h as a maximum bounding box. $crop_w = $orig_w; $crop_h = $orig_h; $s_x = 0; $s_y = 0; list( $new_w, $new_h ) = wp_constrain_dimensions( $orig_w, $orig_h, $dest_w, $dest_h ); } if ( wp_fuzzy_number_match( $new_w, $orig_w ) && wp_fuzzy_number_match( $new_h, $orig_h ) ) { // The new size has virtually the same dimensions as the original image. /** * Filters whether to proceed with making an image sub-size with identical dimensions * with the original/source image. Differences of 1px may be due to rounding and are ignored. * * @since 5.3.0 * * @param bool $proceed The filtered value. * @param int $orig_w Original image width. * @param int $orig_h Original image height. */ $proceed = (bool) apply_filters( 'wp_image_resize_identical_dimensions', false, $orig_w, $orig_h ); if ( ! $proceed ) { return false; } } /* * The return array matches the parameters to imagecopyresampled(). * int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h */ return array( 0, 0, (int) $s_x, (int) $s_y, (int) $new_w, (int) $new_h, (int) $crop_w, (int) $crop_h ); } /** * Resizes an image to make a thumbnail or intermediate size. * * The returned array has the file size, the image width, and image height. The * {@see 'image_make_intermediate_size'} filter can be used to hook in and change the * values of the returned array. The only parameter is the resized file path. * * @since 2.5.0 * * @param string $file File path. * @param int $width Image width. * @param int $height Image height. * @param bool|array $crop { * Optional. Image cropping behavior. If false, the image will be scaled (default). * If true, image will be cropped to the specified dimensions using center positions. * If an array, the image will be cropped using the array to specify the crop location: * * @type string $0 The x crop position. Accepts 'left', 'center', or 'right'. * @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'. * } * @return array|false Metadata array on success. False if no image was created. */ function image_make_intermediate_size( $file, $width, $height, $crop = false ) { if ( $width || $height ) { $editor = wp_get_image_editor( $file ); if ( is_wp_error( $editor ) || is_wp_error( $editor->resize( $width, $height, $crop ) ) ) { return false; } $resized_file = $editor->save(); if ( ! is_wp_error( $resized_file ) && $resized_file ) { unset( $resized_file['path'] ); return $resized_file; } } return false; } /** * Helper function to test if aspect ratios for two images match. * * @since 4.6.0 * * @param int $source_width Width of the first image in pixels. * @param int $source_height Height of the first image in pixels. * @param int $target_width Width of the second image in pixels. * @param int $target_height Height of the second image in pixels. * @return bool True if aspect ratios match within 1px. False if not. */ function wp_image_matches_ratio( $source_width, $source_height, $target_width, $target_height ) { /* * To test for varying crops, we constrain the dimensions of the larger image * to the dimensions of the smaller image and see if they match. */ if ( $source_width > $target_width ) { $constrained_size = wp_constrain_dimensions( $source_width, $source_height, $target_width ); $expected_size = array( $target_width, $target_height ); } else { $constrained_size = wp_constrain_dimensions( $target_width, $target_height, $source_width ); $expected_size = array( $source_width, $source_height ); } // If the image dimensions are within 1px of the expected size, we consider it a match. $matched = ( wp_fuzzy_number_match( $constrained_size[0], $expected_size[0] ) && wp_fuzzy_number_match( $constrained_size[1], $expected_size[1] ) ); return $matched; } /** * Retrieves the image's intermediate size (resized) path, width, and height. * * The $size parameter can be an array with the width and height respectively. * If the size matches the 'sizes' metadata array for width and height, then it * will be used. If there is no direct match, then the nearest image size larger * than the specified size will be used. If nothing is found, then the function * will break out and return false. * * The metadata 'sizes' is used for compatible sizes that can be used for the * parameter $size value. * * The url path will be given, when the $size parameter is a string. * * If you are passing an array for the $size, you should consider using * add_image_size() so that a cropped version is generated. It's much more * efficient than having to find the closest-sized image and then having the * browser scale down the image. * * @since 2.5.0 * * @param int $post_id Attachment ID. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'thumbnail'. * @return array|false { * Array of file relative path, width, and height on success. Additionally includes absolute * path and URL if registered size is passed to `$size` parameter. False on failure. * * @type string $file Filename of image. * @type int $width Width of image in pixels. * @type int $height Height of image in pixels. * @type string $path Path of image relative to uploads directory. * @type string $url URL of image. * } */ function image_get_intermediate_size( $post_id, $size = 'thumbnail' ) { $imagedata = wp_get_attachment_metadata( $post_id ); if ( ! $size || ! is_array( $imagedata ) || empty( $imagedata['sizes'] ) ) { return false; } $data = array(); // Find the best match when '$size' is an array. if ( is_array( $size ) ) { $candidates = array(); if ( ! isset( $imagedata['file'] ) && isset( $imagedata['sizes']['full'] ) ) { $imagedata['height'] = $imagedata['sizes']['full']['height']; $imagedata['width'] = $imagedata['sizes']['full']['width']; } foreach ( $imagedata['sizes'] as $_size => $data ) { // If there's an exact match to an existing image size, short circuit. if ( (int) $data['width'] === (int) $size[0] && (int) $data['height'] === (int) $size[1] ) { $candidates[ $data['width'] * $data['height'] ] = $data; break; } // If it's not an exact match, consider larger sizes with the same aspect ratio. if ( $data['width'] >= $size[0] && $data['height'] >= $size[1] ) { // If '0' is passed to either size, we test ratios against the original file. if ( 0 === $size[0] || 0 === $size[1] ) { $same_ratio = wp_image_matches_ratio( $data['width'], $data['height'], $imagedata['width'], $imagedata['height'] ); } else { $same_ratio = wp_image_matches_ratio( $data['width'], $data['height'], $size[0], $size[1] ); } if ( $same_ratio ) { $candidates[ $data['width'] * $data['height'] ] = $data; } } } if ( ! empty( $candidates ) ) { // Sort the array by size if we have more than one candidate. if ( 1 < count( $candidates ) ) { ksort( $candidates ); } $data = array_shift( $candidates ); } elseif ( ! empty( $imagedata['sizes']['thumbnail'] ) && $size[0] <= $imagedata['sizes']['thumbnail']['width'] && $size[1] <= $imagedata['sizes']['thumbnail']['width'] ) { /* * When the size requested is smaller than the thumbnail dimensions, we * fall back to the thumbnail size to maintain backward compatibility with * pre-4.6 versions of WordPress. */ $data = $imagedata['sizes']['thumbnail']; } else { return false; } // Constrain the width and height attributes to the requested values. list( $data['width'], $data['height'] ) = image_constrain_size_for_editor( $data['width'], $data['height'], $size ); } elseif ( ! empty( $imagedata['sizes'][ $size ] ) ) { $data = $imagedata['sizes'][ $size ]; } // If we still don't have a match at this point, return false. if ( empty( $data ) ) { return false; } // Include the full filesystem path of the intermediate file. if ( empty( $data['path'] ) && ! empty( $data['file'] ) && ! empty( $imagedata['file'] ) ) { $file_url = wp_get_attachment_url( $post_id ); $data['path'] = path_join( dirname( $imagedata['file'] ), $data['file'] ); $data['url'] = path_join( dirname( $file_url ), $data['file'] ); } /** * Filters the output of image_get_intermediate_size() * * @since 4.4.0 * * @see image_get_intermediate_size() * * @param array $data Array of file relative path, width, and height on success. May also include * file absolute path and URL. * @param int $post_id The ID of the image attachment. * @param string|int[] $size Requested image size. Can be any registered image size name, or * an array of width and height values in pixels (in that order). */ return apply_filters( 'image_get_intermediate_size', $data, $post_id, $size ); } /** * Gets the available intermediate image size names. * * @since 3.0.0 * * @return string[] An array of image size names. */ function get_intermediate_image_sizes() { $default_sizes = array( 'thumbnail', 'medium', 'medium_large', 'large' ); $additional_sizes = wp_get_additional_image_sizes(); if ( ! empty( $additional_sizes ) ) { $default_sizes = array_merge( $default_sizes, array_keys( $additional_sizes ) ); } /** * Filters the list of intermediate image sizes. * * @since 2.5.0 * * @param string[] $default_sizes An array of intermediate image size names. Defaults * are 'thumbnail', 'medium', 'medium_large', 'large'. */ return apply_filters( 'intermediate_image_sizes', $default_sizes ); } /** * Returns a normalized list of all currently registered image sub-sizes. * * @since 5.3.0 * @uses wp_get_additional_image_sizes() * @uses get_intermediate_image_sizes() * * @return array[] Associative array of arrays of image sub-size information, * keyed by image size name. */ function wp_get_registered_image_subsizes() { $additional_sizes = wp_get_additional_image_sizes(); $all_sizes = array(); foreach ( get_intermediate_image_sizes() as $size_name ) { $size_data = array( 'width' => 0, 'height' => 0, 'crop' => false, ); if ( isset( $additional_sizes[ $size_name ]['width'] ) ) { // For sizes added by plugins and themes. $size_data['width'] = (int) $additional_sizes[ $size_name ]['width']; } else { // For default sizes set in options. $size_data['width'] = (int) get_option( "{$size_name}_size_w" ); } if ( isset( $additional_sizes[ $size_name ]['height'] ) ) { $size_data['height'] = (int) $additional_sizes[ $size_name ]['height']; } else { $size_data['height'] = (int) get_option( "{$size_name}_size_h" ); } if ( empty( $size_data['width'] ) && empty( $size_data['height'] ) ) { // This size isn't set. continue; } if ( isset( $additional_sizes[ $size_name ]['crop'] ) ) { $size_data['crop'] = $additional_sizes[ $size_name ]['crop']; } else { $size_data['crop'] = get_option( "{$size_name}_crop" ); } if ( ! is_array( $size_data['crop'] ) || empty( $size_data['crop'] ) ) { $size_data['crop'] = (bool) $size_data['crop']; } $all_sizes[ $size_name ] = $size_data; } return $all_sizes; } /** * Retrieves an image to represent an attachment. * * @since 2.5.0 * * @param int $attachment_id Image attachment ID. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of * width and height values in pixels (in that order). Default 'thumbnail'. * @param bool $icon Optional. Whether the image should fall back to a mime type icon. Default false. * @return array|false { * Array of image data, or boolean false if no image is available. * * @type string $0 Image source URL. * @type int $1 Image width in pixels. * @type int $2 Image height in pixels. * @type bool $3 Whether the image is a resized image. * } */ function wp_get_attachment_image_src( $attachment_id, $size = 'thumbnail', $icon = false ) { // Get a thumbnail or intermediate image if there is one. $image = image_downsize( $attachment_id, $size ); if ( ! $image ) { $src = false; if ( $icon ) { $src = wp_mime_type_icon( $attachment_id, '.svg' ); if ( $src ) { /** This filter is documented in wp-includes/post.php */ $icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' ); $src_file = $icon_dir . '/' . wp_basename( $src ); list( $width, $height ) = wp_getimagesize( $src_file ); $ext = strtolower( substr( $src_file, -4 ) ); if ( '.svg' === $ext ) { // SVG does not have true dimensions, so this assigns width and height directly. $width = 48; $height = 64; } else { list( $width, $height ) = wp_getimagesize( $src_file ); } } } if ( $src && $width && $height ) { $image = array( $src, $width, $height, false ); } } /** * Filters the attachment image source result. * * @since 4.3.0 * * @param array|false $image { * Array of image data, or boolean false if no image is available. * * @type string $0 Image source URL. * @type int $1 Image width in pixels. * @type int $2 Image height in pixels. * @type bool $3 Whether the image is a resized image. * } * @param int $attachment_id Image attachment ID. * @param string|int[] $size Requested image size. Can be any registered image size name, or * an array of width and height values in pixels (in that order). * @param bool $icon Whether the image should be treated as an icon. */ return apply_filters( 'wp_get_attachment_image_src', $image, $attachment_id, $size, $icon ); } /** * Gets an HTML img element representing an image attachment. * * While `$size` will accept an array, it is better to register a size with * add_image_size() so that a cropped version is generated. It's much more * efficient than having to find the closest-sized image and then having the * browser scale down the image. * * @since 2.5.0 * @since 4.4.0 The `$srcset` and `$sizes` attributes were added. * @since 5.5.0 The `$loading` attribute was added. * @since 6.1.0 The `$decoding` attribute was added. * * @param int $attachment_id Image attachment ID. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'thumbnail'. * @param bool $icon Optional. Whether the image should be treated as an icon. Default false. * @param string|array $attr { * Optional. Attributes for the image markup. * * @type string $src Image attachment URL. * @type string $class CSS class name or space-separated list of classes. * Default `attachment-$size_class size-$size_class`, * where `$size_class` is the image size being requested. * @type string $alt Image description for the alt attribute. * @type string $srcset The 'srcset' attribute value. * @type string $sizes The 'sizes' attribute value. * @type string|false $loading The 'loading' attribute value. Passing a value of false * will result in the attribute being omitted for the image. * Default determined by {@see wp_get_loading_optimization_attributes()}. * @type string $decoding The 'decoding' attribute value. Possible values are * 'async' (default), 'sync', or 'auto'. Passing false or an empty * string will result in the attribute being omitted. * @type string $fetchpriority The 'fetchpriority' attribute value, whether `high`, `low`, or `auto`. * Default determined by {@see wp_get_loading_optimization_attributes()}. * } * @return string HTML img element or empty string on failure. */ function wp_get_attachment_image( $attachment_id, $size = 'thumbnail', $icon = false, $attr = '' ) { $html = ''; $image = wp_get_attachment_image_src( $attachment_id, $size, $icon ); if ( $image ) { list( $src, $width, $height ) = $image; $attachment = get_post( $attachment_id ); $size_class = $size; if ( is_array( $size_class ) ) { $size_class = implode( 'x', $size_class ); } $default_attr = array( 'src' => $src, 'class' => "attachment-$size_class size-$size_class", 'alt' => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ), ); /** * Filters the context in which wp_get_attachment_image() is used. * * @since 6.3.0 * * @param string $context The context. Default 'wp_get_attachment_image'. */ $context = apply_filters( 'wp_get_attachment_image_context', 'wp_get_attachment_image' ); $attr = wp_parse_args( $attr, $default_attr ); // Ensure that the `$width` doesn't overwrite an already valid user-provided width. if ( ! isset( $attr['width'] ) || ! is_numeric( $attr['width'] ) ) { $attr['width'] = $width; } // Ensure that the `$height` doesn't overwrite an already valid user-provided height. if ( ! isset( $attr['height'] ) || ! is_numeric( $attr['height'] ) ) { $attr['height'] = $height; } $loading_optimization_attr = wp_get_loading_optimization_attributes( 'img', $attr, $context ); // Add loading optimization attributes if not available. $attr = array_merge( $attr, $loading_optimization_attr ); // Omit the `decoding` attribute if the value is invalid according to the spec. if ( empty( $attr['decoding'] ) || ! in_array( $attr['decoding'], array( 'async', 'sync', 'auto' ), true ) ) { unset( $attr['decoding'] ); } /* * If the default value of `lazy` for the `loading` attribute is overridden * to omit the attribute for this image, ensure it is not included. */ if ( isset( $attr['loading'] ) && ! $attr['loading'] ) { unset( $attr['loading'] ); } // If the `fetchpriority` attribute is overridden and set to false or an empty string. if ( isset( $attr['fetchpriority'] ) && ! $attr['fetchpriority'] ) { unset( $attr['fetchpriority'] ); } // Generate 'srcset' and 'sizes' if not already present. if ( empty( $attr['srcset'] ) ) { $image_meta = wp_get_attachment_metadata( $attachment_id ); if ( is_array( $image_meta ) ) { $size_array = array( absint( $width ), absint( $height ) ); $srcset = wp_calculate_image_srcset( $size_array, $src, $image_meta, $attachment_id ); $sizes = wp_calculate_image_sizes( $size_array, $src, $image_meta, $attachment_id ); if ( $srcset && ( $sizes || ! empty( $attr['sizes'] ) ) ) { $attr['srcset'] = $srcset; if ( empty( $attr['sizes'] ) ) { $attr['sizes'] = $sizes; } } } } /** This filter is documented in wp-includes/media.php */ $add_auto_sizes = apply_filters( 'wp_img_tag_add_auto_sizes', true ); // Adds 'auto' to the sizes attribute if applicable. if ( $add_auto_sizes && isset( $attr['loading'] ) && 'lazy' === $attr['loading'] && isset( $attr['sizes'] ) && ! wp_sizes_attribute_includes_valid_auto( $attr['sizes'] ) ) { $attr['sizes'] = 'auto, ' . $attr['sizes']; } /** * Filters the list of attachment image attributes. * * @since 2.8.0 * @since 6.8.2 The `$attr` array includes `width` and `height` attributes. * * @param string[] $attr Array of attribute values for the image markup, keyed by attribute name. * See wp_get_attachment_image(). * @param WP_Post $attachment Image attachment post. * @param string|int[] $size Requested image size. Can be any registered image size name, or * an array of width and height values in pixels (in that order). */ $attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $attachment, $size ); if ( isset( $attr['width'] ) && is_numeric( $attr['width'] ) ) { $width = absint( $attr['width'] ); } if ( isset( $attr['height'] ) && is_numeric( $attr['height'] ) ) { $height = absint( $attr['height'] ); } unset( $attr['width'], $attr['height'] ); $attr = array_map( 'esc_attr', $attr ); $hwstring = image_hwstring( $width, $height ); $html = rtrim( "<img $hwstring" ); foreach ( $attr as $name => $value ) { $html .= " $name=" . '"' . $value . '"'; } $html .= ' />'; } /** * Filters the HTML img element representing an image attachment. * * @since 5.6.0 * * @param string $html HTML img element or empty string on failure. * @param int $attachment_id Image attachment ID. * @param string|int[] $size Requested image size. Can be any registered image size name, or * an array of width and height values in pixels (in that order). * @param bool $icon Whether the image should be treated as an icon. * @param string[] $attr Array of attribute values for the image markup, keyed by attribute name. * See wp_get_attachment_image(). */ return apply_filters( 'wp_get_attachment_image', $html, $attachment_id, $size, $icon, $attr ); } /** * Gets the URL of an image attachment. * * @since 4.4.0 * * @param int $attachment_id Image attachment ID. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of * width and height values in pixels (in that order). Default 'thumbnail'. * @param bool $icon Optional. Whether the image should be treated as an icon. Default false. * @return string|false Attachment URL or false if no image is available. If `$size` does not match * any registered image size, the original image URL will be returned. */ function wp_get_attachment_image_url( $attachment_id, $size = 'thumbnail', $icon = false ) { $image = wp_get_attachment_image_src( $attachment_id, $size, $icon ); return isset( $image[0] ) ? $image[0] : false; } /** * Gets the attachment path relative to the upload directory. * * @since 4.4.1 * @access private * * @param string $file Attachment file name. * @return string Attachment path relative to the upload directory. */ function _wp_get_attachment_relative_path( $file ) { $dirname = dirname( $file ); if ( '.' === $dirname ) { return ''; } if ( str_contains( $dirname, 'wp-content/uploads' ) ) { // Get the directory name relative to the upload directory (back compat for pre-2.7 uploads). $dirname = substr( $dirname, strpos( $dirname, 'wp-content/uploads' ) + 18 ); $dirname = ltrim( $dirname, '/' ); } return $dirname; } /** * Gets the image size as array from its meta data. * * Used for responsive images. * * @since 4.4.0 * @access private * * @param string $size_name Image size. Accepts any registered image size name. * @param array $image_meta The image meta data. * @return array|false { * Array of width and height or false if the size isn't present in the meta data. * * @type int $0 Image width. * @type int $1 Image height. * } */ function _wp_get_image_size_from_meta( $size_name, $image_meta ) { if ( 'full' === $size_name ) { return array( absint( $image_meta['width'] ), absint( $image_meta['height'] ), ); } elseif ( ! empty( $image_meta['sizes'][ $size_name ] ) ) { return array( absint( $image_meta['sizes'][ $size_name ]['width'] ), absint( $image_meta['sizes'][ $size_name ]['height'] ), ); } return false; } /** * Retrieves the value for an image attachment's 'srcset' attribute. * * @since 4.4.0 * * @see wp_calculate_image_srcset() * * @param int $attachment_id Image attachment ID. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of * width and height values in pixels (in that order). Default 'medium'. * @param array|null $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'. * Default null. * @return string|false A 'srcset' value string or false. */ function wp_get_attachment_image_srcset( $attachment_id, $size = 'medium', $image_meta = null ) { $image = wp_get_attachment_image_src( $attachment_id, $size ); if ( ! $image ) { return false; } if ( ! is_array( $image_meta ) ) { $image_meta = wp_get_attachment_metadata( $attachment_id ); } $image_src = $image[0]; $size_array = array( absint( $image[1] ), absint( $image[2] ), ); return wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id ); } /** * A helper function to calculate the image sources to include in a 'srcset' attribute. * * @since 4.4.0 * * @param int[] $size_array { * An array of width and height values. * * @type int $0 The width in pixels. * @type int $1 The height in pixels. * } * @param string $image_src The 'src' of the image. * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'. * @param int $attachment_id Optional. The image attachment ID. Default 0. * @return string|false The 'srcset' attribute value. False on error or when only one source exists. */ function wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id = 0 ) { /** * Pre-filters the image meta to be able to fix inconsistencies in the stored data. * * @since 4.5.0 * * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'. * @param int[] $size_array { * An array of requested width and height values. * * @type int $0 The width in pixels. * @type int $1 The height in pixels. * } * @param string $image_src The 'src' of the image. * @param int $attachment_id The image attachment ID or 0 if not supplied. */ $image_meta = apply_filters( 'wp_calculate_image_srcset_meta', $image_meta, $size_array, $image_src, $attachment_id ); if ( empty( $image_meta['sizes'] ) || ! isset( $image_meta['file'] ) || strlen( $image_meta['file'] ) < 4 ) { return false; } $image_sizes = $image_meta['sizes']; // Get the width and height of the image. $image_width = (int) $size_array[0]; $image_height = (int) $size_array[1]; // Bail early if error/no width. if ( $image_width < 1 ) { return false; } $image_basename = wp_basename( $image_meta['file'] ); /* * WordPress flattens animated GIFs into one frame when generating intermediate sizes. * To avoid hiding animation in user content, if src is a full size GIF, a srcset attribute is not generated. * If src is an intermediate size GIF, the full size is excluded from srcset to keep a flattened GIF from becoming animated. */ if ( ! isset( $image_sizes['thumbnail']['mime-type'] ) || 'image/gif' !== $image_sizes['thumbnail']['mime-type'] ) { $image_sizes[] = array( 'width' => $image_meta['width'], 'height' => $image_meta['height'], 'file' => $image_basename, ); } elseif ( str_contains( $image_src, $image_meta['file'] ) ) { return false; } // Retrieve the uploads sub-directory from the full size image. $dirname = _wp_get_attachment_relative_path( $image_meta['file'] ); if ( $dirname ) { $dirname = trailingslashit( $dirname ); } $upload_dir = wp_get_upload_dir(); $image_baseurl = trailingslashit( $upload_dir['baseurl'] ) . $dirname; /* * If currently on HTTPS, prefer HTTPS URLs when we know they're supported by the domain * (which is to say, when they share the domain name of the current request). */ if ( is_ssl() && ! str_starts_with( $image_baseurl, 'https' ) ) { /* * Since the `Host:` header might contain a port, it should * be compared against the image URL using the same port. */ $parsed = parse_url( $image_baseurl ); $domain = isset( $parsed['host'] ) ? $parsed['host'] : ''; if ( isset( $parsed['port'] ) ) { $domain .= ':' . $parsed['port']; } if ( $_SERVER['HTTP_HOST'] === $domain ) { $image_baseurl = set_url_scheme( $image_baseurl, 'https' ); } } /* * Images that have been edited in WordPress after being uploaded will * contain a unique hash. Look for that hash and use it later to filter * out images that are leftovers from previous versions. */ $image_edited = preg_match( '/-e[0-9]{13}/', wp_basename( $image_src ), $image_edit_hash ); /** * Filters the maximum image width to be included in a 'srcset' attribute. * * @since 4.4.0 * * @param int $max_width The maximum image width to be included in the 'srcset'. Default '2048'. * @param int[] $size_array { * An array of requested width and height values. * * @type int $0 The width in pixels. * @type int $1 The height in pixels. * } */ $max_srcset_image_width = apply_filters( 'max_srcset_image_width', 2048, $size_array ); // Array to hold URL candidates. $sources = array(); /** * To make sure the ID matches our image src, we will check to see if any sizes in our attachment * meta match our $image_src. If no matches are found we don't return a srcset to avoid serving * an incorrect image. See #35045. */ $src_matched = false; /* * Loop through available images. Only use images that are resized * versions of the same edit. */ foreach ( $image_sizes as $image ) { $is_src = false; // Check if image meta isn't corrupted. if ( ! is_array( $image ) ) { continue; } // If the file name is part of the `src`, we've confirmed a match. if ( ! $src_matched && str_contains( $image_src, $dirname . $image['file'] ) ) { $src_matched = true; $is_src = true; } // Filter out images that are from previous edits. if ( $image_edited && ! strpos( $image['file'], $image_edit_hash[0] ) ) { continue; } /* * Filters out images that are wider than '$max_srcset_image_width' unless * that file is in the 'src' attribute. */ if ( $max_srcset_image_width && $image['width'] > $max_srcset_image_width && ! $is_src ) { continue; } // If the image dimensions are within 1px of the expected size, use it. if ( wp_image_matches_ratio( $image_width, $image_height, $image['width'], $image['height'] ) ) { // Add the URL, descriptor, and value to the sources array to be returned. $source = array( 'url' => $image_baseurl . $image['file'], 'descriptor' => 'w', 'value' => $image['width'], ); // The 'src' image has to be the first in the 'srcset', because of a bug in iOS8. See #35030. if ( $is_src ) { $sources = array( $image['width'] => $source ) + $sources; } else { $sources[ $image['width'] ] = $source; } } } /** * Filters an image's 'srcset' sources. * * @since 4.4.0 * * @param array $sources { * One or more arrays of source data to include in the 'srcset'. * * @type array $width { * @type string $url The URL of an image source. * @type string $descriptor The descriptor type used in the image candidate string, * either 'w' or 'x'. * @type int $value The source width if paired with a 'w' descriptor, or a * pixel density value if paired with an 'x' descriptor. * } * } * @param array $size_array { * An array of requested width and height values. * * @type int $0 The width in pixels. * @type int $1 The height in pixels. * } * @param string $image_src The 'src' of the image. * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'. * @param int $attachment_id Image attachment ID or 0. */ $sources = apply_filters( 'wp_calculate_image_srcset', $sources, $size_array, $image_src, $image_meta, $attachment_id ); // Only return a 'srcset' value if there is more than one source. if ( ! $src_matched || ! is_array( $sources ) || count( $sources ) < 2 ) { return false; } $srcset = ''; foreach ( $sources as $source ) { $srcset .= str_replace( ' ', '%20', $source['url'] ) . ' ' . $source['value'] . $source['descriptor'] . ', '; } return rtrim( $srcset, ', ' ); } /** * Retrieves the value for an image attachment's 'sizes' attribute. * * @since 4.4.0 * * @see wp_calculate_image_sizes() * * @param int $attachment_id Image attachment ID. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of * width and height values in pixels (in that order). Default 'medium'. * @param array|null $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'. * Default null. * @return string|false A valid source size value for use in a 'sizes' attribute or false. */ function wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $image_meta = null ) { $image = wp_get_attachment_image_src( $attachment_id, $size ); if ( ! $image ) { return false; } if ( ! is_array( $image_meta ) ) { $image_meta = wp_get_attachment_metadata( $attachment_id ); } $image_src = $image[0]; $size_array = array( absint( $image[1] ), absint( $image[2] ), ); return wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id ); } /** * Creates a 'sizes' attribute value for an image. * * @since 4.4.0 * * @param string|int[] $size Image size. Accepts any registered image size name, or an array of * width and height values in pixels (in that order). * @param string|null $image_src Optional. The URL to the image file. Default null. * @param array|null $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'. * Default null. * @param int $attachment_id Optional. Image attachment ID. Either `$image_meta` or `$attachment_id` * is needed when using the image size name as argument for `$size`. Default 0. * @return string|false A valid source size value for use in a 'sizes' attribute or false. */ function wp_calculate_image_sizes( $size, $image_src = null, $image_meta = null, $attachment_id = 0 ) { $width = 0; if ( is_array( $size ) ) { $width = absint( $size[0] ); } elseif ( is_string( $size ) ) { if ( ! $image_meta && $attachment_id ) { $image_meta = wp_get_attachment_metadata( $attachment_id ); } if ( is_array( $image_meta ) ) { $size_array = _wp_get_image_size_from_meta( $size, $image_meta ); if ( $size_array ) { $width = absint( $size_array[0] ); } } } if ( ! $width ) { return false; } // Setup the default 'sizes' attribute. $sizes = sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $width ); /** * Filters the output of 'wp_calculate_image_sizes()'. * * @since 4.4.0 * * @param string $sizes A source size value for use in a 'sizes' attribute. * @param string|int[] $size Requested image size. Can be any registered image size name, or * an array of width and height values in pixels (in that order). * @param string|null $image_src The URL to the image file or null. * @param array|null $image_meta The image meta data as returned by wp_get_attachment_metadata() or null. * @param int $attachment_id Image attachment ID of the original image or 0. */ return apply_filters( 'wp_calculate_image_sizes', $sizes, $size, $image_src, $image_meta, $attachment_id ); } /** * Determines if the image meta data is for the image source file. * * The image meta data is retrieved by attachment post ID. In some cases the post IDs may change. * For example when the website is exported and imported at another website. Then the * attachment post IDs that are in post_content for the exported website may not match * the same attachments at the new website. * * @since 5.5.0 * * @param string $image_location The full path or URI to the image file. * @param array $image_meta The attachment meta data as returned by 'wp_get_attachment_metadata()'. * @param int $attachment_id Optional. The image attachment ID. Default 0. * @return bool Whether the image meta is for this image file. */ function wp_image_file_matches_image_meta( $image_location, $image_meta, $attachment_id = 0 ) { $match = false; // Ensure the $image_meta is valid. if ( isset( $image_meta['file'] ) && strlen( $image_meta['file'] ) > 4 ) { // Remove query args in image URI. list( $image_location ) = explode( '?', $image_location ); // Check if the relative image path from the image meta is at the end of $image_location. if ( strrpos( $image_location, $image_meta['file'] ) === strlen( $image_location ) - strlen( $image_meta['file'] ) ) { $match = true; } else { // Retrieve the uploads sub-directory from the full size image. $dirname = _wp_get_attachment_relative_path( $image_meta['file'] ); if ( $dirname ) { $dirname = trailingslashit( $dirname ); } if ( ! empty( $image_meta['original_image'] ) ) { $relative_path = $dirname . $image_meta['original_image']; if ( strrpos( $image_location, $relative_path ) === strlen( $image_location ) - strlen( $relative_path ) ) { $match = true; } } if ( ! $match && ! empty( $image_meta['sizes'] ) ) { foreach ( $image_meta['sizes'] as $image_size_data ) { $relative_path = $dirname . $image_size_data['file']; if ( strrpos( $image_location, $relative_path ) === strlen( $image_location ) - strlen( $relative_path ) ) { $match = true; break; } } } } } /** * Filters whether an image path or URI matches image meta. * * @since 5.5.0 * * @param bool $match Whether the image relative path from the image meta * matches the end of the URI or path to the image file. * @param string $image_location Full path or URI to the tested image file. * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'. * @param int $attachment_id The image attachment ID or 0 if not supplied. */ return apply_filters( 'wp_image_file_matches_image_meta', $match, $image_location, $image_meta, $attachment_id ); } /** * Determines an image's width and height dimensions based on the source file. * * @since 5.5.0 * * @param string $image_src The image source file. * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'. * @param int $attachment_id Optional. The image attachment ID. Default 0. * @return array|false Array with first element being the width and second element being the height, * or false if dimensions cannot be determined. */ function wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id = 0 ) { $dimensions = false; // Is it a full size image? if ( isset( $image_meta['file'] ) && str_contains( $image_src, wp_basename( $image_meta['file'] ) ) ) { $dimensions = array( (int) $image_meta['width'], (int) $image_meta['height'], ); } if ( ! $dimensions && ! empty( $image_meta['sizes'] ) ) { $src_filename = wp_basename( $image_src ); foreach ( $image_meta['sizes'] as $image_size_data ) { if ( $src_filename === $image_size_data['file'] ) { $dimensions = array( (int) $image_size_data['width'], (int) $image_size_data['height'], ); break; } } } /** * Filters the 'wp_image_src_get_dimensions' value. * * @since 5.7.0 * * @param array|false $dimensions Array with first element being the width * and second element being the height, or * false if dimensions could not be determined. * @param string $image_src The image source file. * @param array $image_meta The image meta data as returned by * 'wp_get_attachment_metadata()'. * @param int $attachment_id The image attachment ID. Default 0. */ return apply_filters( 'wp_image_src_get_dimensions', $dimensions, $image_src, $image_meta, $attachment_id ); } /** * Adds 'srcset' and 'sizes' attributes to an existing 'img' element. * * @since 4.4.0 * * @see wp_calculate_image_srcset() * @see wp_calculate_image_sizes() * * @param string $image An HTML 'img' element to be filtered. * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'. * @param int $attachment_id Image attachment ID. * @return string Converted 'img' element with 'srcset' and 'sizes' attributes added. */ function wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ) { // Ensure the image meta exists. if ( empty( $image_meta['sizes'] ) ) { return $image; } $image_src = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : ''; list( $image_src ) = explode( '?', $image_src ); // Return early if we couldn't get the image source. if ( ! $image_src ) { return $image; } // Bail early if an image has been inserted and later edited. if ( preg_match( '/-e[0-9]{13}/', $image_meta['file'], $img_edit_hash ) && ! str_contains( wp_basename( $image_src ), $img_edit_hash[0] ) ) { return $image; } $width = preg_match( '/ width="([0-9]+)"/', $image, $match_width ) ? (int) $match_width[1] : 0; $height = preg_match( '/ height="([0-9]+)"/', $image, $match_height ) ? (int) $match_height[1] : 0; if ( $width && $height ) { $size_array = array( $width, $height ); } else { $size_array = wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id ); if ( ! $size_array ) { return $image; } } $srcset = wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id ); if ( $srcset ) { // Check if there is already a 'sizes' attribute. $sizes = strpos( $image, ' sizes=' ); if ( ! $sizes ) { $sizes = wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id ); } } if ( $srcset && $sizes ) { // Format the 'srcset' and 'sizes' string and escape attributes. $attr = sprintf( ' srcset="%s"', esc_attr( $srcset ) ); if ( is_string( $sizes ) ) { $attr .= sprintf( ' sizes="%s"', esc_attr( $sizes ) ); } // Add the srcset and sizes attributes to the image markup. return preg_replace( '/<img ([^>]+?)[\/ ]*>/', '<img $1' . $attr . ' />', $image ); } return $image; } /** * Determines whether to add the `loading` attribute to the specified tag in the specified context. * * @since 5.5.0 * @since 5.7.0 Now returns `true` by default for `iframe` tags. * * @param string $tag_name The tag name. * @param string $context Additional context, like the current filter name * or the function name from where this was called. * @return bool Whether to add the attribute. */ function wp_lazy_loading_enabled( $tag_name, $context ) { /* * By default add to all 'img' and 'iframe' tags. * See https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading * See https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-loading */ $default = ( 'img' === $tag_name || 'iframe' === $tag_name ); /** * Filters whether to add the `loading` attribute to the specified tag in the specified context. * * @since 5.5.0 * * @param bool $default Default value. * @param string $tag_name The tag name. * @param string $context Additional context, like the current filter name * or the function name from where this was called. */ return (bool) apply_filters( 'wp_lazy_loading_enabled', $default, $tag_name, $context ); } /** * Filters specific tags in post content and modifies their markup. * * Modifies HTML tags in post content to include new browser and HTML technologies * that may not have existed at the time of post creation. These modifications currently * include adding `srcset`, `sizes`, and `loading` attributes to `img` HTML tags, as well * as adding `loading` attributes to `iframe` HTML tags. * Future similar optimizations should be added/expected here. * * @since 5.5.0 * @since 5.7.0 Now supports adding `loading` attributes to `iframe` tags. * * @see wp_img_tag_add_width_and_height_attr() * @see wp_img_tag_add_srcset_and_sizes_attr() * @see wp_img_tag_add_loading_optimization_attrs() * @see wp_iframe_tag_add_loading_attr() * * @param string $content The HTML content to be filtered. * @param string $context Optional. Additional context to pass to the filters. * Defaults to `current_filter()` when not set. * @return string Converted content with images modified. */ function wp_filter_content_tags( $content, $context = null ) { if ( null === $context ) { $context = current_filter(); } $add_iframe_loading_attr = wp_lazy_loading_enabled( 'iframe', $context ); if ( ! preg_match_all( '/<(img|iframe)\s[^>]+>/', $content, $matches, PREG_SET_ORDER ) ) { return $content; } // List of the unique `img` tags found in $content. $images = array(); // List of the unique `iframe` tags found in $content. $iframes = array(); foreach ( $matches as $match ) { list( $tag, $tag_name ) = $match; switch ( $tag_name ) { case 'img': if ( preg_match( '/wp-image-([0-9]+)/i', $tag, $class_id ) ) { $attachment_id = absint( $class_id[1] ); if ( $attachment_id ) { /* * If exactly the same image tag is used more than once, overwrite it. * All identical tags will be replaced later with 'str_replace()'. */ $images[ $tag ] = $attachment_id; break; } } $images[ $tag ] = 0; break; case 'iframe': $iframes[ $tag ] = 0; break; } } // Reduce the array to unique attachment IDs. $attachment_ids = array_unique( array_filter( array_values( $images ) ) ); if ( count( $attachment_ids ) > 1 ) { /* * Warm the object cache with post and meta information for all found * images to avoid making individual database calls. */ _prime_post_caches( $attachment_ids, false, true ); } // Iterate through the matches in order of occurrence as it is relevant for whether or not to lazy-load. foreach ( $matches as $match ) { // Filter an image match. if ( isset( $images[ $match[0] ] ) ) { $filtered_image = $match[0]; $attachment_id = $images[ $match[0] ]; // Add 'width' and 'height' attributes if applicable. if ( $attachment_id > 0 && ! str_contains( $filtered_image, ' width=' ) && ! str_contains( $filtered_image, ' height=' ) ) { $filtered_image = wp_img_tag_add_width_and_height_attr( $filtered_image, $context, $attachment_id ); } // Add 'srcset' and 'sizes' attributes if applicable. if ( $attachment_id > 0 && ! str_contains( $filtered_image, ' srcset=' ) ) { $filtered_image = wp_img_tag_add_srcset_and_sizes_attr( $filtered_image, $context, $attachment_id ); } // Add loading optimization attributes if applicable. $filtered_image = wp_img_tag_add_loading_optimization_attrs( $filtered_image, $context ); // Adds 'auto' to the sizes attribute if applicable. $filtered_image = wp_img_tag_add_auto_sizes( $filtered_image ); /** * Filters an img tag within the content for a given context. * * @since 6.0.0 * * @param string $filtered_image Full img tag with attributes that will replace the source img tag. * @param string $context Additional context, like the current filter name or the function name from where this was called. * @param int $attachment_id The image attachment ID. May be 0 in case the image is not an attachment. */ $filtered_image = apply_filters( 'wp_content_img_tag', $filtered_image, $context, $attachment_id ); if ( $filtered_image !== $match[0] ) { $content = str_replace( $match[0], $filtered_image, $content ); } /* * Unset image lookup to not run the same logic again unnecessarily if the same image tag is used more than * once in the same blob of content. */ unset( $images[ $match[0] ] ); } // Filter an iframe match. if ( isset( $iframes[ $match[0] ] ) ) { $filtered_iframe = $match[0]; // Add 'loading' attribute if applicable. if ( $add_iframe_loading_attr && ! str_contains( $filtered_iframe, ' loading=' ) ) { $filtered_iframe = wp_iframe_tag_add_loading_attr( $filtered_iframe, $context ); } if ( $filtered_iframe !== $match[0] ) { $content = str_replace( $match[0], $filtered_iframe, $content ); } /* * Unset iframe lookup to not run the same logic again unnecessarily if the same iframe tag is used more * than once in the same blob of content. */ unset( $iframes[ $match[0] ] ); } } return $content; } /** * Adds 'auto' to the sizes attribute to the image, if the image is lazy loaded and does not already include it. * * @since 6.7.0 * * @param string $image The image tag markup being filtered. * @return string The filtered image tag markup. */ function wp_img_tag_add_auto_sizes( string $image ): string { /** * Filters whether auto-sizes for lazy loaded images is enabled. * * @since 6.7.1 * * @param boolean $enabled Whether auto-sizes for lazy loaded images is enabled. */ if ( ! apply_filters( 'wp_img_tag_add_auto_sizes', true ) ) { return $image; } $processor = new WP_HTML_Tag_Processor( $image ); // Bail if there is no IMG tag. if ( ! $processor->next_tag( array( 'tag_name' => 'IMG' ) ) ) { return $image; } // Bail early if the image is not lazy-loaded. $loading = $processor->get_attribute( 'loading' ); if ( ! is_string( $loading ) || 'lazy' !== strtolower( trim( $loading, " \t\f\r\n" ) ) ) { return $image; } /* * Bail early if the image doesn't have a width attribute. * Per WordPress Core itself, lazy-loaded images should always have a width attribute. * However, it is possible that lazy-loading could be added by a plugin, where we don't have that guarantee. * As such, it still makes sense to ensure presence of a width attribute here in order to use `sizes=auto`. */ $width = $processor->get_attribute( 'width' ); if ( ! is_string( $width ) || '' === $width ) { return $image; } $sizes = $processor->get_attribute( 'sizes' ); // Bail early if the image is not responsive. if ( ! is_string( $sizes ) ) { return $image; } // Don't add 'auto' to the sizes attribute if it already exists. if ( wp_sizes_attribute_includes_valid_auto( $sizes ) ) { return $image; } $processor->set_attribute( 'sizes', "auto, $sizes" ); return $processor->get_updated_html(); } /** * Checks whether the given 'sizes' attribute includes the 'auto' keyword as the first item in the list. * * Per the HTML spec, if present it must be the first entry. * * @since 6.7.0 * * @param string $sizes_attr The 'sizes' attribute value. * @return bool True if the 'auto' keyword is present, false otherwise. */ function wp_sizes_attribute_includes_valid_auto( string $sizes_attr ): bool { list( $first_size ) = explode( ',', $sizes_attr, 2 ); return 'auto' === strtolower( trim( $first_size, " \t\f\r\n" ) ); } /** * Enqueues a CSS rule to fix potential visual issues with images using `sizes=auto`. * * This rule overrides the similar rule in the default user agent stylesheet, to avoid images that use e.g. * `width: auto` or `width: fit-content` to appear smaller. * * @since 6.9.0 * * @see https://html.spec.whatwg.org/multipage/rendering.html#img-contain-size * @see https://core.trac.wordpress.org/ticket/62413 * @see https://core.trac.wordpress.org/ticket/62731 */ function wp_enqueue_img_auto_sizes_contain_css_fix(): void { // Back-compat for plugins that disable functionality by unhooking this action. $priority = has_action( 'wp_head', 'wp_print_auto_sizes_contain_css_fix' ); if ( false === $priority ) { return; } remove_action( 'wp_head', 'wp_print_auto_sizes_contain_css_fix', $priority ); /** This filter is documented in wp-includes/media.php */ $add_auto_sizes = apply_filters( 'wp_img_tag_add_auto_sizes', true ); if ( ! $add_auto_sizes ) { return; } $handle = 'wp-img-auto-sizes-contain'; wp_register_style( $handle, false ); wp_add_inline_style( $handle, 'img:is([sizes=auto i],[sizes^="auto," i]){contain-intrinsic-size:3000px 1500px}' ); // Make sure inline style is printed first since it was previously printed at wp_head priority 1 and this preserves the CSS cascade. array_unshift( wp_styles()->queue, $handle ); } /** * Adds optimization attributes to an `img` HTML tag. * * @since 6.3.0 * * @param string $image The HTML `img` tag where the attribute should be added. * @param string $context Additional context to pass to the filters. * @return string Converted `img` tag with optimization attributes added. */ function wp_img_tag_add_loading_optimization_attrs( $image, $context ) { $src = preg_match( '/ src=["\']?([^"\']*)/i', $image, $matche_src ) ? $matche_src[1] : null; $width = preg_match( '/ width=["\']([0-9]+)["\']/', $image, $match_width ) ? (int) $match_width[1] : null; $height = preg_match( '/ height=["\']([0-9]+)["\']/', $image, $match_height ) ? (int) $match_height[1] : null; $loading_val = preg_match( '/ loading=["\']([A-Za-z]+)["\']/', $image, $match_loading ) ? $match_loading[1] : null; $fetchpriority_val = preg_match( '/ fetchpriority=["\']([A-Za-z]+)["\']/', $image, $match_fetchpriority ) ? $match_fetchpriority[1] : null; $decoding_val = preg_match( '/ decoding=["\']([A-Za-z]+)["\']/', $image, $match_decoding ) ? $match_decoding[1] : null; /* * Get loading optimization attributes to use. * This must occur before the conditional check below so that even images * that are ineligible for being lazy-loaded are considered. */ $optimization_attrs = wp_get_loading_optimization_attributes( 'img', array( 'src' => $src, 'width' => $width, 'height' => $height, 'loading' => $loading_val, 'fetchpriority' => $fetchpriority_val, 'decoding' => $decoding_val, ), $context ); // Images should have source for the loading optimization attributes to be added. if ( ! str_contains( $image, ' src="' ) ) { return $image; } if ( empty( $decoding_val ) ) { /** * Filters the `decoding` attribute value to add to an image. Default `async`. * * Returning a falsey value will omit the attribute. * * @since 6.1.0 * * @param string|false|null $value The `decoding` attribute value. Returning a falsey value * will result in the attribute being omitted for the image. * Otherwise, it may be: 'async', 'sync', or 'auto'. Defaults to false. * @param string $image The HTML `img` tag to be filtered. * @param string $context Additional context about how the function was called * or where the img tag is. */ $filtered_decoding_attr = apply_filters( 'wp_img_tag_add_decoding_attr', isset( $optimization_attrs['decoding'] ) ? $optimization_attrs['decoding'] : false, $image, $context ); // Validate the values after filtering. if ( isset( $optimization_attrs['decoding'] ) && ! $filtered_decoding_attr ) { // Unset `decoding` attribute if `$filtered_decoding_attr` is set to `false`. unset( $optimization_attrs['decoding'] ); } elseif ( in_array( $filtered_decoding_attr, array( 'async', 'sync', 'auto' ), true ) ) { $optimization_attrs['decoding'] = $filtered_decoding_attr; } if ( ! empty( $optimization_attrs['decoding'] ) ) { $image = str_replace( '<img', '<img decoding="' . esc_attr( $optimization_attrs['decoding'] ) . '"', $image ); } } // Images should have dimension attributes for the 'loading' and 'fetchpriority' attributes to be added. if ( ! str_contains( $image, ' width="' ) || ! str_contains( $image, ' height="' ) ) { return $image; } // Retained for backward compatibility. $loading_attrs_enabled = wp_lazy_loading_enabled( 'img', $context ); if ( empty( $loading_val ) && $loading_attrs_enabled ) { /** * Filters the `loading` attribute value to add to an image. Default `lazy`. * * Returning `false` or an empty string will not add the attribute. * Returning `true` will add the default value. * * @since 5.5.0 * * @param string|bool $value The `loading` attribute value. Returning a falsey value will result in * the attribute being omitted for the image. * @param string $image The HTML `img` tag to be filtered. * @param string $context Additional context about how the function was called or where the img tag is. */ $filtered_loading_attr = apply_filters( 'wp_img_tag_add_loading_attr', isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false, $image, $context ); // Validate the values after filtering. if ( isset( $optimization_attrs['loading'] ) && ! $filtered_loading_attr ) { // Unset `loading` attributes if `$filtered_loading_attr` is set to `false`. unset( $optimization_attrs['loading'] ); } elseif ( in_array( $filtered_loading_attr, array( 'lazy', 'eager' ), true ) ) { /* * If the filter changed the loading attribute to "lazy" when a fetchpriority attribute * with value "high" is already present, trigger a warning since those two attribute * values should be mutually exclusive. * * The same warning is present in `wp_get_loading_optimization_attributes()`, and here it * is only intended for the specific scenario where the above filtered caused the problem. */ if ( isset( $optimization_attrs['fetchpriority'] ) && 'high' === $optimization_attrs['fetchpriority'] && ( isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false ) !== $filtered_loading_attr && 'lazy' === $filtered_loading_attr ) { _doing_it_wrong( __FUNCTION__, __( 'An image should not be lazy-loaded and marked as high priority at the same time.' ), '6.3.0' ); } // The filtered value will still be respected. $optimization_attrs['loading'] = $filtered_loading_attr; } if ( ! empty( $optimization_attrs['loading'] ) ) { $image = str_replace( '<img', '<img loading="' . esc_attr( $optimization_attrs['loading'] ) . '"', $image ); } } if ( empty( $fetchpriority_val ) && ! empty( $optimization_attrs['fetchpriority'] ) ) { $image = str_replace( '<img', '<img fetchpriority="' . esc_attr( $optimization_attrs['fetchpriority'] ) . '"', $image ); } return $image; } /** * Adds `width` and `height` attributes to an `img` HTML tag. * * @since 5.5.0 * * @param string $image The HTML `img` tag where the attribute should be added. * @param string $context Additional context to pass to the filters. * @param int $attachment_id Image attachment ID. * @return string Converted 'img' element with 'width' and 'height' attributes added. */ function wp_img_tag_add_width_and_height_attr( $image, $context, $attachment_id ) { $image_src = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : ''; list( $image_src ) = explode( '?', $image_src ); // Return early if we couldn't get the image source. if ( ! $image_src ) { return $image; } /** * Filters whether to add the missing `width` and `height` HTML attributes to the img tag. Default `true`. * * Returning anything else than `true` will not add the attributes. * * @since 5.5.0 * * @param bool $value The filtered value, defaults to `true`. * @param string $image The HTML `img` tag where the attribute should be added. * @param string $context Additional context about how the function was called or where the img tag is. * @param int $attachment_id The image attachment ID. */ $add = apply_filters( 'wp_img_tag_add_width_and_height_attr', true, $image, $context, $attachment_id ); if ( true === $add ) { $image_meta = wp_get_attachment_metadata( $attachment_id ); $size_array = wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id ); if ( $size_array && $size_array[0] && $size_array[1] ) { // If the width is enforced through style (e.g. in an inline image), calculate the dimension attributes. $style_width = preg_match( '/style="width:\s*(\d+)px;"/', $image, $match_width ) ? (int) $match_width[1] : 0; if ( $style_width ) { $size_array[1] = (int) round( $size_array[1] * $style_width / $size_array[0] ); $size_array[0] = $style_width; } $hw = trim( image_hwstring( $size_array[0], $size_array[1] ) ); return str_replace( '<img', "<img {$hw}", $image ); } } return $image; } /** * Adds `srcset` and `sizes` attributes to an existing `img` HTML tag. * * @since 5.5.0 * * @param string $image The HTML `img` tag where the attribute should be added. * @param string $context Additional context to pass to the filters. * @param int $attachment_id Image attachment ID. * @return string Converted 'img' element with 'loading' attribute added. */ function wp_img_tag_add_srcset_and_sizes_attr( $image, $context, $attachment_id ) { /** * Filters whether to add the `srcset` and `sizes` HTML attributes to the img tag. Default `true`. * * Returning anything else than `true` will not add the attributes. * * @since 5.5.0 * * @param bool $value The filtered value, defaults to `true`. * @param string $image The HTML `img` tag where the attribute should be added. * @param string $context Additional context about how the function was called or where the img tag is. * @param int $attachment_id The image attachment ID. */ $add = apply_filters( 'wp_img_tag_add_srcset_and_sizes_attr', true, $image, $context, $attachment_id ); if ( true === $add ) { $image_meta = wp_get_attachment_metadata( $attachment_id ); return wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ); } return $image; } /** * Adds `loading` attribute to an `iframe` HTML tag. * * @since 5.7.0 * * @param string $iframe The HTML `iframe` tag where the attribute should be added. * @param string $context Additional context to pass to the filters. * @return string Converted `iframe` tag with `loading` attribute added. */ function wp_iframe_tag_add_loading_attr( $iframe, $context ) { /* * Get loading attribute value to use. This must occur before the conditional check below so that even iframes that * are ineligible for being lazy-loaded are considered. */ $optimization_attrs = wp_get_loading_optimization_attributes( 'iframe', array( /* * The concrete values for width and height are not important here for now * since fetchpriority is not yet supported for iframes. * TODO: Use WP_HTML_Tag_Processor to extract actual values once support is * added. */ 'width' => str_contains( $iframe, ' width="' ) ? 100 : null, 'height' => str_contains( $iframe, ' height="' ) ? 100 : null, // This function is never called when a 'loading' attribute is already present. 'loading' => null, ), $context ); // Iframes should have source and dimension attributes for the `loading` attribute to be added. if ( ! str_contains( $iframe, ' src="' ) || ! str_contains( $iframe, ' width="' ) || ! str_contains( $iframe, ' height="' ) ) { return $iframe; } $value = isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false; /** * Filters the `loading` attribute value to add to an iframe. Default `lazy`. * * Returning `false` or an empty string will not add the attribute. * Returning `true` will add the default value. * * @since 5.7.0 * * @param string|bool $value The `loading` attribute value. Returning a falsey value will result in * the attribute being omitted for the iframe. * @param string $iframe The HTML `iframe` tag to be filtered. * @param string $context Additional context about how the function was called or where the iframe tag is. */ $value = apply_filters( 'wp_iframe_tag_add_loading_attr', $value, $iframe, $context ); if ( $value ) { if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) { $value = 'lazy'; } return str_replace( '<iframe', '<iframe loading="' . esc_attr( $value ) . '"', $iframe ); } return $iframe; } /** * Adds a 'wp-post-image' class to post thumbnails. Internal use only. * * Uses the {@see 'begin_fetch_post_thumbnail_html'} and {@see 'end_fetch_post_thumbnail_html'} * action hooks to dynamically add/remove itself so as to only filter post thumbnails. * * @ignore * @since 2.9.0 * * @param string[] $attr Array of thumbnail attributes including src, class, alt, title, keyed by attribute name. * @return string[] Modified array of attributes including the new 'wp-post-image' class. */ function _wp_post_thumbnail_class_filter( $attr ) { $attr['class'] .= ' wp-post-image'; return $attr; } /** * Adds '_wp_post_thumbnail_class_filter' callback to the 'wp_get_attachment_image_attributes' * filter hook. Internal use only. * * @ignore * @since 2.9.0 * * @param string[] $attr Array of thumbnail attributes including src, class, alt, title, keyed by attribute name. */ function _wp_post_thumbnail_class_filter_add( $attr ) { add_filter( 'wp_get_attachment_image_attributes', '_wp_post_thumbnail_class_filter' ); } /** * Removes the '_wp_post_thumbnail_class_filter' callback from the 'wp_get_attachment_image_attributes' * filter hook. Internal use only. * * @ignore * @since 2.9.0 * * @param string[] $attr Array of thumbnail attributes including src, class, alt, title, keyed by attribute name. */ function _wp_post_thumbnail_class_filter_remove( $attr ) { remove_filter( 'wp_get_attachment_image_attributes', '_wp_post_thumbnail_class_filter' ); } /** * Overrides the context used in {@see wp_get_attachment_image()}. Internal use only. * * Uses the {@see 'begin_fetch_post_thumbnail_html'} and {@see 'end_fetch_post_thumbnail_html'} * action hooks to dynamically add/remove itself so as to only filter post thumbnails. * * @ignore * @since 6.3.0 * @access private * * @param string $context The context for rendering an attachment image. * @return string Modified context set to 'the_post_thumbnail'. */ function _wp_post_thumbnail_context_filter( $context ) { return 'the_post_thumbnail'; } /** * Adds the '_wp_post_thumbnail_context_filter' callback to the 'wp_get_attachment_image_context' * filter hook. Internal use only. * * @ignore * @since 6.3.0 * @access private */ function _wp_post_thumbnail_context_filter_add() { add_filter( 'wp_get_attachment_image_context', '_wp_post_thumbnail_context_filter' ); } /** * Removes the '_wp_post_thumbnail_context_filter' callback from the 'wp_get_attachment_image_context' * filter hook. Internal use only. * * @ignore * @since 6.3.0 * @access private */ function _wp_post_thumbnail_context_filter_remove() { remove_filter( 'wp_get_attachment_image_context', '_wp_post_thumbnail_context_filter' ); } add_shortcode( 'wp_caption', 'img_caption_shortcode' ); add_shortcode( 'caption', 'img_caption_shortcode' ); /** * Builds the Caption shortcode output. * * Allows a plugin to replace the content that would otherwise be returned. The * filter is {@see 'img_caption_shortcode'} and passes an empty string, the attr * parameter and the content parameter values. * * The supported attributes for the shortcode are 'id', 'caption_id', 'align', * 'width', 'caption', and 'class'. * * @since 2.6.0 * @since 3.9.0 The `class` attribute was added. * @since 5.1.0 The `caption_id` attribute was added. * @since 5.9.0 The `$content` parameter default value changed from `null` to `''`. * * @param array $attr { * Attributes of the caption shortcode. * * @type string $id ID of the image and caption container element, i.e. `<figure>` or `<div>`. * @type string $caption_id ID of the caption element, i.e. `<figcaption>` or `<p>`. * @type string $align Class name that aligns the caption. Default 'alignnone'. Accepts 'alignleft', * 'aligncenter', alignright', 'alignnone'. * @type int $width The width of the caption, in pixels. * @type string $caption The caption text. * @type string $class Additional class name(s) added to the caption container. * } * @param string $content Optional. Shortcode content. Default empty string. * @return string HTML content to display the caption. */ function img_caption_shortcode( $attr, $content = '' ) { // New-style shortcode with the caption inside the shortcode with the link and image tags. if ( ! isset( $attr['caption'] ) ) { if ( preg_match( '#((?:<a [^>]+>\s*)?<img [^>]+>(?:\s*</a>)?)(.*)#is', $content, $matches ) ) { $content = $matches[1]; $attr['caption'] = trim( $matches[2] ); } } elseif ( str_contains( $attr['caption'], '<' ) ) { $attr['caption'] = wp_kses( $attr['caption'], 'post' ); } /** * Filters the default caption shortcode output. * * If the filtered output isn't empty, it will be used instead of generating * the default caption template. * * @since 2.6.0 * * @see img_caption_shortcode() * * @param string $output The caption output. Default empty. * @param array $attr Attributes of the caption shortcode. * @param string $content The image element, possibly wrapped in a hyperlink. */ $output = apply_filters( 'img_caption_shortcode', '', $attr, $content ); if ( ! empty( $output ) ) { return $output; } $atts = shortcode_atts( array( 'id' => '', 'caption_id' => '', 'align' => 'alignnone', 'width' => '', 'caption' => '', 'class' => '', ), $attr, 'caption' ); $atts['width'] = (int) $atts['width']; if ( $atts['width'] < 1 || empty( $atts['caption'] ) ) { return $content; } $id = ''; $caption_id = ''; $describedby = ''; if ( $atts['id'] ) { $atts['id'] = sanitize_html_class( $atts['id'] ); $id = 'id="' . esc_attr( $atts['id'] ) . '" '; } if ( $atts['caption_id'] ) { $atts['caption_id'] = sanitize_html_class( $atts['caption_id'] ); } elseif ( $atts['id'] ) { $atts['caption_id'] = 'caption-' . str_replace( '_', '-', $atts['id'] ); } if ( $atts['caption_id'] ) { $caption_id = 'id="' . esc_attr( $atts['caption_id'] ) . '" '; $describedby = 'aria-describedby="' . esc_attr( $atts['caption_id'] ) . '" '; } $class = trim( 'wp-caption ' . $atts['align'] . ' ' . $atts['class'] ); $html5 = current_theme_supports( 'html5', 'caption' ); // HTML5 captions never added the extra 10px to the image width. $width = $html5 ? $atts['width'] : ( 10 + $atts['width'] ); /** * Filters the width of an image's caption. * * By default, the caption is 10 pixels greater than the width of the image, * to prevent post content from running up against a floated image. * * @since 3.7.0 * * @see img_caption_shortcode() * * @param int $width Width of the caption in pixels. To remove this inline style, * return zero. * @param array $atts Attributes of the caption shortcode. * @param string $content The image element, possibly wrapped in a hyperlink. */ $caption_width = apply_filters( 'img_caption_shortcode_width', $width, $atts, $content ); $style = ''; if ( $caption_width ) { $style = 'style="width: ' . (int) $caption_width . 'px" '; } if ( $html5 ) { $html = sprintf( '<figure %s%s%sclass="%s">%s%s</figure>', $id, $describedby, $style, esc_attr( $class ), do_shortcode( $content ), sprintf( '<figcaption %sclass="wp-caption-text">%s</figcaption>', $caption_id, $atts['caption'] ) ); } else { $html = sprintf( '<div %s%sclass="%s">%s%s</div>', $id, $style, esc_attr( $class ), str_replace( '<img ', '<img ' . $describedby, do_shortcode( $content ) ), sprintf( '<p %sclass="wp-caption-text">%s</p>', $caption_id, $atts['caption'] ) ); } return $html; } add_shortcode( 'gallery', 'gallery_shortcode' ); /** * Builds the Gallery shortcode output. * * This implements the functionality of the Gallery Shortcode for displaying * WordPress images on a post. * * @since 2.5.0 * @since 2.8.0 Added the `$attr` parameter to set the shortcode output. New attributes included * such as `size`, `itemtag`, `icontag`, `captiontag`, and columns. Changed markup from * `div` tags to `dl`, `dt` and `dd` tags. Support more than one gallery on the * same page. * @since 2.9.0 Added support for `include` and `exclude` to shortcode. * @since 3.5.0 Use get_post() instead of global `$post`. Handle mapping of `ids` to `include` * and `orderby`. * @since 3.6.0 Added validation for tags used in gallery shortcode. Add orientation information to items. * @since 3.7.0 Introduced the `link` attribute. * @since 3.9.0 `html5` gallery support, accepting 'itemtag', 'icontag', and 'captiontag' attributes. * @since 4.0.0 Removed use of `extract()`. * @since 4.1.0 Added attribute to `wp_get_attachment_link()` to output `aria-describedby`. * @since 4.2.0 Passed the shortcode instance ID to `post_gallery` and `post_playlist` filters. * @since 4.6.0 Standardized filter docs to match documentation standards for PHP. * @since 5.1.0 Code cleanup for WPCS 1.0.0 coding standards. * @since 5.3.0 Saved progress of intermediate image creation after upload. * @since 5.5.0 Ensured that galleries can be output as a list of links in feeds. * @since 5.6.0 Replaced order-style PHP type conversion functions with typecasts. Fix logic for * an array of image dimensions. * * @param array $attr { * Attributes of the gallery shortcode. * * @type string $order Order of the images in the gallery. Default 'ASC'. Accepts 'ASC', 'DESC'. * @type string $orderby The field to use when ordering the images. Default 'menu_order ID'. * Accepts any valid SQL ORDERBY statement. * @type int $id Post ID. * @type string $itemtag HTML tag to use for each image in the gallery. * Default 'dl', or 'figure' when the theme registers HTML5 gallery support. * @type string $icontag HTML tag to use for each image's icon. * Default 'dt', or 'div' when the theme registers HTML5 gallery support. * @type string $captiontag HTML tag to use for each image's caption. * Default 'dd', or 'figcaption' when the theme registers HTML5 gallery support. * @type int $columns Number of columns of images to display. Default 3. * @type string|int[] $size Size of the images to display. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'thumbnail'. * @type string $ids A comma-separated list of IDs of attachments to display. Default empty. * @type string $include A comma-separated list of IDs of attachments to include. Default empty. * @type string $exclude A comma-separated list of IDs of attachments to exclude. Default empty. * @type string $link What to link each image to. Default empty (links to the attachment page). * Accepts 'file', 'none'. * } * @return string HTML content to display gallery. */ function gallery_shortcode( $attr ) { $post = get_post(); static $instance = 0; ++$instance; if ( ! empty( $attr['ids'] ) ) { // 'ids' is explicitly ordered, unless you specify otherwise. if ( empty( $attr['orderby'] ) ) { $attr['orderby'] = 'post__in'; } $attr['include'] = $attr['ids']; } /** * Filters the default gallery shortcode output. * * If the filtered output isn't empty, it will be used instead of generating * the default gallery template. * * @since 2.5.0 * @since 4.2.0 The `$instance` parameter was added. * * @see gallery_shortcode() * * @param string $output The gallery output. Default empty. * @param array $attr Attributes of the gallery shortcode. * @param int $instance Unique numeric ID of this gallery shortcode instance. */ $output = apply_filters( 'post_gallery', '', $attr, $instance ); if ( ! empty( $output ) ) { return $output; } $html5 = current_theme_supports( 'html5', 'gallery' ); $atts = shortcode_atts( array( 'order' => 'ASC', 'orderby' => 'menu_order ID', 'id' => $post ? $post->ID : 0, 'itemtag' => $html5 ? 'figure' : 'dl', 'icontag' => $html5 ? 'div' : 'dt', 'captiontag' => $html5 ? 'figcaption' : 'dd', 'columns' => 3, 'size' => 'thumbnail', 'include' => '', 'exclude' => '', 'link' => '', ), $attr, 'gallery' ); $id = (int) $atts['id']; if ( ! empty( $atts['include'] ) ) { $_attachments = get_posts( array( 'include' => $atts['include'], 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $atts['order'], 'orderby' => $atts['orderby'], ) ); $attachments = array(); foreach ( $_attachments as $key => $val ) { $attachments[ $val->ID ] = $_attachments[ $key ]; } } elseif ( ! empty( $atts['exclude'] ) ) { $post_parent_id = $id; $attachments = get_children( array( 'post_parent' => $id, 'exclude' => $atts['exclude'], 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $atts['order'], 'orderby' => $atts['orderby'], ) ); } else { $post_parent_id = $id; $attachments = get_children( array( 'post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $atts['order'], 'orderby' => $atts['orderby'], ) ); } if ( ! empty( $post_parent_id ) ) { $post_parent = get_post( $post_parent_id ); // Terminate the shortcode execution if the user cannot read the post or it is password-protected. if ( ! is_post_publicly_viewable( $post_parent->ID ) && ! current_user_can( 'read_post', $post_parent->ID ) || post_password_required( $post_parent ) ) { return ''; } } if ( empty( $attachments ) ) { return ''; } if ( is_feed() ) { $output = "\n"; foreach ( $attachments as $att_id => $attachment ) { if ( ! empty( $atts['link'] ) ) { if ( 'none' === $atts['link'] ) { $output .= wp_get_attachment_image( $att_id, $atts['size'], false, $attr ); } else { $output .= wp_get_attachment_link( $att_id, $atts['size'], false ); } } else { $output .= wp_get_attachment_link( $att_id, $atts['size'], true ); } $output .= "\n"; } return $output; } $itemtag = tag_escape( $atts['itemtag'] ); $captiontag = tag_escape( $atts['captiontag'] ); $icontag = tag_escape( $atts['icontag'] ); $valid_tags = wp_kses_allowed_html( 'post' ); if ( ! isset( $valid_tags[ $itemtag ] ) ) { $itemtag = 'dl'; } if ( ! isset( $valid_tags[ $captiontag ] ) ) { $captiontag = 'dd'; } if ( ! isset( $valid_tags[ $icontag ] ) ) { $icontag = 'dt'; } $columns = (int) $atts['columns']; $itemwidth = $columns > 0 ? floor( 100 / $columns ) : 100; $float = is_rtl() ? 'right' : 'left'; $selector = "gallery-{$instance}"; $gallery_style = ''; /** * Filters whether to print default gallery styles. * * @since 3.1.0 * * @param bool $print Whether to print default gallery styles. * Defaults to false if the theme supports HTML5 galleries. * Otherwise, defaults to true. */ if ( apply_filters( 'use_default_gallery_style', ! $html5 ) ) { $type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"'; $gallery_style = " <style{$type_attr}> #{$selector} { margin: auto; } #{$selector} .gallery-item { float: {$float}; margin-top: 10px; text-align: center; width: {$itemwidth}%; } #{$selector} img { border: 2px solid #cfcfcf; } #{$selector} .gallery-caption { margin-left: 0; } /* see gallery_shortcode() in wp-includes/media.php */ </style>\n\t\t"; } $size_class = sanitize_html_class( is_array( $atts['size'] ) ? implode( 'x', $atts['size'] ) : $atts['size'] ); $gallery_div = "<div id='$selector' class='gallery galleryid-{$id} gallery-columns-{$columns} gallery-size-{$size_class}'>"; /** * Filters the default gallery shortcode CSS styles. * * @since 2.5.0 * * @param string $gallery_style Default CSS styles and opening HTML div container * for the gallery shortcode output. */ $output = apply_filters( 'gallery_style', $gallery_style . $gallery_div ); $i = 0; foreach ( $attachments as $id => $attachment ) { $attr = ( trim( $attachment->post_excerpt ) ) ? array( 'aria-describedby' => "$selector-$id" ) : ''; if ( ! empty( $atts['link'] ) && 'file' === $atts['link'] ) { $image_output = wp_get_attachment_link( $id, $atts['size'], false, false, false, $attr ); } elseif ( ! empty( $atts['link'] ) && 'none' === $atts['link'] ) { $image_output = wp_get_attachment_image( $id, $atts['size'], false, $attr ); } else { $image_output = wp_get_attachment_link( $id, $atts['size'], true, false, false, $attr ); } $image_meta = wp_get_attachment_metadata( $id ); $orientation = ''; if ( isset( $image_meta['height'], $image_meta['width'] ) ) { $orientation = ( $image_meta['height'] > $image_meta['width'] ) ? 'portrait' : 'landscape'; } $output .= "<{$itemtag} class='gallery-item'>"; $output .= " <{$icontag} class='gallery-icon {$orientation}'> $image_output </{$icontag}>"; if ( $captiontag && trim( $attachment->post_excerpt ) ) { $output .= " <{$captiontag} class='wp-caption-text gallery-caption' id='$selector-$id'> " . wptexturize( $attachment->post_excerpt ) . " </{$captiontag}>"; } $output .= "</{$itemtag}>"; if ( ! $html5 && $columns > 0 && 0 === ++$i % $columns ) { $output .= '<br style="clear: both" />'; } } if ( ! $html5 && $columns > 0 && 0 !== $i % $columns ) { $output .= " <br style='clear: both' />"; } $output .= " </div>\n"; return $output; } /** * Outputs the templates used by playlists. * * @since 3.9.0 */ function wp_underscore_playlist_templates() { ?> <script type="text/html" id="tmpl-wp-playlist-current-item"> <# if ( data.thumb && data.thumb.src ) { #> <img src="{{ data.thumb.src }}" alt="" /> <# } #> <div class="wp-playlist-caption"> <span class="wp-playlist-item-meta wp-playlist-item-title"> <# if ( data.meta.album || data.meta.artist ) { #> <?php /* translators: %s: Playlist item title. */ printf( _x( '“%s”', 'playlist item title' ), '{{ data.title }}' ); ?> <# } else { #> {{ data.title }} <# } #> </span> <# if ( data.meta.album ) { #><span class="wp-playlist-item-meta wp-playlist-item-album">{{ data.meta.album }}</span><# } #> <# if ( data.meta.artist ) { #><span class="wp-playlist-item-meta wp-playlist-item-artist">{{ data.meta.artist }}</span><# } #> </div> </script> <script type="text/html" id="tmpl-wp-playlist-item"> <div class="wp-playlist-item"> <a class="wp-playlist-caption" href="{{ data.src }}"> {{ data.index ? ( data.index + '. ' ) : '' }} <# if ( data.caption ) { #> {{ data.caption }} <# } else { #> <# if ( data.artists && data.meta.artist ) { #> <span class="wp-playlist-item-title"> <?php /* translators: %s: Playlist item title. */ printf( _x( '“%s”', 'playlist item title' ), '{{{ data.title }}}' ); ?> </span> <span class="wp-playlist-item-artist"> — {{ data.meta.artist }}</span> <# } else { #> <span class="wp-playlist-item-title">{{{ data.title }}}</span> <# } #> <# } #> </a> <# if ( data.meta.length_formatted ) { #> <div class="wp-playlist-item-length">{{ data.meta.length_formatted }}</div> <# } #> </div> </script> <?php } /** * Outputs and enqueues default scripts and styles for playlists. * * @since 3.9.0 * * @param string $type Type of playlist. Accepts 'audio' or 'video'. */ function wp_playlist_scripts( $type ) { wp_enqueue_style( 'wp-mediaelement' ); wp_enqueue_script( 'wp-playlist' ); add_action( 'wp_footer', 'wp_underscore_playlist_templates', 0 ); add_action( 'admin_footer', 'wp_underscore_playlist_templates', 0 ); } /** * Builds the Playlist shortcode output. * * This implements the functionality of the playlist shortcode for displaying * a collection of WordPress audio or video files in a post. * * @since 3.9.0 * * @global int $content_width * * @param array $attr { * Array of default playlist attributes. * * @type string $type Type of playlist to display. Accepts 'audio' or 'video'. Default 'audio'. * @type string $order Designates ascending or descending order of items in the playlist. * Accepts 'ASC', 'DESC'. Default 'ASC'. * @type string $orderby Any column, or columns, to sort the playlist. If $ids are * passed, this defaults to the order of the $ids array ('post__in'). * Otherwise default is 'menu_order ID'. * @type int $id If an explicit $ids array is not present, this parameter * will determine which attachments are used for the playlist. * Default is the current post ID. * @type array $ids Create a playlist out of these explicit attachment IDs. If empty, * a playlist will be created from all $type attachments of $id. * Default empty. * @type array $exclude List of specific attachment IDs to exclude from the playlist. Default empty. * @type string $style Playlist style to use. Accepts 'light' or 'dark'. Default 'light'. * @type bool $tracklist Whether to show or hide the playlist. Default true. * @type bool $tracknumbers Whether to show or hide the numbers next to entries in the playlist. Default true. * @type bool $images Show or hide the video or audio thumbnail (Featured Image/post * thumbnail). Default true. * @type bool $artists Whether to show or hide artist name in the playlist. Default true. * } * * @return string Playlist output. Empty string if the passed type is unsupported. */ function wp_playlist_shortcode( $attr ) { global $content_width; $post = get_post(); static $instance = 0; ++$instance; static $is_loaded = false; if ( ! empty( $attr['ids'] ) ) { // 'ids' is explicitly ordered, unless you specify otherwise. if ( empty( $attr['orderby'] ) ) { $attr['orderby'] = 'post__in'; } $attr['include'] = $attr['ids']; } /** * Filters the playlist output. * * Returning a non-empty value from the filter will short-circuit generation * of the default playlist output, returning the passed value instead. * * @since 3.9.0 * @since 4.2.0 The `$instance` parameter was added. * * @param string $output Playlist output. Default empty. * @param array $attr An array of shortcode attributes. * @param int $instance Unique numeric ID of this playlist shortcode instance. */ $output = apply_filters( 'post_playlist', '', $attr, $instance ); if ( ! empty( $output ) ) { return $output; } $atts = shortcode_atts( array( 'type' => 'audio', 'order' => 'ASC', 'orderby' => 'menu_order ID', 'id' => $post ? $post->ID : 0, 'include' => '', 'exclude' => '', 'style' => 'light', 'tracklist' => true, 'tracknumbers' => true, 'images' => true, 'artists' => true, ), $attr, 'playlist' ); $id = (int) $atts['id']; if ( 'audio' !== $atts['type'] ) { $atts['type'] = 'video'; } $args = array( 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => $atts['type'], 'order' => $atts['order'], 'orderby' => $atts['orderby'], ); if ( ! empty( $atts['include'] ) ) { $args['include'] = $atts['include']; $_attachments = get_posts( $args ); $attachments = array(); foreach ( $_attachments as $key => $val ) { $attachments[ $val->ID ] = $_attachments[ $key ]; } } elseif ( ! empty( $atts['exclude'] ) ) { $args['post_parent'] = $id; $args['exclude'] = $atts['exclude']; $attachments = get_children( $args ); } else { $args['post_parent'] = $id; $attachments = get_children( $args ); } if ( ! empty( $args['post_parent'] ) ) { $post_parent = get_post( $id ); // Terminate the shortcode execution if the user cannot read the post or it is password-protected. if ( ! current_user_can( 'read_post', $post_parent->ID ) || post_password_required( $post_parent ) ) { return ''; } } if ( empty( $attachments ) ) { return ''; } if ( is_feed() ) { $output = "\n"; foreach ( $attachments as $att_id => $attachment ) { $output .= wp_get_attachment_link( $att_id ) . "\n"; } return $output; } $outer = 22; // Default padding and border of wrapper. $default_width = 640; $default_height = 360; $theme_width = empty( $content_width ) ? $default_width : ( $content_width - $outer ); $theme_height = empty( $content_width ) ? $default_height : round( ( $default_height * $theme_width ) / $default_width ); $data = array( 'type' => $atts['type'], // Don't pass strings to JSON, will be truthy in JS. 'tracklist' => wp_validate_boolean( $atts['tracklist'] ), 'tracknumbers' => wp_validate_boolean( $atts['tracknumbers'] ), 'images' => wp_validate_boolean( $atts['images'] ), 'artists' => wp_validate_boolean( $atts['artists'] ), ); $tracks = array(); foreach ( $attachments as $attachment ) { $url = wp_get_attachment_url( $attachment->ID ); $ftype = wp_check_filetype( $url, wp_get_mime_types() ); $track = array( 'src' => $url, 'type' => $ftype['type'], 'title' => $attachment->post_title, 'caption' => $attachment->post_excerpt, 'description' => $attachment->post_content, ); $track['meta'] = array(); $meta = wp_get_attachment_metadata( $attachment->ID ); if ( ! empty( $meta ) ) { foreach ( wp_get_attachment_id3_keys( $attachment ) as $key => $label ) { if ( ! empty( $meta[ $key ] ) ) { $track['meta'][ $key ] = $meta[ $key ]; } } if ( 'video' === $atts['type'] ) { if ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ) { $width = $meta['width']; $height = $meta['height']; $theme_height = round( ( $height * $theme_width ) / $width ); } else { $width = $default_width; $height = $default_height; } $track['dimensions'] = array( 'original' => compact( 'width', 'height' ), 'resized' => array( 'width' => $theme_width, 'height' => $theme_height, ), ); } } if ( $atts['images'] ) { $thumb_id = get_post_thumbnail_id( $attachment->ID ); if ( ! empty( $thumb_id ) ) { list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'full' ); $track['image'] = compact( 'src', 'width', 'height' ); list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'thumbnail' ); $track['thumb'] = compact( 'src', 'width', 'height' ); } else { $src = wp_mime_type_icon( $attachment->ID, '.svg' ); $width = 48; $height = 64; $track['image'] = compact( 'src', 'width', 'height' ); $track['thumb'] = compact( 'src', 'width', 'height' ); } } $tracks[] = $track; } $data['tracks'] = $tracks; $safe_type = esc_attr( $atts['type'] ); $safe_style = esc_attr( $atts['style'] ); ob_start(); if ( ! $is_loaded ) { /** * Prints and enqueues playlist scripts, styles, and JavaScript templates. * * @since 3.9.0 * * @param string $type Type of playlist. Possible values are 'audio' or 'video'. * @param string $style The 'theme' for the playlist. Core provides 'light' and 'dark'. */ do_action( 'wp_playlist_scripts', $atts['type'], $atts['style'] ); $is_loaded = true; } ?> <div class="wp-playlist wp-<?php echo $safe_type; ?>-playlist wp-playlist-<?php echo $safe_style; ?>"> <?php if ( 'audio' === $atts['type'] ) : ?> <div class="wp-playlist-current-item"></div> <?php endif; ?> <<?php echo $safe_type; ?> controls="controls" preload="none" width="<?php echo (int) $theme_width; ?>" <?php if ( 'video' === $safe_type ) { echo ' height="', (int) $theme_height, '"'; } ?> ></<?php echo $safe_type; ?>> <div class="wp-playlist-next"></div> <div class="wp-playlist-prev"></div> <noscript> <ol> <?php foreach ( $attachments as $att_id => $attachment ) { printf( '<li>%s</li>', wp_get_attachment_link( $att_id ) ); } ?> </ol> </noscript> <script type="application/json" class="wp-playlist-script"><?php echo wp_json_encode( $data, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); ?></script> </div> <?php return ob_get_clean(); } add_shortcode( 'playlist', 'wp_playlist_shortcode' ); /** * Provides a No-JS Flash fallback as a last resort for audio / video. * * @since 3.6.0 * * @param string $url The media element URL. * @return string Fallback HTML. */ function wp_mediaelement_fallback( $url ) { /** * Filters the MediaElement fallback output for no-JS. * * @since 3.6.0 * * @param string $output Fallback output for no-JS. * @param string $url Media file URL. */ return apply_filters( 'wp_mediaelement_fallback', sprintf( '<a href="%1$s">%1$s</a>', esc_url( $url ) ), $url ); } /** * Returns a filtered list of supported audio formats. * * @since 3.6.0 * * @return string[] Supported audio formats. */ function wp_get_audio_extensions() { /** * Filters the list of supported audio formats. * * @since 3.6.0 * * @param string[] $extensions An array of supported audio formats. Defaults are * 'mp3', 'ogg', 'flac', 'm4a', 'wav'. */ return apply_filters( 'wp_audio_extensions', array( 'mp3', 'ogg', 'flac', 'm4a', 'wav' ) ); } /** * Returns useful keys to use to lookup data from an attachment's stored metadata. * * @since 3.9.0 * * @param WP_Post $attachment The current attachment, provided for context. * @param string $context Optional. The context. Accepts 'edit', 'display'. Default 'display'. * @return string[] Key/value pairs of field keys to labels. */ function wp_get_attachment_id3_keys( $attachment, $context = 'display' ) { $fields = array( 'artist' => __( 'Artist' ), 'album' => __( 'Album' ), ); if ( 'display' === $context ) { $fields['genre'] = __( 'Genre' ); $fields['year'] = __( 'Year' ); $fields['length_formatted'] = _x( 'Length', 'video or audio' ); } elseif ( 'js' === $context ) { $fields['bitrate'] = __( 'Bitrate' ); $fields['bitrate_mode'] = __( 'Bitrate Mode' ); } /** * Filters the editable list of keys to look up data from an attachment's metadata. * * @since 3.9.0 * * @param array $fields Key/value pairs of field keys to labels. * @param WP_Post $attachment Attachment object. * @param string $context The context. Accepts 'edit', 'display'. Default 'display'. */ return apply_filters( 'wp_get_attachment_id3_keys', $fields, $attachment, $context ); } /** * Builds the Audio shortcode output. * * This implements the functionality of the Audio Shortcode for displaying * WordPress mp3s in a post. * * @since 3.6.0 * @since 6.8.0 Added the 'muted' attribute. * * @param array $attr { * Attributes of the audio shortcode. * * @type string $src URL to the source of the audio file. Default empty. * @type string $loop The 'loop' attribute for the `<audio>` element. Default empty. * @type string $autoplay The 'autoplay' attribute for the `<audio>` element. Default empty. * @type string $muted The 'muted' attribute for the `<audio>` element. Default 'false'. * @type string $preload The 'preload' attribute for the `<audio>` element. Default 'none'. * @type string $class The 'class' attribute for the `<audio>` element. Default 'wp-audio-shortcode'. * @type string $style The 'style' attribute for the `<audio>` element. Default 'width: 100%;'. * } * @param string $content Shortcode content. * @return string|void HTML content to display audio. */ function wp_audio_shortcode( $attr, $content = '' ) { $post_id = get_post() ? get_the_ID() : 0; static $instance = 0; ++$instance; /** * Filters the default audio shortcode output. * * If the filtered output isn't empty, it will be used instead of generating the default audio template. * * @since 3.6.0 * * @param string $html Empty variable to be replaced with shortcode markup. * @param array $attr Attributes of the shortcode. See {@see wp_audio_shortcode()}. * @param string $content Shortcode content. * @param int $instance Unique numeric ID of this audio shortcode instance. */ $override = apply_filters( 'wp_audio_shortcode_override', '', $attr, $content, $instance ); if ( '' !== $override ) { return $override; } $audio = null; $default_types = wp_get_audio_extensions(); $defaults_atts = array( 'src' => '', 'loop' => '', 'autoplay' => '', 'muted' => 'false', 'preload' => 'none', 'class' => 'wp-audio-shortcode', 'style' => 'width: 100%;', ); foreach ( $default_types as $type ) { $defaults_atts[ $type ] = ''; } $atts = shortcode_atts( $defaults_atts, $attr, 'audio' ); $primary = false; if ( ! empty( $atts['src'] ) ) { $type = wp_check_filetype( $atts['src'], wp_get_mime_types() ); if ( ! in_array( strtolower( $type['ext'] ), $default_types, true ) ) { return sprintf( '<a class="wp-embedded-audio" href="%s">%s</a>', esc_url( $atts['src'] ), esc_html( $atts['src'] ) ); } $primary = true; array_unshift( $default_types, 'src' ); } else { foreach ( $default_types as $ext ) { if ( ! empty( $atts[ $ext ] ) ) { $type = wp_check_filetype( $atts[ $ext ], wp_get_mime_types() ); if ( strtolower( $type['ext'] ) === $ext ) { $primary = true; } } } } if ( ! $primary ) { $audios = get_attached_media( 'audio', $post_id ); if ( empty( $audios ) ) { return; } $audio = reset( $audios ); $atts['src'] = wp_get_attachment_url( $audio->ID ); if ( empty( $atts['src'] ) ) { return; } array_unshift( $default_types, 'src' ); } /** * Filters the media library used for the audio shortcode. * * @since 3.6.0 * * @param string $library Media library used for the audio shortcode. */ $library = apply_filters( 'wp_audio_shortcode_library', 'mediaelement' ); if ( 'mediaelement' === $library && did_action( 'init' ) ) { wp_enqueue_style( 'wp-mediaelement' ); wp_enqueue_script( 'wp-mediaelement' ); } /** * Filters the class attribute for the audio shortcode output container. * * @since 3.6.0 * @since 4.9.0 The `$atts` parameter was added. * * @param string $class CSS class or list of space-separated classes. * @param array $atts Array of audio shortcode attributes. */ $atts['class'] = apply_filters( 'wp_audio_shortcode_class', $atts['class'], $atts ); $html_atts = array( 'class' => $atts['class'], 'id' => sprintf( 'audio-%d-%d', $post_id, $instance ), 'loop' => wp_validate_boolean( $atts['loop'] ), 'autoplay' => wp_validate_boolean( $atts['autoplay'] ), 'muted' => wp_validate_boolean( $atts['muted'] ), 'preload' => $atts['preload'], 'style' => $atts['style'], ); // These ones should just be omitted altogether if they are blank. foreach ( array( 'loop', 'autoplay', 'preload', 'muted' ) as $a ) { if ( empty( $html_atts[ $a ] ) ) { unset( $html_atts[ $a ] ); } } $attr_strings = array(); foreach ( $html_atts as $attribute_name => $attribute_value ) { if ( in_array( $attribute_name, array( 'loop', 'autoplay', 'muted' ), true ) && true === $attribute_value ) { // Add boolean attributes without a value. $attr_strings[] = esc_attr( $attribute_name ); } elseif ( 'preload' === $attribute_name && ! empty( $attribute_value ) ) { // Handle the preload attribute with specific allowed values. $allowed_preload_values = array( 'none', 'metadata', 'auto' ); if ( in_array( $attribute_value, $allowed_preload_values, true ) ) { $attr_strings[] = sprintf( '%s="%s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) ); } } else { // For other attributes, include the value. $attr_strings[] = sprintf( '%s="%s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) ); } } $html = sprintf( '<audio %s controls="controls">', implode( ' ', $attr_strings ) ); $fileurl = ''; $source = '<source type="%s" src="%s" />'; foreach ( $default_types as $fallback ) { if ( ! empty( $atts[ $fallback ] ) ) { if ( empty( $fileurl ) ) { $fileurl = $atts[ $fallback ]; } $type = wp_check_filetype( $atts[ $fallback ], wp_get_mime_types() ); $url = add_query_arg( '_', $instance, $atts[ $fallback ] ); $html .= sprintf( $source, $type['type'], esc_url( $url ) ); } } if ( 'mediaelement' === $library ) { $html .= wp_mediaelement_fallback( $fileurl ); } $html .= '</audio>'; /** * Filters the audio shortcode output. * * @since 3.6.0 * * @param string $html Audio shortcode HTML output. * @param array $atts Array of audio shortcode attributes. * @param string $audio Audio file. * @param int $post_id Post ID. * @param string $library Media library used for the audio shortcode. */ return apply_filters( 'wp_audio_shortcode', $html, $atts, $audio, $post_id, $library ); } add_shortcode( 'audio', 'wp_audio_shortcode' ); /** * Returns a filtered list of supported video formats. * * @since 3.6.0 * * @return string[] List of supported video formats. */ function wp_get_video_extensions() { /** * Filters the list of supported video formats. * * @since 3.6.0 * * @param string[] $extensions An array of supported video formats. Defaults are * 'mp4', 'm4v', 'webm', 'ogv', 'flv'. */ return apply_filters( 'wp_video_extensions', array( 'mp4', 'm4v', 'webm', 'ogv', 'flv' ) ); } /** * Builds the Video shortcode output. * * This implements the functionality of the Video Shortcode for displaying * WordPress mp4s in a post. * * @since 3.6.0 * * @global int $content_width * * @param array $attr { * Attributes of the shortcode. * * @type string $src URL to the source of the video file. Default empty. * @type int $height Height of the video embed in pixels. Default 360. * @type int $width Width of the video embed in pixels. Default $content_width or 640. * @type string $poster The 'poster' attribute for the `<video>` element. Default empty. * @type string $loop The 'loop' attribute for the `<video>` element. Default empty. * @type string $autoplay The 'autoplay' attribute for the `<video>` element. Default empty. * @type string $muted The 'muted' attribute for the `<video>` element. Default false. * @type string $preload The 'preload' attribute for the `<video>` element. * Default 'metadata'. * @type string $class The 'class' attribute for the `<video>` element. * Default 'wp-video-shortcode'. * } * @param string $content Shortcode content. * @return string|void HTML content to display video. */ function wp_video_shortcode( $attr, $content = '' ) { global $content_width; $post_id = get_post() ? get_the_ID() : 0; static $instance = 0; ++$instance; /** * Filters the default video shortcode output. * * If the filtered output isn't empty, it will be used instead of generating * the default video template. * * @since 3.6.0 * * @see wp_video_shortcode() * * @param string $html Empty variable to be replaced with shortcode markup. * @param array $attr Attributes of the shortcode. See {@see wp_video_shortcode()}. * @param string $content Video shortcode content. * @param int $instance Unique numeric ID of this video shortcode instance. */ $override = apply_filters( 'wp_video_shortcode_override', '', $attr, $content, $instance ); if ( '' !== $override ) { return $override; } $video = null; $default_types = wp_get_video_extensions(); $defaults_atts = array( 'src' => '', 'poster' => '', 'loop' => '', 'autoplay' => '', 'muted' => 'false', 'preload' => 'metadata', 'width' => 640, 'height' => 360, 'class' => 'wp-video-shortcode', ); foreach ( $default_types as $type ) { $defaults_atts[ $type ] = ''; } $atts = shortcode_atts( $defaults_atts, $attr, 'video' ); if ( is_admin() ) { // Shrink the video so it isn't huge in the admin. if ( $atts['width'] > $defaults_atts['width'] ) { $atts['height'] = round( ( $atts['height'] * $defaults_atts['width'] ) / $atts['width'] ); $atts['width'] = $defaults_atts['width']; } } else { // If the video is bigger than the theme. if ( ! empty( $content_width ) && $atts['width'] > $content_width ) { $atts['height'] = round( ( $atts['height'] * $content_width ) / $atts['width'] ); $atts['width'] = $content_width; } } $is_vimeo = false; $is_youtube = false; $yt_pattern = '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#'; $vimeo_pattern = '#^https?://(.+\.)?vimeo\.com/.*#'; $primary = false; if ( ! empty( $atts['src'] ) ) { $is_vimeo = ( preg_match( $vimeo_pattern, $atts['src'] ) ); $is_youtube = ( preg_match( $yt_pattern, $atts['src'] ) ); if ( ! $is_youtube && ! $is_vimeo ) { $type = wp_check_filetype( $atts['src'], wp_get_mime_types() ); if ( ! in_array( strtolower( $type['ext'] ), $default_types, true ) ) { return sprintf( '<a class="wp-embedded-video" href="%s">%s</a>', esc_url( $atts['src'] ), esc_html( $atts['src'] ) ); } } if ( $is_vimeo ) { wp_enqueue_script( 'mediaelement-vimeo' ); } $primary = true; array_unshift( $default_types, 'src' ); } else { foreach ( $default_types as $ext ) { if ( ! empty( $atts[ $ext ] ) ) { $type = wp_check_filetype( $atts[ $ext ], wp_get_mime_types() ); if ( strtolower( $type['ext'] ) === $ext ) { $primary = true; } } } } if ( ! $primary ) { $videos = get_attached_media( 'video', $post_id ); if ( empty( $videos ) ) { return; } $video = reset( $videos ); $atts['src'] = wp_get_attachment_url( $video->ID ); if ( empty( $atts['src'] ) ) { return; } array_unshift( $default_types, 'src' ); } /** * Filters the media library used for the video shortcode. * * @since 3.6.0 * * @param string $library Media library used for the video shortcode. */ $library = apply_filters( 'wp_video_shortcode_library', 'mediaelement' ); if ( 'mediaelement' === $library && did_action( 'init' ) ) { wp_enqueue_style( 'wp-mediaelement' ); wp_enqueue_script( 'wp-mediaelement' ); wp_enqueue_script( 'mediaelement-vimeo' ); } /* * MediaElement.js has issues with some URL formats for Vimeo and YouTube, * so update the URL to prevent the ME.js player from breaking. */ if ( 'mediaelement' === $library ) { if ( $is_youtube ) { // Remove `feature` query arg and force SSL - see #40866. $atts['src'] = remove_query_arg( 'feature', $atts['src'] ); $atts['src'] = set_url_scheme( $atts['src'], 'https' ); } elseif ( $is_vimeo ) { // Remove all query arguments and force SSL - see #40866. $parsed_vimeo_url = wp_parse_url( $atts['src'] ); $vimeo_src = 'https://' . $parsed_vimeo_url['host'] . $parsed_vimeo_url['path']; // Add loop param for mejs bug - see #40977, not needed after #39686. $loop = $atts['loop'] ? '1' : '0'; $atts['src'] = add_query_arg( 'loop', $loop, $vimeo_src ); } } /** * Filters the class attribute for the video shortcode output container. * * @since 3.6.0 * @since 4.9.0 The `$atts` parameter was added. * * @param string $class CSS class or list of space-separated classes. * @param array $atts Array of video shortcode attributes. */ $atts['class'] = apply_filters( 'wp_video_shortcode_class', $atts['class'], $atts ); $html_atts = array( 'class' => $atts['class'], 'id' => sprintf( 'video-%d-%d', $post_id, $instance ), 'width' => absint( $atts['width'] ), 'height' => absint( $atts['height'] ), 'poster' => esc_url( $atts['poster'] ), 'loop' => wp_validate_boolean( $atts['loop'] ), 'autoplay' => wp_validate_boolean( $atts['autoplay'] ), 'muted' => wp_validate_boolean( $atts['muted'] ), 'preload' => $atts['preload'], ); // These ones should just be omitted altogether if they are blank. foreach ( array( 'poster', 'loop', 'autoplay', 'preload', 'muted' ) as $a ) { if ( empty( $html_atts[ $a ] ) ) { unset( $html_atts[ $a ] ); } } $attr_strings = array(); foreach ( $html_atts as $attribute_name => $attribute_value ) { if ( in_array( $attribute_name, array( 'loop', 'autoplay', 'muted' ), true ) && true === $attribute_value ) { // Add boolean attributes without their value for true. $attr_strings[] = esc_attr( $attribute_name ); } elseif ( 'preload' === $attribute_name && ! empty( $attribute_value ) ) { // Handle the preload attribute with specific allowed values. $allowed_preload_values = array( 'none', 'metadata', 'auto' ); if ( in_array( $attribute_value, $allowed_preload_values, true ) ) { $attr_strings[] = sprintf( '%s="%s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) ); } } elseif ( ! empty( $attribute_value ) ) { // For non-boolean attributes, add them with their value. $attr_strings[] = sprintf( '%s="%s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) ); } } $html = sprintf( '<video %s controls="controls">', implode( ' ', $attr_strings ) ); $fileurl = ''; $source = '<source type="%s" src="%s" />'; foreach ( $default_types as $fallback ) { if ( ! empty( $atts[ $fallback ] ) ) { if ( empty( $fileurl ) ) { $fileurl = $atts[ $fallback ]; } if ( 'src' === $fallback && $is_youtube ) { $type = array( 'type' => 'video/youtube' ); } elseif ( 'src' === $fallback && $is_vimeo ) { $type = array( 'type' => 'video/vimeo' ); } else { $type = wp_check_filetype( $atts[ $fallback ], wp_get_mime_types() ); } $url = add_query_arg( '_', $instance, $atts[ $fallback ] ); $html .= sprintf( $source, $type['type'], esc_url( $url ) ); } } if ( ! empty( $content ) ) { if ( str_contains( $content, "\n" ) ) { $content = str_replace( array( "\r\n", "\n", "\t" ), '', $content ); } $html .= trim( $content ); } if ( 'mediaelement' === $library ) { $html .= wp_mediaelement_fallback( $fileurl ); } $html .= '</video>'; $width_rule = ''; if ( ! empty( $atts['width'] ) ) { $width_rule = sprintf( 'width: %dpx;', $atts['width'] ); } $output = sprintf( '<div style="%s" class="wp-video">%s</div>', $width_rule, $html ); /** * Filters the output of the video shortcode. * * @since 3.6.0 * * @param string $output Video shortcode HTML output. * @param array $atts Array of video shortcode attributes. * @param string $video Video file. * @param int $post_id Post ID. * @param string $library Media library used for the video shortcode. */ return apply_filters( 'wp_video_shortcode', $output, $atts, $video, $post_id, $library ); } add_shortcode( 'video', 'wp_video_shortcode' ); /** * Gets the previous image link that has the same post parent. * * @since 5.8.0 * * @see get_adjacent_image_link() * * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'thumbnail'. * @param string|false $text Optional. Link text. Default false. * @return string Markup for previous image link. */ function get_previous_image_link( $size = 'thumbnail', $text = false ) { return get_adjacent_image_link( true, $size, $text ); } /** * Displays previous image link that has the same post parent. * * @since 2.5.0 * * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'thumbnail'. * @param string|false $text Optional. Link text. Default false. */ function previous_image_link( $size = 'thumbnail', $text = false ) { echo get_previous_image_link( $size, $text ); } /** * Gets the next image link that has the same post parent. * * @since 5.8.0 * * @see get_adjacent_image_link() * * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'thumbnail'. * @param string|false $text Optional. Link text. Default false. * @return string Markup for next image link. */ function get_next_image_link( $size = 'thumbnail', $text = false ) { return get_adjacent_image_link( false, $size, $text ); } /** * Displays next image link that has the same post parent. * * @since 2.5.0 * * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'thumbnail'. * @param string|false $text Optional. Link text. Default false. */ function next_image_link( $size = 'thumbnail', $text = false ) { echo get_next_image_link( $size, $text ); } /** * Gets the next or previous image link that has the same post parent. * * Retrieves the current attachment object from the $post global. * * @since 5.8.0 * * @param bool $prev Optional. Whether to display the next (false) or previous (true) link. Default true. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'thumbnail'. * @param bool $text Optional. Link text. Default false. * @return string Markup for image link. */ function get_adjacent_image_link( $prev = true, $size = 'thumbnail', $text = false ) { $post = get_post(); $attachments = array_values( get_children( array( 'post_parent' => $post->post_parent, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID', ) ) ); foreach ( $attachments as $k => $attachment ) { if ( (int) $attachment->ID === (int) $post->ID ) { break; } } $output = ''; $attachment_id = 0; if ( $attachments ) { $k = $prev ? $k - 1 : $k + 1; if ( isset( $attachments[ $k ] ) ) { $attachment_id = $attachments[ $k ]->ID; $attr = array( 'alt' => get_the_title( $attachment_id ) ); $output = wp_get_attachment_link( $attachment_id, $size, true, false, $text, $attr ); } } $adjacent = $prev ? 'previous' : 'next'; /** * Filters the adjacent image link. * * The dynamic portion of the hook name, `$adjacent`, refers to the type of adjacency, * either 'next', or 'previous'. * * Possible hook names include: * * - `next_image_link` * - `previous_image_link` * * @since 3.5.0 * * @param string $output Adjacent image HTML markup. * @param int $attachment_id Attachment ID * @param string|int[] $size Requested image size. Can be any registered image size name, or * an array of width and height values in pixels (in that order). * @param string $text Link text. */ return apply_filters( "{$adjacent}_image_link", $output, $attachment_id, $size, $text ); } /** * Displays next or previous image link that has the same post parent. * * Retrieves the current attachment object from the $post global. * * @since 2.5.0 * * @param bool $prev Optional. Whether to display the next (false) or previous (true) link. Default true. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'thumbnail'. * @param bool $text Optional. Link text. Default false. */ function adjacent_image_link( $prev = true, $size = 'thumbnail', $text = false ) { echo get_adjacent_image_link( $prev, $size, $text ); } /** * Retrieves taxonomies attached to given the attachment. * * @since 2.5.0 * @since 4.7.0 Introduced the `$output` parameter. * * @param int|array|object $attachment Attachment ID, data array, or data object. * @param string $output Output type. 'names' to return an array of taxonomy names, * or 'objects' to return an array of taxonomy objects. * Default is 'names'. * @return string[]|WP_Taxonomy[] List of taxonomies or taxonomy names. Empty array on failure. */ function get_attachment_taxonomies( $attachment, $output = 'names' ) { if ( is_int( $attachment ) ) { $attachment = get_post( $attachment ); } elseif ( is_array( $attachment ) ) { $attachment = (object) $attachment; } if ( ! is_object( $attachment ) ) { return array(); } $file = get_attached_file( $attachment->ID ); $filename = wp_basename( $file ); $objects = array( 'attachment' ); if ( str_contains( $filename, '.' ) ) { $objects[] = 'attachment:' . substr( $filename, strrpos( $filename, '.' ) + 1 ); } if ( ! empty( $attachment->post_mime_type ) ) { $objects[] = 'attachment:' . $attachment->post_mime_type; if ( str_contains( $attachment->post_mime_type, '/' ) ) { foreach ( explode( '/', $attachment->post_mime_type ) as $token ) { if ( ! empty( $token ) ) { $objects[] = "attachment:$token"; } } } } $taxonomies = array(); foreach ( $objects as $object ) { $taxes = get_object_taxonomies( $object, $output ); if ( $taxes ) { $taxonomies = array_merge( $taxonomies, $taxes ); } } if ( 'names' === $output ) { $taxonomies = array_unique( $taxonomies ); } return $taxonomies; } /** * Retrieves all of the taxonomies that are registered for attachments. * * Handles mime-type-specific taxonomies such as attachment:image and attachment:video. * * @since 3.5.0 * * @see get_taxonomies() * * @param string $output Optional. The type of taxonomy output to return. Accepts 'names' or 'objects'. * Default 'names'. * @return string[]|WP_Taxonomy[] Array of names or objects of registered taxonomies for attachments. */ function get_taxonomies_for_attachments( $output = 'names' ) { $taxonomies = array(); foreach ( get_taxonomies( array(), 'objects' ) as $taxonomy ) { foreach ( $taxonomy->object_type as $object_type ) { if ( 'attachment' === $object_type || str_starts_with( $object_type, 'attachment:' ) ) { if ( 'names' === $output ) { $taxonomies[] = $taxonomy->name; } else { $taxonomies[ $taxonomy->name ] = $taxonomy; } break; } } } return $taxonomies; } /** * Determines whether the value is an acceptable type for GD image functions. * * In PHP 8.0, the GD extension uses GdImage objects for its data structures. * This function checks if the passed value is either a GdImage object instance * or a resource of type `gd`. Any other type will return false. * * @since 5.6.0 * * @param resource|GdImage|false $image A value to check the type for. * @return bool True if `$image` is either a GD image resource or a GdImage instance, * false otherwise. */ function is_gd_image( $image ) { if ( $image instanceof GdImage || is_resource( $image ) && 'gd' === get_resource_type( $image ) ) { return true; } return false; } /** * Creates a new GD image resource with transparency support. * * @todo Deprecate if possible. * * @since 2.9.0 * * @param int $width Image width in pixels. * @param int $height Image height in pixels. * @return resource|GdImage|false The GD image resource or GdImage instance on success. * False on failure. */ function wp_imagecreatetruecolor( $width, $height ) { $img = imagecreatetruecolor( $width, $height ); if ( is_gd_image( $img ) && function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' ) ) { imagealphablending( $img, false ); imagesavealpha( $img, true ); } return $img; } /** * Based on a supplied width/height example, returns the biggest possible dimensions based on the max width/height. * * @since 2.9.0 * * @see wp_constrain_dimensions() * * @param int $example_width The width of an example embed. * @param int $example_height The height of an example embed. * @param int $max_width The maximum allowed width. * @param int $max_height The maximum allowed height. * @return int[] { * An array of maximum width and height values. * * @type int $0 The maximum width in pixels. * @type int $1 The maximum height in pixels. * } */ function wp_expand_dimensions( $example_width, $example_height, $max_width, $max_height ) { $example_width = (int) $example_width; $example_height = (int) $example_height; $max_width = (int) $max_width; $max_height = (int) $max_height; return wp_constrain_dimensions( $example_width * 1000000, $example_height * 1000000, $max_width, $max_height ); } /** * Determines the maximum upload size allowed in php.ini. * * @since 2.5.0 * * @return int Allowed upload size. */ function wp_max_upload_size() { $u_bytes = wp_convert_hr_to_bytes( ini_get( 'upload_max_filesize' ) ); $p_bytes = wp_convert_hr_to_bytes( ini_get( 'post_max_size' ) ); /** * Filters the maximum upload size allowed in php.ini. * * @since 2.5.0 * * @param int $size Max upload size limit in bytes. * @param int $u_bytes Maximum upload filesize in bytes. * @param int $p_bytes Maximum size of POST data in bytes. */ return apply_filters( 'upload_size_limit', min( $u_bytes, $p_bytes ), $u_bytes, $p_bytes ); } /** * Returns a WP_Image_Editor instance and loads file into it. * * @since 3.5.0 * * @param string $path Path to the file to load. * @param array $args Optional. Additional arguments for retrieving the image editor. * Default empty array. * @return WP_Image_Editor|WP_Error The WP_Image_Editor object on success, * a WP_Error object otherwise. */ function wp_get_image_editor( $path, $args = array() ) { $args['path'] = $path; // If the mime type is not set in args, try to extract and set it from the file. if ( ! isset( $args['mime_type'] ) ) { $file_info = wp_check_filetype( $args['path'] ); /* * If $file_info['type'] is false, then we let the editor attempt to * figure out the file type, rather than forcing a failure based on extension. */ if ( isset( $file_info ) && $file_info['type'] ) { $args['mime_type'] = $file_info['type']; } } // Check and set the output mime type mapped to the input type. if ( isset( $args['mime_type'] ) ) { $output_format = wp_get_image_editor_output_format( $path, $args['mime_type'] ); if ( isset( $output_format[ $args['mime_type'] ] ) ) { $args['output_mime_type'] = $output_format[ $args['mime_type'] ]; } } $implementation = _wp_image_editor_choose( $args ); if ( $implementation ) { $editor = new $implementation( $path ); $loaded = $editor->load(); if ( is_wp_error( $loaded ) ) { return $loaded; } return $editor; } return new WP_Error( 'image_no_editor', __( 'No editor could be selected.' ) ); } /** * Tests whether there is an editor that supports a given mime type or methods. * * @since 3.5.0 * * @param string|array $args Optional. Array of arguments to retrieve the image editor supports. * Default empty array. * @return bool True if an eligible editor is found; false otherwise. */ function wp_image_editor_supports( $args = array() ) { return (bool) _wp_image_editor_choose( $args ); } /** * Tests which editors are capable of supporting the request. * * @ignore * @since 3.5.0 * * @param array $args Optional. Array of arguments for choosing a capable editor. Default empty array. * @return string|false Class name for the first editor that claims to support the request. * False if no editor claims to support the request. */ function _wp_image_editor_choose( $args = array() ) { require_once ABSPATH . WPINC . '/class-wp-image-editor.php'; require_once ABSPATH . WPINC . '/class-wp-image-editor-gd.php'; require_once ABSPATH . WPINC . '/class-wp-image-editor-imagick.php'; require_once ABSPATH . WPINC . '/class-avif-info.php'; /** * Filters the list of image editing library classes. * * @since 3.5.0 * * @param string[] $image_editors Array of available image editor class names. Defaults are * 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD'. */ $implementations = apply_filters( 'wp_image_editors', array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ) ); $editors = wp_cache_get( 'wp_image_editor_choose', 'image_editor' ); if ( ! is_array( $editors ) ) { $editors = array(); } // Cache the chosen editor implementation based on specific args and available implementations. $cache_key = md5( serialize( array( $args, $implementations ) ) ); if ( isset( $editors[ $cache_key ] ) ) { return $editors[ $cache_key ]; } // Assume no support until a capable implementation is identified. $editor = false; foreach ( $implementations as $implementation ) { if ( ! call_user_func( array( $implementation, 'test' ), $args ) ) { continue; } // Implementation should support the passed mime type. if ( isset( $args['mime_type'] ) && ! call_user_func( array( $implementation, 'supports_mime_type' ), $args['mime_type'] ) ) { continue; } // Implementation should support requested methods. if ( isset( $args['methods'] ) && array_diff( $args['methods'], get_class_methods( $implementation ) ) ) { continue; } // Implementation should ideally support the output mime type as well if set and different than the passed type. if ( isset( $args['mime_type'] ) && isset( $args['output_mime_type'] ) && $args['mime_type'] !== $args['output_mime_type'] && ! call_user_func( array( $implementation, 'supports_mime_type' ), $args['output_mime_type'] ) ) { /* * This implementation supports the input type but not the output type. * Keep looking to see if we can find an implementation that supports both. */ $editor = $implementation; continue; } // Favor the implementation that supports both input and output mime types. $editor = $implementation; break; } $editors[ $cache_key ] = $editor; wp_cache_set( 'wp_image_editor_choose', $editors, 'image_editor', DAY_IN_SECONDS ); return $editor; } /** * Prints default Plupload arguments. * * @since 3.4.0 */ function wp_plupload_default_settings() { $wp_scripts = wp_scripts(); $data = $wp_scripts->get_data( 'wp-plupload', 'data' ); if ( $data && str_contains( $data, '_wpPluploadSettings' ) ) { return; } $max_upload_size = wp_max_upload_size(); $allowed_extensions = array_keys( get_allowed_mime_types() ); $extensions = array(); foreach ( $allowed_extensions as $extension ) { $extensions = array_merge( $extensions, explode( '|', $extension ) ); } /* * Since 4.9 the `runtimes` setting is hardcoded in our version of Plupload to `html5,html4`, * and the `flash_swf_url` and `silverlight_xap_url` are not used. */ $defaults = array( 'file_data_name' => 'async-upload', // Key passed to $_FILE. 'url' => admin_url( 'async-upload.php', 'relative' ), 'filters' => array( 'max_file_size' => $max_upload_size . 'b', 'mime_types' => array( array( 'extensions' => implode( ',', $extensions ) ) ), ), ); /* * Currently only iOS Safari supports multiple files uploading, * but iOS 7.x has a bug that prevents uploading of videos when enabled. * See #29602. */ if ( wp_is_mobile() && str_contains( $_SERVER['HTTP_USER_AGENT'], 'OS 7_' ) && str_contains( $_SERVER['HTTP_USER_AGENT'], 'like Mac OS X' ) ) { $defaults['multi_selection'] = false; } // Check if WebP images can be edited. if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) { $defaults['webp_upload_error'] = true; } // Check if AVIF images can be edited. if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) { $defaults['avif_upload_error'] = true; } // Check if HEIC images can be edited. if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) { $defaults['heic_upload_error'] = true; } /** * Filters the Plupload default settings. * * @since 3.4.0 * * @param array $defaults Default Plupload settings array. */ $defaults = apply_filters( 'plupload_default_settings', $defaults ); $params = array( 'action' => 'upload-attachment', ); /** * Filters the Plupload default parameters. * * @since 3.4.0 * * @param array $params Default Plupload parameters array. */ $params = apply_filters( 'plupload_default_params', $params ); $params['_wpnonce'] = wp_create_nonce( 'media-form' ); $defaults['multipart_params'] = $params; $settings = array( 'defaults' => $defaults, 'browser' => array( 'mobile' => wp_is_mobile(), 'supported' => _device_can_upload(), ), 'limitExceeded' => is_multisite() && ! is_upload_space_available(), ); $script = 'var _wpPluploadSettings = ' . wp_json_encode( $settings, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';'; if ( $data ) { $script = "$data\n$script"; } $wp_scripts->add_data( 'wp-plupload', 'data', $script ); } /** * Prepares an attachment post object for JS, where it is expected * to be JSON-encoded and fit into an Attachment model. * * @since 3.5.0 * * @param int|WP_Post $attachment Attachment ID or object. * @return array|void { * Array of attachment details, or void if the parameter does not correspond to an attachment. * * @type string $alt Alt text of the attachment. * @type string $author ID of the attachment author, as a string. * @type string $authorName Name of the attachment author. * @type string $caption Caption for the attachment. * @type array $compat Containing item and meta. * @type string $context Context, whether it's used as the site icon for example. * @type int $date Uploaded date, timestamp in milliseconds. * @type string $dateFormatted Formatted date (e.g. June 29, 2018). * @type string $description Description of the attachment. * @type string $editLink URL to the edit page for the attachment. * @type string $filename File name of the attachment. * @type string $filesizeHumanReadable Filesize of the attachment in human readable format (e.g. 1 MB). * @type int $filesizeInBytes Filesize of the attachment in bytes. * @type int $height If the attachment is an image, represents the height of the image in pixels. * @type string $icon Icon URL of the attachment (e.g. /wp-includes/images/media/archive.png). * @type int $id ID of the attachment. * @type string $link URL to the attachment. * @type int $menuOrder Menu order of the attachment post. * @type array $meta Meta data for the attachment. * @type string $mime Mime type of the attachment (e.g. image/jpeg or application/zip). * @type int $modified Last modified, timestamp in milliseconds. * @type string $name Name, same as title of the attachment. * @type array $nonces Nonces for update, delete and edit. * @type string $orientation If the attachment is an image, represents the image orientation * (landscape or portrait). * @type array $sizes If the attachment is an image, contains an array of arrays * for the images sizes: thumbnail, medium, large, and full. * @type string $status Post status of the attachment (usually 'inherit'). * @type string $subtype Mime subtype of the attachment (usually the last part, e.g. jpeg or zip). * @type string $title Title of the attachment (usually slugified file name without the extension). * @type string $type Type of the attachment (usually first part of the mime type, e.g. image). * @type int $uploadedTo Parent post to which the attachment was uploaded. * @type string $uploadedToLink URL to the edit page of the parent post of the attachment. * @type string $uploadedToTitle Post title of the parent of the attachment. * @type string $url Direct URL to the attachment file (from wp-content). * @type int $width If the attachment is an image, represents the width of the image in pixels. * } */ function wp_prepare_attachment_for_js( $attachment ) { $attachment = get_post( $attachment ); if ( ! $attachment ) { return; } if ( 'attachment' !== $attachment->post_type ) { return; } $meta = wp_get_attachment_metadata( $attachment->ID ); if ( str_contains( $attachment->post_mime_type, '/' ) ) { list( $type, $subtype ) = explode( '/', $attachment->post_mime_type ); } else { list( $type, $subtype ) = array( $attachment->post_mime_type, '' ); } $attachment_url = wp_get_attachment_url( $attachment->ID ); $base_url = str_replace( wp_basename( $attachment_url ), '', $attachment_url ); $response = array( 'id' => $attachment->ID, 'title' => $attachment->post_title, 'filename' => wp_basename( get_attached_file( $attachment->ID ) ), 'url' => $attachment_url, 'link' => get_attachment_link( $attachment->ID ), 'alt' => get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ), 'author' => $attachment->post_author, 'description' => $attachment->post_content, 'caption' => $attachment->post_excerpt, 'name' => $attachment->post_name, 'status' => $attachment->post_status, 'uploadedTo' => $attachment->post_parent, 'date' => strtotime( $attachment->post_date_gmt ) * 1000, 'modified' => strtotime( $attachment->post_modified_gmt ) * 1000, 'menuOrder' => $attachment->menu_order, 'mime' => $attachment->post_mime_type, 'type' => $type, 'subtype' => $subtype, 'icon' => wp_mime_type_icon( $attachment->ID, '.svg' ), 'dateFormatted' => mysql2date( __( 'F j, Y' ), $attachment->post_date ), 'nonces' => array( 'update' => false, 'delete' => false, 'edit' => false, ), 'editLink' => false, 'meta' => false, ); $author = new WP_User( $attachment->post_author ); if ( $author->exists() ) { $author_name = $author->display_name ? $author->display_name : $author->nickname; $response['authorName'] = html_entity_decode( $author_name, ENT_QUOTES, get_bloginfo( 'charset' ) ); $response['authorLink'] = get_edit_user_link( $author->ID ); } else { $response['authorName'] = __( '(no author)' ); } if ( $attachment->post_parent ) { $post_parent = get_post( $attachment->post_parent ); if ( $post_parent && current_user_can( 'read_post', $attachment->post_parent ) ) { $response['uploadedToTitle'] = $post_parent->post_title ? $post_parent->post_title : __( '(no title)' ); $response['uploadedToLink'] = get_edit_post_link( $attachment->post_parent, 'raw' ); } } $attached_file = get_attached_file( $attachment->ID ); if ( isset( $meta['filesize'] ) ) { $bytes = $meta['filesize']; } elseif ( file_exists( $attached_file ) ) { $bytes = wp_filesize( $attached_file ); } else { $bytes = ''; } if ( $bytes ) { $response['filesizeInBytes'] = $bytes; $response['filesizeHumanReadable'] = size_format( $bytes ); } $context = get_post_meta( $attachment->ID, '_wp_attachment_context', true ); $response['context'] = ( $context ) ? $context : ''; if ( current_user_can( 'edit_post', $attachment->ID ) ) { $response['nonces']['update'] = wp_create_nonce( 'update-post_' . $attachment->ID ); $response['nonces']['edit'] = wp_create_nonce( 'image_editor-' . $attachment->ID ); $response['editLink'] = get_edit_post_link( $attachment->ID, 'raw' ); } if ( current_user_can( 'delete_post', $attachment->ID ) ) { $response['nonces']['delete'] = wp_create_nonce( 'delete-post_' . $attachment->ID ); } if ( $meta && ( 'image' === $type || ! empty( $meta['sizes'] ) ) ) { $sizes = array(); /** This filter is documented in wp-admin/includes/media.php */ $possible_sizes = apply_filters( 'image_size_names_choose', array( 'thumbnail' => __( 'Thumbnail' ), 'medium' => __( 'Medium' ), 'large' => __( 'Large' ), 'full' => __( 'Full Size' ), ) ); unset( $possible_sizes['full'] ); /* * Loop through all potential sizes that may be chosen. Try to do this with some efficiency. * First: run the image_downsize filter. If it returns something, we can use its data. * If the filter does not return something, then image_downsize() is just an expensive way * to check the image metadata, which we do second. */ foreach ( $possible_sizes as $size => $label ) { /** This filter is documented in wp-includes/media.php */ $downsize = apply_filters( 'image_downsize', false, $attachment->ID, $size ); if ( $downsize ) { if ( empty( $downsize[3] ) ) { continue; } $sizes[ $size ] = array( 'height' => $downsize[2], 'width' => $downsize[1], 'url' => $downsize[0], 'orientation' => $downsize[2] > $downsize[1] ? 'portrait' : 'landscape', ); } elseif ( isset( $meta['sizes'][ $size ] ) ) { // Nothing from the filter, so consult image metadata if we have it. $size_meta = $meta['sizes'][ $size ]; /* * We have the actual image size, but might need to further constrain it if content_width is narrower. * Thumbnail, medium, and full sizes are also checked against the site's height/width options. */ list( $width, $height ) = image_constrain_size_for_editor( $size_meta['width'], $size_meta['height'], $size, 'edit' ); $sizes[ $size ] = array( 'height' => $height, 'width' => $width, 'url' => $base_url . $size_meta['file'], 'orientation' => $height > $width ? 'portrait' : 'landscape', ); } } if ( 'image' === $type ) { if ( ! empty( $meta['original_image'] ) ) { $response['originalImageURL'] = wp_get_original_image_url( $attachment->ID ); $response['originalImageName'] = wp_basename( wp_get_original_image_path( $attachment->ID ) ); } $sizes['full'] = array( 'url' => $attachment_url ); if ( isset( $meta['height'], $meta['width'] ) ) { $sizes['full']['height'] = $meta['height']; $sizes['full']['width'] = $meta['width']; $sizes['full']['orientation'] = $meta['height'] > $meta['width'] ? 'portrait' : 'landscape'; } $response = array_merge( $response, $sizes['full'] ); } elseif ( $meta['sizes']['full']['file'] ) { $sizes['full'] = array( 'url' => $base_url . $meta['sizes']['full']['file'], 'height' => $meta['sizes']['full']['height'], 'width' => $meta['sizes']['full']['width'], 'orientation' => $meta['sizes']['full']['height'] > $meta['sizes']['full']['width'] ? 'portrait' : 'landscape', ); } $response = array_merge( $response, array( 'sizes' => $sizes ) ); } if ( $meta && 'video' === $type ) { if ( isset( $meta['width'] ) ) { $response['width'] = (int) $meta['width']; } if ( isset( $meta['height'] ) ) { $response['height'] = (int) $meta['height']; } } if ( $meta && ( 'audio' === $type || 'video' === $type ) ) { if ( isset( $meta['length_formatted'] ) ) { $response['fileLength'] = $meta['length_formatted']; $response['fileLengthHumanReadable'] = human_readable_duration( $meta['length_formatted'] ); } $response['meta'] = array(); foreach ( wp_get_attachment_id3_keys( $attachment, 'js' ) as $key => $label ) { $response['meta'][ $key ] = false; if ( ! empty( $meta[ $key ] ) ) { $response['meta'][ $key ] = $meta[ $key ]; } } $id = get_post_thumbnail_id( $attachment->ID ); if ( ! empty( $id ) ) { list( $src, $width, $height ) = wp_get_attachment_image_src( $id, 'full' ); $response['image'] = compact( 'src', 'width', 'height' ); list( $src, $width, $height ) = wp_get_attachment_image_src( $id, 'thumbnail' ); $response['thumb'] = compact( 'src', 'width', 'height' ); } else { $src = wp_mime_type_icon( $attachment->ID, '.svg' ); $width = 48; $height = 64; $response['image'] = compact( 'src', 'width', 'height' ); $response['thumb'] = compact( 'src', 'width', 'height' ); } } if ( function_exists( 'get_compat_media_markup' ) ) { $response['compat'] = get_compat_media_markup( $attachment->ID, array( 'in_modal' => true ) ); } if ( function_exists( 'get_media_states' ) ) { $media_states = get_media_states( $attachment ); if ( ! empty( $media_states ) ) { $response['mediaStates'] = implode( ', ', $media_states ); } } /** * Filters the attachment data prepared for JavaScript. * * @since 3.5.0 * * @param array $response Array of prepared attachment data. See {@see wp_prepare_attachment_for_js()}. * @param WP_Post $attachment Attachment object. * @param array|false $meta Array of attachment meta data, or false if there is none. */ return apply_filters( 'wp_prepare_attachment_for_js', $response, $attachment, $meta ); } /** * Enqueues all scripts, styles, settings, and templates necessary to use * all media JS APIs. * * @since 3.5.0 * * @global int $content_width * @global wpdb $wpdb WordPress database abstraction object. * @global WP_Locale $wp_locale WordPress date and time locale object. * * @param array $args { * Arguments for enqueuing media scripts. * * @type int|WP_Post $post Post ID or post object. * } */ function wp_enqueue_media( $args = array() ) { // Enqueue me just once per page, please. if ( did_action( 'wp_enqueue_media' ) ) { return; } global $content_width, $wpdb, $wp_locale; $defaults = array( 'post' => null, ); $args = wp_parse_args( $args, $defaults ); /* * We're going to pass the old thickbox media tabs to `media_upload_tabs` * to ensure plugins will work. We will then unset those tabs. */ $tabs = array( // handler action suffix => tab label 'type' => '', 'type_url' => '', 'gallery' => '', 'library' => '', ); /** This filter is documented in wp-admin/includes/media.php */ $tabs = apply_filters( 'media_upload_tabs', $tabs ); unset( $tabs['type'], $tabs['type_url'], $tabs['gallery'], $tabs['library'] ); $props = array( 'link' => get_option( 'image_default_link_type' ), // DB default is 'file'. 'align' => get_option( 'image_default_align' ), // Empty default. 'size' => get_option( 'image_default_size' ), // Empty default. ); $exts = array_merge( wp_get_audio_extensions(), wp_get_video_extensions() ); $mimes = get_allowed_mime_types(); $ext_mimes = array(); foreach ( $exts as $ext ) { foreach ( $mimes as $ext_preg => $mime_match ) { if ( preg_match( '#' . $ext . '#i', $ext_preg ) ) { $ext_mimes[ $ext ] = $mime_match; break; } } } /** * Allows showing or hiding the "Create Audio Playlist" button in the media library. * * By default, the "Create Audio Playlist" button will always be shown in * the media library. If this filter returns `null`, a query will be run * to determine whether the media library contains any audio items. This * was the default behavior prior to version 4.8.0, but this query is * expensive for large media libraries. * * @since 4.7.4 * @since 4.8.0 The filter's default value is `true` rather than `null`. * * @link https://core.trac.wordpress.org/ticket/31071 * * @param bool|null $show Whether to show the button, or `null` to decide based * on whether any audio files exist in the media library. */ $show_audio_playlist = apply_filters( 'media_library_show_audio_playlist', true ); if ( null === $show_audio_playlist ) { $show_audio_playlist = $wpdb->get_var( "SELECT ID FROM $wpdb->posts WHERE post_type = 'attachment' AND post_mime_type LIKE 'audio%' LIMIT 1" ); } /** * Allows showing or hiding the "Create Video Playlist" button in the media library. * * By default, the "Create Video Playlist" button will always be shown in * the media library. If this filter returns `null`, a query will be run * to determine whether the media library contains any video items. This * was the default behavior prior to version 4.8.0, but this query is * expensive for large media libraries. * * @since 4.7.4 * @since 4.8.0 The filter's default value is `true` rather than `null`. * * @link https://core.trac.wordpress.org/ticket/31071 * * @param bool|null $show Whether to show the button, or `null` to decide based * on whether any video files exist in the media library. */ $show_video_playlist = apply_filters( 'media_library_show_video_playlist', true ); if ( null === $show_video_playlist ) { $show_video_playlist = $wpdb->get_var( "SELECT ID FROM $wpdb->posts WHERE post_type = 'attachment' AND post_mime_type LIKE 'video%' LIMIT 1" ); } /** * Allows overriding the list of months displayed in the media library. * * By default (if this filter does not return an array), a query will be * run to determine the months that have media items. This query can be * expensive for large media libraries, so it may be desirable for sites to * override this behavior. * * @since 4.7.4 * * @link https://core.trac.wordpress.org/ticket/31071 * * @param stdClass[]|null $months An array of objects with `month` and `year` * properties, or `null` for default behavior. */ $months = apply_filters( 'media_library_months_with_files', null ); if ( ! is_array( $months ) ) { $months = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month FROM $wpdb->posts WHERE post_type = %s ORDER BY post_date DESC", 'attachment' ) ); } foreach ( $months as $month_year ) { $month_year->text = sprintf( /* translators: 1: Month, 2: Year. */ __( '%1$s %2$d' ), $wp_locale->get_month( $month_year->month ), $month_year->year ); } /** * Filters whether the Media Library grid has infinite scrolling. Default `false`. * * @since 5.8.0 * * @param bool $infinite Whether the Media Library grid has infinite scrolling. */ $infinite_scrolling = apply_filters( 'media_library_infinite_scrolling', false ); $settings = array( 'tabs' => $tabs, 'tabUrl' => add_query_arg( array( 'chromeless' => true ), admin_url( 'media-upload.php' ) ), 'mimeTypes' => wp_list_pluck( get_post_mime_types(), 0 ), /** This filter is documented in wp-admin/includes/media.php */ 'captions' => ! apply_filters( 'disable_captions', '' ), 'nonce' => array( 'sendToEditor' => wp_create_nonce( 'media-send-to-editor' ), 'setAttachmentThumbnail' => wp_create_nonce( 'set-attachment-thumbnail' ), ), 'post' => array( 'id' => 0, ), 'defaultProps' => $props, 'attachmentCounts' => array( 'audio' => ( $show_audio_playlist ) ? 1 : 0, 'video' => ( $show_video_playlist ) ? 1 : 0, ), 'oEmbedProxyUrl' => rest_url( 'oembed/1.0/proxy' ), 'embedExts' => $exts, 'embedMimes' => $ext_mimes, 'contentWidth' => $content_width, 'months' => $months, 'mediaTrash' => MEDIA_TRASH ? 1 : 0, 'infiniteScrolling' => ( $infinite_scrolling ) ? 1 : 0, ); $post = null; if ( isset( $args['post'] ) ) { $post = get_post( $args['post'] ); $settings['post'] = array( 'id' => $post->ID, 'nonce' => wp_create_nonce( 'update-post_' . $post->ID ), ); $thumbnail_support = current_theme_supports( 'post-thumbnails', $post->post_type ) && post_type_supports( $post->post_type, 'thumbnail' ); if ( ! $thumbnail_support && 'attachment' === $post->post_type && $post->post_mime_type ) { if ( wp_attachment_is( 'audio', $post ) ) { $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' ); } elseif ( wp_attachment_is( 'video', $post ) ) { $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' ); } } if ( $thumbnail_support ) { $featured_image_id = get_post_meta( $post->ID, '_thumbnail_id', true ); $settings['post']['featuredImageId'] = $featured_image_id ? $featured_image_id : -1; } } if ( $post ) { $post_type_object = get_post_type_object( $post->post_type ); } else { $post_type_object = get_post_type_object( 'post' ); } $strings = array( // Generic. 'mediaFrameDefaultTitle' => __( 'Media' ), 'url' => __( 'URL' ), 'addMedia' => __( 'Add media' ), 'search' => __( 'Search' ), 'select' => __( 'Select' ), 'cancel' => __( 'Cancel' ), 'update' => __( 'Update' ), 'replace' => __( 'Replace' ), 'remove' => __( 'Remove' ), 'back' => __( 'Back' ), /* * translators: This is a would-be plural string used in the media manager. * If there is not a word you can use in your language to avoid issues with the * lack of plural support here, turn it into "selected: %d" then translate it. */ 'selected' => __( '%d selected' ), 'dragInfo' => __( 'Drag and drop to reorder media files.' ), // Upload. 'uploadFilesTitle' => __( 'Upload files' ), 'uploadImagesTitle' => __( 'Upload images' ), // Library. 'mediaLibraryTitle' => __( 'Media Library' ), 'insertMediaTitle' => __( 'Add media' ), 'createNewGallery' => __( 'Create a new gallery' ), 'createNewPlaylist' => __( 'Create a new playlist' ), 'createNewVideoPlaylist' => __( 'Create a new video playlist' ), 'returnToLibrary' => __( '← Go to library' ), 'allMediaItems' => __( 'All media items' ), 'allDates' => __( 'All dates' ), 'noItemsFound' => __( 'No items found.' ), 'insertIntoPost' => $post_type_object->labels->insert_into_item, 'unattached' => _x( 'Unattached', 'media items' ), 'mine' => _x( 'Mine', 'media items' ), 'trash' => _x( 'Trash', 'noun' ), 'uploadedToThisPost' => $post_type_object->labels->uploaded_to_this_item, 'warnDelete' => __( "You are about to permanently delete this item from your site.\nThis action cannot be undone.\n 'Cancel' to stop, 'OK' to delete." ), 'warnBulkDelete' => __( "You are about to permanently delete these items from your site.\nThis action cannot be undone.\n 'Cancel' to stop, 'OK' to delete." ), 'warnBulkTrash' => __( "You are about to trash these items.\n 'Cancel' to stop, 'OK' to delete." ), 'bulkSelect' => __( 'Bulk select' ), 'trashSelected' => __( 'Move to Trash' ), 'restoreSelected' => __( 'Restore from Trash' ), 'deletePermanently' => __( 'Delete permanently' ), 'errorDeleting' => __( 'Error in deleting the attachment.' ), 'apply' => __( 'Apply' ), 'filterByDate' => __( 'Filter by date' ), 'filterByType' => __( 'Filter by type' ), 'searchLabel' => __( 'Search media' ), 'searchMediaLabel' => __( 'Search media' ), // Backward compatibility pre-5.3. 'searchMediaPlaceholder' => __( 'Search media items...' ), // Placeholder (no ellipsis), backward compatibility pre-5.3. /* translators: %d: Number of attachments found in a search. */ 'mediaFound' => __( 'Number of media items found: %d' ), 'noMedia' => __( 'No media items found.' ), 'noMediaTryNewSearch' => __( 'No media items found. Try a different search.' ), // Library Details. 'attachmentDetails' => __( 'Attachment details' ), // From URL. 'insertFromUrlTitle' => __( 'Insert from URL' ), // Featured Images. 'setFeaturedImageTitle' => $post_type_object->labels->featured_image, 'setFeaturedImage' => $post_type_object->labels->set_featured_image, // Gallery. 'createGalleryTitle' => __( 'Create gallery' ), 'editGalleryTitle' => __( 'Edit gallery' ), 'cancelGalleryTitle' => __( '← Cancel gallery' ), 'insertGallery' => __( 'Insert gallery' ), 'updateGallery' => __( 'Update gallery' ), 'addToGallery' => __( 'Add to gallery' ), 'addToGalleryTitle' => __( 'Add to gallery' ), 'reverseOrder' => __( 'Reverse order' ), // Edit Image. 'imageDetailsTitle' => __( 'Image details' ), 'imageReplaceTitle' => __( 'Replace image' ), 'imageDetailsCancel' => __( 'Cancel edit' ), 'editImage' => __( 'Edit image' ), // Crop Image. 'chooseImage' => __( 'Choose image' ), 'selectAndCrop' => __( 'Select and crop' ), 'skipCropping' => __( 'Skip cropping' ), 'cropImage' => __( 'Crop image' ), 'cropYourImage' => __( 'Crop your image' ), 'cropping' => __( 'Cropping…' ), /* translators: 1: Suggested width number, 2: Suggested height number. */ 'suggestedDimensions' => __( 'Suggested image dimensions: %1$s by %2$s pixels.' ), 'cropError' => __( 'There has been an error cropping your image.' ), // Edit Audio. 'audioDetailsTitle' => __( 'Audio details' ), 'audioReplaceTitle' => __( 'Replace audio' ), 'audioAddSourceTitle' => __( 'Add audio source' ), 'audioDetailsCancel' => __( 'Cancel edit' ), // Edit Video. 'videoDetailsTitle' => __( 'Video details' ), 'videoReplaceTitle' => __( 'Replace video' ), 'videoAddSourceTitle' => __( 'Add video source' ), 'videoDetailsCancel' => __( 'Cancel edit' ), 'videoSelectPosterImageTitle' => __( 'Select poster image' ), 'videoAddTrackTitle' => __( 'Add subtitles' ), // Playlist. 'playlistDragInfo' => __( 'Drag and drop to reorder tracks.' ), 'createPlaylistTitle' => __( 'Create audio playlist' ), 'editPlaylistTitle' => __( 'Edit audio playlist' ), 'cancelPlaylistTitle' => __( '← Cancel audio playlist' ), 'insertPlaylist' => __( 'Insert audio playlist' ), 'updatePlaylist' => __( 'Update audio playlist' ), 'addToPlaylist' => __( 'Add to audio playlist' ), 'addToPlaylistTitle' => __( 'Add to Audio Playlist' ), // Video Playlist. 'videoPlaylistDragInfo' => __( 'Drag and drop to reorder videos.' ), 'createVideoPlaylistTitle' => __( 'Create video playlist' ), 'editVideoPlaylistTitle' => __( 'Edit video playlist' ), 'cancelVideoPlaylistTitle' => __( '← Cancel video playlist' ), 'insertVideoPlaylist' => __( 'Insert video playlist' ), 'updateVideoPlaylist' => __( 'Update video playlist' ), 'addToVideoPlaylist' => __( 'Add to video playlist' ), 'addToVideoPlaylistTitle' => __( 'Add to video Playlist' ), // Headings. 'filterAttachments' => __( 'Filter media' ), 'attachmentsList' => __( 'Media list' ), ); /** * Filters the media view settings. * * @since 3.5.0 * * @param array $settings List of media view settings. * @param WP_Post $post Post object. */ $settings = apply_filters( 'media_view_settings', $settings, $post ); /** * Filters the media view strings. * * @since 3.5.0 * * @param string[] $strings Array of media view strings keyed by the name they'll be referenced by in JavaScript. * @param WP_Post $post Post object. */ $strings = apply_filters( 'media_view_strings', $strings, $post ); $strings['settings'] = $settings; /* * Ensure we enqueue media-editor first, that way media-views * is registered internally before we try to localize it. See #24724. */ wp_enqueue_script( 'media-editor' ); wp_localize_script( 'media-views', '_wpMediaViewsL10n', $strings ); wp_enqueue_script( 'media-audiovideo' ); wp_enqueue_style( 'media-views' ); if ( is_admin() ) { wp_enqueue_script( 'mce-view' ); wp_enqueue_script( 'image-edit' ); } wp_enqueue_style( 'imgareaselect' ); wp_plupload_default_settings(); require_once ABSPATH . WPINC . '/media-template.php'; add_action( 'admin_footer', 'wp_print_media_templates' ); add_action( 'wp_footer', 'wp_print_media_templates' ); add_action( 'customize_controls_print_footer_scripts', 'wp_print_media_templates' ); /** * Fires at the conclusion of wp_enqueue_media(). * * @since 3.5.0 */ do_action( 'wp_enqueue_media' ); } /** * Retrieves media attached to the passed post. * * @since 3.6.0 * * @param string $type Mime type. * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post. * @return WP_Post[] Array of media attached to the given post. */ function get_attached_media( $type, $post = 0 ) { $post = get_post( $post ); if ( ! $post ) { return array(); } $args = array( 'post_parent' => $post->ID, 'post_type' => 'attachment', 'post_mime_type' => $type, 'posts_per_page' => -1, 'orderby' => 'menu_order', 'order' => 'ASC', ); /** * Filters arguments used to retrieve media attached to the given post. * * @since 3.6.0 * * @param array $args Post query arguments. * @param string $type Mime type of the desired media. * @param WP_Post $post Post object. */ $args = apply_filters( 'get_attached_media_args', $args, $type, $post ); $children = get_children( $args ); /** * Filters the list of media attached to the given post. * * @since 3.6.0 * * @param WP_Post[] $children Array of media attached to the given post. * @param string $type Mime type of the media desired. * @param WP_Post $post Post object. */ return (array) apply_filters( 'get_attached_media', $children, $type, $post ); } /** * Checks the HTML content for an audio, video, object, embed, or iframe tags. * * @since 3.6.0 * * @param string $content A string of HTML which might contain media elements. * @param string[] $types An array of media types: 'audio', 'video', 'object', 'embed', or 'iframe'. * @return string[] Array of found HTML media elements. */ function get_media_embedded_in_content( $content, $types = null ) { $html = array(); /** * Filters the embedded media types that are allowed to be returned from the content blob. * * @since 4.2.0 * * @param string[] $allowed_media_types An array of allowed media types. Default media types are * 'audio', 'video', 'object', 'embed', and 'iframe'. */ $allowed_media_types = apply_filters( 'media_embedded_in_content_allowed_types', array( 'audio', 'video', 'object', 'embed', 'iframe' ) ); if ( ! empty( $types ) ) { if ( ! is_array( $types ) ) { $types = array( $types ); } $allowed_media_types = array_intersect( $allowed_media_types, $types ); } $tags = implode( '|', $allowed_media_types ); if ( preg_match_all( '#<(?P<tag>' . $tags . ')[^<]*?(?:>[\s\S]*?<\/(?P=tag)>|\s*\/>)#', $content, $matches ) ) { foreach ( $matches[0] as $match ) { $html[] = $match; } } return $html; } /** * Retrieves galleries from the passed post's content. * * @since 3.6.0 * * @param int|WP_Post $post Post ID or object. * @param bool $html Optional. Whether to return HTML or data in the array. Default true. * @return array A list of arrays, each containing gallery data and srcs parsed * from the expanded shortcode. */ function get_post_galleries( $post, $html = true ) { $post = get_post( $post ); if ( ! $post ) { return array(); } if ( ! has_shortcode( $post->post_content, 'gallery' ) && ! has_block( 'gallery', $post->post_content ) ) { return array(); } $galleries = array(); if ( preg_match_all( '/' . get_shortcode_regex() . '/s', $post->post_content, $matches, PREG_SET_ORDER ) ) { foreach ( $matches as $shortcode ) { if ( 'gallery' === $shortcode[2] ) { $srcs = array(); $shortcode_attrs = shortcode_parse_atts( $shortcode[3] ); // Specify the post ID of the gallery we're viewing if the shortcode doesn't reference another post already. if ( ! isset( $shortcode_attrs['id'] ) ) { $shortcode[3] .= ' id="' . (int) $post->ID . '"'; } $gallery = do_shortcode_tag( $shortcode ); if ( $html ) { $galleries[] = $gallery; } else { preg_match_all( '#src=([\'"])(.+?)\1#is', $gallery, $src, PREG_SET_ORDER ); if ( ! empty( $src ) ) { foreach ( $src as $s ) { $srcs[] = $s[2]; } } $galleries[] = array_merge( $shortcode_attrs, array( 'src' => array_values( array_unique( $srcs ) ), ) ); } } } } if ( has_block( 'gallery', $post->post_content ) ) { $post_blocks = parse_blocks( $post->post_content ); while ( $block = array_shift( $post_blocks ) ) { $has_inner_blocks = ! empty( $block['innerBlocks'] ); // Skip blocks with no blockName and no innerHTML. if ( ! $block['blockName'] ) { continue; } // Skip non-Gallery blocks. if ( 'core/gallery' !== $block['blockName'] ) { // Move inner blocks into the root array before skipping. if ( $has_inner_blocks ) { array_push( $post_blocks, ...$block['innerBlocks'] ); } continue; } // New Gallery block format as HTML. if ( $has_inner_blocks && $html ) { $block_html = wp_list_pluck( $block['innerBlocks'], 'innerHTML' ); $galleries[] = '<figure>' . implode( ' ', $block_html ) . '</figure>'; continue; } $srcs = array(); // New Gallery block format as an array. if ( $has_inner_blocks ) { $attrs = wp_list_pluck( $block['innerBlocks'], 'attrs' ); $ids = wp_list_pluck( $attrs, 'id' ); foreach ( $ids as $id ) { $url = wp_get_attachment_url( $id ); if ( is_string( $url ) && ! in_array( $url, $srcs, true ) ) { $srcs[] = $url; } } $galleries[] = array( 'ids' => implode( ',', $ids ), 'src' => $srcs, ); continue; } // Old Gallery block format as HTML. if ( $html ) { $galleries[] = $block['innerHTML']; continue; } // Old Gallery block format as an array. $ids = ! empty( $block['attrs']['ids'] ) ? $block['attrs']['ids'] : array(); // If present, use the image IDs from the JSON blob as canonical. if ( ! empty( $ids ) ) { foreach ( $ids as $id ) { $url = wp_get_attachment_url( $id ); if ( is_string( $url ) && ! in_array( $url, $srcs, true ) ) { $srcs[] = $url; } } $galleries[] = array( 'ids' => implode( ',', $ids ), 'src' => $srcs, ); continue; } // Otherwise, extract srcs from the innerHTML. preg_match_all( '#src=([\'"])(.+?)\1#is', $block['innerHTML'], $found_srcs, PREG_SET_ORDER ); if ( ! empty( $found_srcs[0] ) ) { foreach ( $found_srcs as $src ) { if ( isset( $src[2] ) && ! in_array( $src[2], $srcs, true ) ) { $srcs[] = $src[2]; } } } $galleries[] = array( 'src' => $srcs ); } } /** * Filters the list of all found galleries in the given post. * * @since 3.6.0 * * @param array $galleries Associative array of all found post galleries. * @param WP_Post $post Post object. */ return apply_filters( 'get_post_galleries', $galleries, $post ); } /** * Checks a specified post's content for gallery and, if present, return the first * * @since 3.6.0 * * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post. * @param bool $html Optional. Whether to return HTML or data. Default is true. * @return string|array Gallery data and srcs parsed from the expanded shortcode. */ function get_post_gallery( $post = 0, $html = true ) { $galleries = get_post_galleries( $post, $html ); $gallery = reset( $galleries ); /** * Filters the first-found post gallery. * * @since 3.6.0 * * @param array $gallery The first-found post gallery. * @param int|WP_Post $post Post ID or object. * @param array $galleries Associative array of all found post galleries. */ return apply_filters( 'get_post_gallery', $gallery, $post, $galleries ); } /** * Retrieves the image srcs from galleries from a post's content, if present. * * @since 3.6.0 * * @see get_post_galleries() * * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global `$post`. * @return array A list of lists, each containing image srcs parsed. * from an expanded shortcode */ function get_post_galleries_images( $post = 0 ) { $galleries = get_post_galleries( $post, false ); return wp_list_pluck( $galleries, 'src' ); } /** * Checks a post's content for galleries and return the image srcs for the first found gallery. * * @since 3.6.0 * * @see get_post_gallery() * * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global `$post`. * @return string[] A list of a gallery's image srcs in order. */ function get_post_gallery_images( $post = 0 ) { $gallery = get_post_gallery( $post, false ); return empty( $gallery['src'] ) ? array() : $gallery['src']; } /** * Maybe attempts to generate attachment metadata, if missing. * * @since 3.9.0 * * @param WP_Post $attachment Attachment object. */ function wp_maybe_generate_attachment_metadata( $attachment ) { if ( empty( $attachment ) || empty( $attachment->ID ) ) { return; } $attachment_id = (int) $attachment->ID; $file = get_attached_file( $attachment_id ); $meta = wp_get_attachment_metadata( $attachment_id ); if ( empty( $meta ) && file_exists( $file ) ) { $_meta = get_post_meta( $attachment_id ); $_lock = 'wp_generating_att_' . $attachment_id; if ( ! array_key_exists( '_wp_attachment_metadata', $_meta ) && ! get_transient( $_lock ) ) { set_transient( $_lock, $file ); wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) ); delete_transient( $_lock ); } } } /** * Tries to convert an attachment URL into a post ID. * * @since 4.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $url The URL to resolve. * @return int The found post ID, or 0 on failure. */ function attachment_url_to_postid( $url ) { global $wpdb; /** * Filters the attachment ID to allow short-circuit the function. * * Allows plugins to short-circuit attachment ID lookups. Plugins making * use of this function should return: * * - 0 (integer) to indicate the attachment is not found, * - attachment ID (integer) to indicate the attachment ID found, * - null to indicate WordPress should proceed with the lookup. * * Warning: The post ID may be null or zero, both of which cast to a * boolean false. For information about casting to booleans see the * {@link https://www.php.net/manual/en/language.types.boolean.php PHP documentation}. * Use the === operator for testing the post ID when developing filters using * this hook. * * @since 6.7.0 * * @param int|null $post_id The result of the post ID lookup. Null to indicate * no lookup has been attempted. Default null. * @param string $url The URL being looked up. */ $post_id = apply_filters( 'pre_attachment_url_to_postid', null, $url ); if ( null !== $post_id ) { return (int) $post_id; } $dir = wp_get_upload_dir(); $path = $url; $site_url = parse_url( $dir['url'] ); $image_path = parse_url( $path ); // Force the protocols to match if needed. if ( isset( $image_path['scheme'] ) && ( $image_path['scheme'] !== $site_url['scheme'] ) ) { $path = str_replace( $image_path['scheme'], $site_url['scheme'], $path ); } if ( str_starts_with( $path, $dir['baseurl'] . '/' ) ) { $path = substr( $path, strlen( $dir['baseurl'] . '/' ) ); } $sql = $wpdb->prepare( "SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value = %s", $path ); $results = $wpdb->get_results( $sql ); $post_id = null; if ( $results ) { // Use the first available result, but prefer a case-sensitive match, if exists. $post_id = reset( $results )->post_id; if ( count( $results ) > 1 ) { foreach ( $results as $result ) { if ( $path === $result->meta_value ) { $post_id = $result->post_id; break; } } } } /** * Filters an attachment ID found by URL. * * @since 4.2.0 * * @param int|null $post_id The post_id (if any) found by the function. * @param string $url The URL being looked up. */ return (int) apply_filters( 'attachment_url_to_postid', $post_id, $url ); } /** * Returns the URLs for CSS files used in an iframe-sandbox'd TinyMCE media view. * * @since 4.0.0 * * @return string[] The relevant CSS file URLs. */ function wpview_media_sandbox_styles() { $version = 'ver=' . get_bloginfo( 'version' ); $mediaelement = includes_url( "js/mediaelement/mediaelementplayer-legacy.min.css?$version" ); $wpmediaelement = includes_url( "js/mediaelement/wp-mediaelement.css?$version" ); return array( $mediaelement, $wpmediaelement ); } /** * Registers the personal data exporter for media. * * @since 4.9.6 * * @param array[] $exporters An array of personal data exporters, keyed by their ID. * @return array[] Updated array of personal data exporters. */ function wp_register_media_personal_data_exporter( $exporters ) { $exporters['wordpress-media'] = array( 'exporter_friendly_name' => __( 'WordPress Media' ), 'callback' => 'wp_media_personal_data_exporter', ); return $exporters; } /** * Finds and exports attachments associated with an email address. * * @since 4.9.6 * * @param string $email_address The attachment owner email address. * @param int $page Attachment page number. * @return array { * An array of personal data. * * @type array[] $data An array of personal data arrays. * @type bool $done Whether the exporter is finished. * } */ function wp_media_personal_data_exporter( $email_address, $page = 1 ) { // Limit us to 50 attachments at a time to avoid timing out. $number = 50; $page = (int) $page; $data_to_export = array(); $user = get_user_by( 'email', $email_address ); if ( false === $user ) { return array( 'data' => $data_to_export, 'done' => true, ); } $post_query = new WP_Query( array( 'author' => $user->ID, 'posts_per_page' => $number, 'paged' => $page, 'post_type' => 'attachment', 'post_status' => 'any', 'orderby' => 'ID', 'order' => 'ASC', ) ); foreach ( (array) $post_query->posts as $post ) { $attachment_url = wp_get_attachment_url( $post->ID ); if ( $attachment_url ) { $post_data_to_export = array( array( 'name' => __( 'URL' ), 'value' => $attachment_url, ), ); $data_to_export[] = array( 'group_id' => 'media', 'group_label' => __( 'Media' ), 'group_description' => __( 'User’s media data.' ), 'item_id' => "post-{$post->ID}", 'data' => $post_data_to_export, ); } } $done = $post_query->max_num_pages <= $page; return array( 'data' => $data_to_export, 'done' => $done, ); } /** * Adds additional default image sub-sizes. * * These sizes are meant to enhance the way WordPress displays images on the front-end on larger, * high-density devices. They make it possible to generate more suitable `srcset` and `sizes` attributes * when the users upload large images. * * The sizes can be changed or removed by themes and plugins but that is not recommended. * The size "names" reflect the image dimensions, so changing the sizes would be quite misleading. * * @since 5.3.0 * @access private */ function _wp_add_additional_image_sizes() { // 2x medium_large size. add_image_size( '1536x1536', 1536, 1536 ); // 2x large size. add_image_size( '2048x2048', 2048, 2048 ); } /** * Callback to enable showing of the user error when uploading .heic images. * * @since 5.5.0 * @since 6.7.0 The default behavior is to enable heic uploads as long as the server * supports the format. The uploads are converted to JPEG's by default. * * @param array[] $plupload_settings The settings for Plupload.js. * @return array[] Modified settings for Plupload.js. */ function wp_show_heic_upload_error( $plupload_settings ) { // Check if HEIC images can be edited. if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) { $plupload_init['heic_upload_error'] = true; } return $plupload_settings; } /** * Allows PHP's getimagesize() to be debuggable when necessary. * * @since 5.7.0 * @since 5.8.0 Added support for WebP images. * @since 6.5.0 Added support for AVIF images. * * @param string $filename The file path. * @param array $image_info Optional. Extended image information (passed by reference). * @return array|false Array of image information or false on failure. */ function wp_getimagesize( $filename, ?array &$image_info = null ) { // Don't silence errors when in debug mode, unless running unit tests. if ( defined( 'WP_DEBUG' ) && WP_DEBUG && ! defined( 'WP_RUN_CORE_TESTS' ) ) { if ( 2 === func_num_args() ) { $info = getimagesize( $filename, $image_info ); } else { $info = getimagesize( $filename ); } } else { /* * Silencing notice and warning is intentional. * * getimagesize() has a tendency to generate errors, such as * "corrupt JPEG data: 7191 extraneous bytes before marker", * even when it's able to provide image size information. * * See https://core.trac.wordpress.org/ticket/42480 */ if ( 2 === func_num_args() ) { $info = @getimagesize( $filename, $image_info ); } else { $info = @getimagesize( $filename ); } } if ( ! empty( $info ) && // Some PHP versions return 0x0 sizes from `getimagesize` for unrecognized image formats, including AVIFs. ! ( empty( $info[0] ) && empty( $info[1] ) ) ) { return $info; } $image_mime_type = wp_get_image_mime( $filename ); // Not an image? if ( false === $image_mime_type ) { return false; } /* * For PHP versions that don't support WebP images, * extract the image size info from the file headers. */ if ( 'image/webp' === $image_mime_type ) { $webp_info = wp_get_webp_info( $filename ); $width = $webp_info['width']; $height = $webp_info['height']; // Mimic the native return format. if ( $width && $height ) { return array( $width, $height, IMAGETYPE_WEBP, sprintf( 'width="%d" height="%d"', $width, $height ), 'mime' => 'image/webp', ); } } // For PHP versions that don't support AVIF images, extract the image size info from the file headers. if ( 'image/avif' === $image_mime_type ) { $avif_info = wp_get_avif_info( $filename ); $width = $avif_info['width']; $height = $avif_info['height']; // Mimic the native return format. if ( $width && $height ) { return array( $width, $height, IMAGETYPE_AVIF, sprintf( 'width="%d" height="%d"', $width, $height ), 'mime' => 'image/avif', ); } } // For PHP versions that don't support HEIC images, extract the size info using Imagick when available. if ( wp_is_heic_image_mime_type( $image_mime_type ) ) { $editor = wp_get_image_editor( $filename ); if ( is_wp_error( $editor ) ) { return false; } // If the editor for HEICs is Imagick, use it to get the image size. if ( $editor instanceof WP_Image_Editor_Imagick ) { $size = $editor->get_size(); return array( $size['width'], $size['height'], IMAGETYPE_HEIF, sprintf( 'width="%d" height="%d"', $size['width'], $size['height'] ), 'mime' => 'image/heic', ); } } // The image could not be parsed. return false; } /** * Extracts meta information about an AVIF file: width, height, bit depth, and number of channels. * * @since 6.5.0 * * @param string $filename Path to an AVIF file. * @return array { * An array of AVIF image information. * * @type int|false $width Image width on success, false on failure. * @type int|false $height Image height on success, false on failure. * @type int|false $bit_depth Image bit depth on success, false on failure. * @type int|false $num_channels Image number of channels on success, false on failure. * } */ function wp_get_avif_info( $filename ) { $results = array( 'width' => false, 'height' => false, 'bit_depth' => false, 'num_channels' => false, ); if ( 'image/avif' !== wp_get_image_mime( $filename ) ) { return $results; } // Parse the file using libavifinfo's PHP implementation. require_once ABSPATH . WPINC . '/class-avif-info.php'; $handle = fopen( $filename, 'rb' ); if ( $handle ) { $parser = new Avifinfo\Parser( $handle ); $success = $parser->parse_ftyp() && $parser->parse_file(); fclose( $handle ); if ( $success ) { $results = $parser->features->primary_item_features; } } return $results; } /** * Extracts meta information about a WebP file: width, height, and type. * * @since 5.8.0 * * @param string $filename Path to a WebP file. * @return array { * An array of WebP image information. * * @type int|false $width Image width on success, false on failure. * @type int|false $height Image height on success, false on failure. * @type string|false $type The WebP type: one of 'lossy', 'lossless' or 'animated-alpha'. * False on failure. * } */ function wp_get_webp_info( $filename ) { $width = false; $height = false; $type = false; if ( 'image/webp' !== wp_get_image_mime( $filename ) ) { return compact( 'width', 'height', 'type' ); } $magic = file_get_contents( $filename, false, null, 0, 40 ); if ( false === $magic ) { return compact( 'width', 'height', 'type' ); } // Make sure we got enough bytes. if ( strlen( $magic ) < 40 ) { return compact( 'width', 'height', 'type' ); } /* * The headers are a little different for each of the three formats. * Header values based on WebP docs, see https://developers.google.com/speed/webp/docs/riff_container. */ switch ( substr( $magic, 12, 4 ) ) { // Lossy WebP. case 'VP8 ': $parts = unpack( 'v2', substr( $magic, 26, 4 ) ); $width = (int) ( $parts[1] & 0x3FFF ); $height = (int) ( $parts[2] & 0x3FFF ); $type = 'lossy'; break; // Lossless WebP. case 'VP8L': $parts = unpack( 'C4', substr( $magic, 21, 4 ) ); $width = (int) ( $parts[1] | ( ( $parts[2] & 0x3F ) << 8 ) ) + 1; $height = (int) ( ( ( $parts[2] & 0xC0 ) >> 6 ) | ( $parts[3] << 2 ) | ( ( $parts[4] & 0x03 ) << 10 ) ) + 1; $type = 'lossless'; break; // Animated/alpha WebP. case 'VP8X': // Pad 24-bit int. $width = unpack( 'V', substr( $magic, 24, 3 ) . "\x00" ); $width = (int) ( $width[1] & 0xFFFFFF ) + 1; // Pad 24-bit int. $height = unpack( 'V', substr( $magic, 27, 3 ) . "\x00" ); $height = (int) ( $height[1] & 0xFFFFFF ) + 1; $type = 'animated-alpha'; break; } return compact( 'width', 'height', 'type' ); } /** * Gets loading optimization attributes. * * This function returns an array of attributes that should be merged into the given attributes array to optimize * loading performance. Potential attributes returned by this function are: * - `loading` attribute with a value of "lazy" * - `fetchpriority` attribute with a value of "high" * - `decoding` attribute with a value of "async" * * If any of these attributes are already present in the given attributes, they will not be modified. Note that no * element should have both `loading="lazy"` and `fetchpriority="high"`, so the function will trigger a warning in case * both attributes are present with those values. * * @since 6.3.0 * * @global WP_Query $wp_query WordPress Query object. * * @param string $tag_name The tag name. * @param array $attr Array of the attributes for the tag. * @param string $context Context for the element for which the loading optimization attribute is requested. * @return array Loading optimization attributes. */ function wp_get_loading_optimization_attributes( $tag_name, $attr, $context ) { global $wp_query; /** * Filters whether to short-circuit loading optimization attributes. * * Returning an array from the filter will effectively short-circuit the loading of optimization attributes, * returning that value instead. * * @since 6.4.0 * * @param array|false $loading_attrs False by default, or array of loading optimization attributes to short-circuit. * @param string $tag_name The tag name. * @param array $attr Array of the attributes for the tag. * @param string $context Context for the element for which the loading optimization attribute is requested. */ $loading_attrs = apply_filters( 'pre_wp_get_loading_optimization_attributes', false, $tag_name, $attr, $context ); if ( is_array( $loading_attrs ) ) { return $loading_attrs; } $loading_attrs = array(); /* * Skip lazy-loading for the overall block template, as it is handled more granularly. * The skip is also applicable for `fetchpriority`. */ if ( 'template' === $context ) { /** This filter is documented in wp-includes/media.php */ return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context ); } // For now this function only supports images and iframes. if ( 'img' !== $tag_name && 'iframe' !== $tag_name ) { /** This filter is documented in wp-includes/media.php */ return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context ); } /* * Skip programmatically created images within content blobs as they need to be handled together with the other * images within the post content or widget content. * Without this clause, they would already be considered within their own context which skews the image count and * can result in the first post content image being lazy-loaded or an image further down the page being marked as a * high priority. */ if ( 'the_content' !== $context && doing_filter( 'the_content' ) || 'widget_text_content' !== $context && doing_filter( 'widget_text_content' ) || 'widget_block_content' !== $context && doing_filter( 'widget_block_content' ) ) { /** This filter is documented in wp-includes/media.php */ return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context ); } /* * Add `decoding` with a value of "async" for every image unless it has a * conflicting `decoding` attribute already present. */ if ( 'img' === $tag_name ) { if ( isset( $attr['decoding'] ) ) { $loading_attrs['decoding'] = $attr['decoding']; } else { $loading_attrs['decoding'] = 'async'; } } // For any resources, width and height must be provided, to avoid layout shifts. if ( ! isset( $attr['width'], $attr['height'] ) ) { /** This filter is documented in wp-includes/media.php */ return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context ); } /* * The key function logic starts here. */ $maybe_in_viewport = null; $increase_count = false; $maybe_increase_count = false; // Logic to handle a `loading` attribute that is already provided. if ( isset( $attr['loading'] ) ) { /* * Interpret "lazy" as not in viewport. Any other value can be * interpreted as in viewport (realistically only "eager" or `false` * to force-omit the attribute are other potential values). */ if ( 'lazy' === $attr['loading'] ) { $maybe_in_viewport = false; } else { $maybe_in_viewport = true; } } // Logic to handle a `fetchpriority` attribute that is already provided. if ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] ) { /* * If the image was already determined to not be in the viewport (e.g. * from an already provided `loading` attribute), trigger a warning. * Otherwise, the value can be interpreted as in viewport, since only * the most important in-viewport image should have `fetchpriority` set * to "high". */ if ( false === $maybe_in_viewport ) { _doing_it_wrong( __FUNCTION__, __( 'An image should not be lazy-loaded and marked as high priority at the same time.' ), '6.3.0' ); /* * Set `fetchpriority` here for backward-compatibility as we should * not override what a developer decided, even though it seems * incorrect. */ $loading_attrs['fetchpriority'] = 'high'; } else { $maybe_in_viewport = true; } } if ( null === $maybe_in_viewport ) { $header_enforced_contexts = array( 'template_part_' . WP_TEMPLATE_PART_AREA_HEADER => true, 'get_header_image_tag' => true, ); /** * Filters the header-specific contexts. * * @since 6.4.0 * * @param array $default_header_enforced_contexts Map of contexts for which elements should be considered * in the header of the page, as $context => $enabled * pairs. The $enabled should always be true. */ $header_enforced_contexts = apply_filters( 'wp_loading_optimization_force_header_contexts', $header_enforced_contexts ); // Consider elements with these header-specific contexts to be in viewport. if ( isset( $header_enforced_contexts[ $context ] ) ) { $maybe_in_viewport = true; $maybe_increase_count = true; } elseif ( ! is_admin() && in_the_loop() && is_main_query() ) { /* * Get the content media count, since this is a main query * content element. This is accomplished by "increasing" * the count by zero, as the only way to get the count is * to call this function. * The actual count increase happens further below, based * on the `$increase_count` flag set here. */ $content_media_count = wp_increase_content_media_count( 0 ); $increase_count = true; // If the count so far is below the threshold, `loading` attribute is omitted. if ( $content_media_count < wp_omit_loading_attr_threshold() ) { $maybe_in_viewport = true; } else { $maybe_in_viewport = false; } } elseif ( // Only apply for main query but before the loop. $wp_query->before_loop && $wp_query->is_main_query() /* * Any image before the loop, but after the header has started should not be lazy-loaded, * except when the footer has already started which can happen when the current template * does not include any loop. */ && did_action( 'get_header' ) && ! did_action( 'get_footer' ) ) { $maybe_in_viewport = true; $maybe_increase_count = true; } } /* * If the element is in the viewport (`true`), potentially add * `fetchpriority` with a value of "high". Otherwise, i.e. if the element * is not not in the viewport (`false`) or it is unknown (`null`), add * `loading` with a value of "lazy". */ if ( $maybe_in_viewport ) { $loading_attrs = wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr ); } else { // Only add `loading="lazy"` if the feature is enabled. if ( wp_lazy_loading_enabled( $tag_name, $context ) ) { $loading_attrs['loading'] = 'lazy'; } } /* * If flag was set based on contextual logic above, increase the content * media count, either unconditionally, or based on whether the image size * is larger than the threshold. */ if ( $increase_count ) { wp_increase_content_media_count(); } elseif ( $maybe_increase_count ) { /** This filter is documented in wp-includes/media.php */ $wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 ); if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) { wp_increase_content_media_count(); } } /** * Filters the loading optimization attributes. * * @since 6.4.0 * * @param array $loading_attrs The loading optimization attributes. * @param string $tag_name The tag name. * @param array $attr Array of the attributes for the tag. * @param string $context Context for the element for which the loading optimization attribute is requested. */ return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context ); } /** * Gets the threshold for how many of the first content media elements to not lazy-load. * * This function runs the {@see 'wp_omit_loading_attr_threshold'} filter, which uses a default threshold value of 3. * The filter is only run once per page load, unless the `$force` parameter is used. * * @since 5.9.0 * * @param bool $force Optional. If set to true, the filter will be (re-)applied even if it already has been before. * Default false. * @return int The number of content media elements to not lazy-load. */ function wp_omit_loading_attr_threshold( $force = false ) { static $omit_threshold; // This function may be called multiple times. Run the filter only once per page load. if ( ! isset( $omit_threshold ) || $force ) { /** * Filters the threshold for how many of the first content media elements to not lazy-load. * * For these first content media elements, the `loading` attribute will be omitted. By default, this is the case * for only the very first content media element. * * @since 5.9.0 * @since 6.3.0 The default threshold was changed from 1 to 3. * * @param int $omit_threshold The number of media elements where the `loading` attribute will not be added. Default 3. */ $omit_threshold = apply_filters( 'wp_omit_loading_attr_threshold', 3 ); } return $omit_threshold; } /** * Increases an internal content media count variable. * * @since 5.9.0 * @access private * * @param int $amount Optional. Amount to increase by. Default 1. * @return int The latest content media count, after the increase. */ function wp_increase_content_media_count( $amount = 1 ) { static $content_media_count = 0; $content_media_count += $amount; return $content_media_count; } /** * Determines whether to add `fetchpriority='high'` to loading attributes. * * @since 6.3.0 * @access private * * @param array $loading_attrs Array of the loading optimization attributes for the element. * @param string $tag_name The tag name. * @param array $attr Array of the attributes for the element. * @return array Updated loading optimization attributes for the element. */ function wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr ) { // For now, adding `fetchpriority="high"` is only supported for images. if ( 'img' !== $tag_name ) { return $loading_attrs; } if ( isset( $attr['fetchpriority'] ) ) { /* * While any `fetchpriority` value could be set in `$loading_attrs`, * for consistency we only do it for `fetchpriority="high"` since that * is the only possible value that WordPress core would apply on its * own. */ if ( 'high' === $attr['fetchpriority'] ) { $loading_attrs['fetchpriority'] = 'high'; wp_high_priority_element_flag( false ); } return $loading_attrs; } // Lazy-loading and `fetchpriority="high"` are mutually exclusive. if ( isset( $loading_attrs['loading'] ) && 'lazy' === $loading_attrs['loading'] ) { return $loading_attrs; } if ( ! wp_high_priority_element_flag() ) { return $loading_attrs; } /** * Filters the minimum square-pixels threshold for an image to be eligible as the high-priority image. * * @since 6.3.0 * * @param int $threshold Minimum square-pixels threshold. Default 50000. */ $wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 ); if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) { $loading_attrs['fetchpriority'] = 'high'; wp_high_priority_element_flag( false ); } return $loading_attrs; } /** * Accesses a flag that indicates if an element is a possible candidate for `fetchpriority='high'`. * * @since 6.3.0 * @access private * * @param bool $value Optional. Used to change the static variable. Default null. * @return bool Returns true if high-priority element was marked already, otherwise false. */ function wp_high_priority_element_flag( $value = null ) { static $high_priority_element = true; if ( is_bool( $value ) ) { $high_priority_element = $value; } return $high_priority_element; } /** * Determines the output format for the image editor. * * @since 6.7.0 * @access private * * @param string $filename Path to the image. * @param string $mime_type The source image mime type. * @return string[] An array of mime type mappings. */ function wp_get_image_editor_output_format( $filename, $mime_type ) { $output_format = array( 'image/heic' => 'image/jpeg', 'image/heif' => 'image/jpeg', 'image/heic-sequence' => 'image/jpeg', 'image/heif-sequence' => 'image/jpeg', ); /** * Filters the image editor output format mapping. * * Enables filtering the mime type used to save images. By default HEIC/HEIF images * are converted to JPEGs. * * @see WP_Image_Editor::get_output_format() * * @since 5.8.0 * @since 6.7.0 The default was changed from an empty array to an array * containing the HEIC/HEIF images mime types. * * @param string[] $output_format { * An array of mime type mappings. Maps a source mime type to a new * destination mime type. By default maps HEIC/HEIF input to JPEG output. * * @type string ...$0 The new mime type. * } * @param string $filename Path to the image. * @param string $mime_type The source image mime type. */ return apply_filters( 'image_editor_output_format', $output_format, $filename, $mime_type ); } class-wp-customize-selective-refresh.php 0000666 00000033023 15226762357 0014467 0 ustar 00 <?php /** * Customize API: WP_Customize_Selective_Refresh class * * @package WordPress * @subpackage Customize * @since 4.5.0 */ /** * Core Customizer class for implementing selective refresh. * * @since 4.5.0 */ #[AllowDynamicProperties] final class WP_Customize_Selective_Refresh { /** * Query var used in requests to render partials. * * @since 4.5.0 */ const RENDER_QUERY_VAR = 'wp_customize_render_partials'; /** * Customize manager. * * @since 4.5.0 * @var WP_Customize_Manager */ public $manager; /** * Registered instances of WP_Customize_Partial. * * @since 4.5.0 * @var WP_Customize_Partial[] */ protected $partials = array(); /** * Log of errors triggered when partials are rendered. * * @since 4.5.0 * @var array */ protected $triggered_errors = array(); /** * Keep track of the current partial being rendered. * * @since 4.5.0 * @var string|null */ protected $current_partial_id; /** * Plugin bootstrap for Partial Refresh functionality. * * @since 4.5.0 * * @param WP_Customize_Manager $manager Customizer bootstrap instance. */ public function __construct( WP_Customize_Manager $manager ) { $this->manager = $manager; require_once ABSPATH . WPINC . '/customize/class-wp-customize-partial.php'; add_action( 'customize_preview_init', array( $this, 'init_preview' ) ); } /** * Retrieves the registered partials. * * @since 4.5.0 * * @return array Partials. */ public function partials() { return $this->partials; } /** * Adds a partial. * * @since 4.5.0 * * @see WP_Customize_Partial::__construct() * * @param WP_Customize_Partial|string $id Customize Partial object, or Partial ID. * @param array $args Optional. Array of properties for the new Partials object. * See WP_Customize_Partial::__construct() for information * on accepted arguments. Default empty array. * @return WP_Customize_Partial The instance of the partial that was added. */ public function add_partial( $id, $args = array() ) { if ( $id instanceof WP_Customize_Partial ) { $partial = $id; } else { $class = 'WP_Customize_Partial'; /** This filter is documented in wp-includes/customize/class-wp-customize-selective-refresh.php */ $args = apply_filters( 'customize_dynamic_partial_args', $args, $id ); /** This filter is documented in wp-includes/customize/class-wp-customize-selective-refresh.php */ $class = apply_filters( 'customize_dynamic_partial_class', $class, $id, $args ); $partial = new $class( $this, $id, $args ); } $this->partials[ $partial->id ] = $partial; return $partial; } /** * Retrieves a partial. * * @since 4.5.0 * * @param string $id Customize Partial ID. * @return WP_Customize_Partial|null The partial, if set. Otherwise null. */ public function get_partial( $id ) { if ( isset( $this->partials[ $id ] ) ) { return $this->partials[ $id ]; } else { return null; } } /** * Removes a partial. * * @since 4.5.0 * * @param string $id Customize Partial ID. */ public function remove_partial( $id ) { unset( $this->partials[ $id ] ); } /** * Initializes the Customizer preview. * * @since 4.5.0 */ public function init_preview() { add_action( 'template_redirect', array( $this, 'handle_render_partials_request' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) ); } /** * Enqueues preview scripts. * * @since 4.5.0 */ public function enqueue_preview_scripts() { wp_enqueue_script( 'customize-selective-refresh' ); add_action( 'wp_footer', array( $this, 'export_preview_data' ), 1000 ); } /** * Exports data in preview after it has finished rendering so that partials can be added at runtime. * * @since 4.5.0 */ public function export_preview_data() { $partials = array(); foreach ( $this->partials() as $partial ) { if ( $partial->check_capabilities() ) { $partials[ $partial->id ] = $partial->json(); } } $switched_locale = switch_to_user_locale( get_current_user_id() ); $l10n = array( 'shiftClickToEdit' => __( 'Shift-click to edit this element.' ), 'clickEditMenu' => __( 'Click to edit this menu.' ), 'clickEditWidget' => __( 'Click to edit this widget.' ), 'clickEditTitle' => __( 'Click to edit the site title.' ), 'clickEditMisc' => __( 'Click to edit this element.' ), /* translators: %s: document.write() */ 'badDocumentWrite' => sprintf( __( '%s is forbidden' ), 'document.write()' ), ); if ( $switched_locale ) { restore_previous_locale(); } $exports = array( 'partials' => $partials, 'renderQueryVar' => self::RENDER_QUERY_VAR, 'l10n' => $l10n, ); // Export data to JS. wp_print_inline_script_tag( sprintf( 'var _customizePartialRefreshExports = %s;', wp_json_encode( $exports, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) ) . "\n//# sourceURL=" . rawurlencode( __METHOD__ ) ); } /** * Registers dynamically-created partials. * * @since 4.5.0 * * @see WP_Customize_Manager::add_dynamic_settings() * * @param string[] $partial_ids Array of the partial IDs to add. * @return WP_Customize_Partial[] Array of added WP_Customize_Partial instances. */ public function add_dynamic_partials( $partial_ids ) { $new_partials = array(); foreach ( $partial_ids as $partial_id ) { // Skip partials already created. $partial = $this->get_partial( $partial_id ); if ( $partial ) { continue; } $partial_args = false; $partial_class = 'WP_Customize_Partial'; /** * Filters a dynamic partial's constructor arguments. * * For a dynamic partial to be registered, this filter must be employed * to override the default false value with an array of args to pass to * the WP_Customize_Partial constructor. * * @since 4.5.0 * * @param false|array $partial_args The arguments to the WP_Customize_Partial constructor. * @param string $partial_id ID for dynamic partial. */ $partial_args = apply_filters( 'customize_dynamic_partial_args', $partial_args, $partial_id ); if ( false === $partial_args ) { continue; } /** * Filters the class used to construct partials. * * Allow non-statically created partials to be constructed with custom WP_Customize_Partial subclass. * * @since 4.5.0 * * @param string $partial_class WP_Customize_Partial or a subclass. * @param string $partial_id ID for dynamic partial. * @param array $partial_args The arguments to the WP_Customize_Partial constructor. */ $partial_class = apply_filters( 'customize_dynamic_partial_class', $partial_class, $partial_id, $partial_args ); $partial = new $partial_class( $this, $partial_id, $partial_args ); $this->add_partial( $partial ); $new_partials[] = $partial; } return $new_partials; } /** * Checks whether the request is for rendering partials. * * Note that this will not consider whether the request is authorized or valid, * just that essentially the route is a match. * * @since 4.5.0 * * @return bool Whether the request is for rendering partials. */ public function is_render_partials_request() { return ! empty( $_POST[ self::RENDER_QUERY_VAR ] ); } /** * Handles PHP errors triggered during rendering the partials. * * These errors will be relayed back to the client in the Ajax response. * * @since 4.5.0 * * @param int $errno Error number. * @param string $errstr Error string. * @param string $errfile Error file. * @param int $errline Error line. * @return true Always true. */ public function handle_error( $errno, $errstr, $errfile = null, $errline = null ) { $this->triggered_errors[] = array( 'partial' => $this->current_partial_id, 'error_number' => $errno, 'error_string' => $errstr, 'error_file' => $errfile, 'error_line' => $errline, ); return true; } /** * Handles the Ajax request to return the rendered partials for the requested placements. * * @since 4.5.0 */ public function handle_render_partials_request() { if ( ! $this->is_render_partials_request() ) { return; } /* * Note that is_customize_preview() returning true will entail that the * user passed the 'customize' capability check and the nonce check, since * WP_Customize_Manager::setup_theme() is where the previewing flag is set. */ if ( ! is_customize_preview() ) { wp_send_json_error( 'expected_customize_preview', 403 ); } elseif ( ! isset( $_POST['partials'] ) ) { wp_send_json_error( 'missing_partials', 400 ); } // Ensure that doing selective refresh on 404 template doesn't result in fallback rendering behavior (full refreshes). status_header( 200 ); $partials = json_decode( wp_unslash( $_POST['partials'] ), true ); if ( ! is_array( $partials ) ) { wp_send_json_error( 'malformed_partials' ); } $this->add_dynamic_partials( array_keys( $partials ) ); /** * Fires immediately before partials are rendered. * * Plugins may do things like call wp_enqueue_scripts() and gather a list of the scripts * and styles which may get enqueued in the response. * * @since 4.5.0 * * @param WP_Customize_Selective_Refresh $refresh Selective refresh component. * @param array $partials Placements' context data for the partials rendered in the request. * The array is keyed by partial ID, with each item being an array of * the placements' context data. */ do_action( 'customize_render_partials_before', $this, $partials ); set_error_handler( array( $this, 'handle_error' ), error_reporting() ); $contents = array(); foreach ( $partials as $partial_id => $container_contexts ) { $this->current_partial_id = $partial_id; if ( ! is_array( $container_contexts ) ) { wp_send_json_error( 'malformed_container_contexts' ); } $partial = $this->get_partial( $partial_id ); if ( ! $partial || ! $partial->check_capabilities() ) { $contents[ $partial_id ] = null; continue; } $contents[ $partial_id ] = array(); // @todo The array should include not only the contents, but also whether the container is included? if ( empty( $container_contexts ) ) { // Since there are no container contexts, render just once. $contents[ $partial_id ][] = $partial->render( null ); } else { foreach ( $container_contexts as $container_context ) { $contents[ $partial_id ][] = $partial->render( $container_context ); } } } $this->current_partial_id = null; restore_error_handler(); /** * Fires immediately after partials are rendered. * * Plugins may do things like call wp_footer() to scrape scripts output and return them * via the {@see 'customize_render_partials_response'} filter. * * @since 4.5.0 * * @param WP_Customize_Selective_Refresh $refresh Selective refresh component. * @param array $partials Placements' context data for the partials rendered in the request. * The array is keyed by partial ID, with each item being an array of * the placements' context data. */ do_action( 'customize_render_partials_after', $this, $partials ); $response = array( 'contents' => $contents, ); if ( defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) { $response['errors'] = $this->triggered_errors; } $setting_validities = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() ); $exported_setting_validities = array_map( array( $this->manager, 'prepare_setting_validity_for_js' ), $setting_validities ); $response['setting_validities'] = $exported_setting_validities; /** * Filters the response from rendering the partials. * * Plugins may use this filter to inject `$scripts` and `$styles`, which are dependencies * for the partials being rendered. The response data will be available to the client via * the `render-partials-response` JS event, so the client can then inject the scripts and * styles into the DOM if they have not already been enqueued there. * * If plugins do this, they'll need to take care for any scripts that do `document.write()` * and make sure that these are not injected, or else to override the function to no-op, * or else the page will be destroyed. * * Plugins should be aware that `$scripts` and `$styles` may eventually be included by * default in the response. * * @since 4.5.0 * * @param array $response { * Response. * * @type array $contents Associative array mapping a partial ID its corresponding array of contents * for the containers requested. * @type array $errors List of errors triggered during rendering of partials, if `WP_DEBUG_DISPLAY` * is enabled. * } * @param WP_Customize_Selective_Refresh $refresh Selective refresh component. * @param array $partials Placements' context data for the partials rendered in the request. * The array is keyed by partial ID, with each item being an array of * the placements' context data. */ $response = apply_filters( 'customize_render_partials_response', $response, $this, $partials ); wp_send_json_success( $response ); } } class-wp-http-encoding.php 0000666 00000015041 15226762357 0011573 0 ustar 00 <?php /** * HTTP API: WP_Http_Encoding class * * @package WordPress * @subpackage HTTP * @since 4.4.0 */ /** * Core class used to implement deflate and gzip transfer encoding support for HTTP requests. * * Includes RFC 1950, RFC 1951, and RFC 1952. * * @since 2.8.0 */ #[AllowDynamicProperties] class WP_Http_Encoding { /** * Compress raw string using the deflate format. * * Supports the RFC 1951 standard. * * @since 2.8.0 * * @param string $raw String to compress. * @param int $level Optional. Compression level, 9 is highest. Default 9. * @param string $supports Optional, not used. When implemented it will choose * the right compression based on what the server supports. * @return string|false Compressed string on success, false on failure. */ public static function compress( $raw, $level = 9, $supports = null ) { return gzdeflate( $raw, $level ); } /** * Decompression of deflated string. * * Will attempt to decompress using the RFC 1950 standard, and if that fails * then the RFC 1951 standard deflate will be attempted. Finally, the RFC * 1952 standard gzip decode will be attempted. If all fail, then the * original compressed string will be returned. * * @since 2.8.0 * * @param string $compressed String to decompress. * @param int $length The optional length of the compressed data. * @return string|false Decompressed string on success, false on failure. */ public static function decompress( $compressed, $length = null ) { if ( empty( $compressed ) ) { return $compressed; } $decompressed = @gzinflate( $compressed ); if ( false !== $decompressed ) { return $decompressed; } $decompressed = self::compatible_gzinflate( $compressed ); if ( false !== $decompressed ) { return $decompressed; } $decompressed = @gzuncompress( $compressed ); if ( false !== $decompressed ) { return $decompressed; } if ( function_exists( 'gzdecode' ) ) { $decompressed = @gzdecode( $compressed ); if ( false !== $decompressed ) { return $decompressed; } } return $compressed; } /** * Decompression of deflated string while staying compatible with the majority of servers. * * Certain Servers will return deflated data with headers which PHP's gzinflate() * function cannot handle out of the box. The following function has been created from * various snippets on the gzinflate() PHP documentation. * * Warning: Magic numbers within. Due to the potential different formats that the compressed * data may be returned in, some "magic offsets" are needed to ensure proper decompression * takes place. For a simple pragmatic way to determine the magic offset in use, see: * https://core.trac.wordpress.org/ticket/18273 * * @since 2.8.1 * * @link https://core.trac.wordpress.org/ticket/18273 * @link https://www.php.net/manual/en/function.gzinflate.php#70875 * @link https://www.php.net/manual/en/function.gzinflate.php#77336 * * @param string $gz_data String to decompress. * @return string|false Decompressed string on success, false on failure. */ public static function compatible_gzinflate( $gz_data ) { // Compressed data might contain a full header, if so strip it for gzinflate(). if ( str_starts_with( $gz_data, "\x1f\x8b\x08" ) ) { $i = 10; $flg = ord( substr( $gz_data, 3, 1 ) ); if ( $flg > 0 ) { if ( $flg & 4 ) { list($xlen) = unpack( 'v', substr( $gz_data, $i, 2 ) ); $i = $i + 2 + $xlen; } if ( $flg & 8 ) { $i = strpos( $gz_data, "\0", $i ) + 1; } if ( $flg & 16 ) { $i = strpos( $gz_data, "\0", $i ) + 1; } if ( $flg & 2 ) { $i = $i + 2; } } $decompressed = @gzinflate( substr( $gz_data, $i, -8 ) ); if ( false !== $decompressed ) { return $decompressed; } } // Compressed data from java.util.zip.Deflater amongst others. $decompressed = @gzinflate( substr( $gz_data, 2 ) ); if ( false !== $decompressed ) { return $decompressed; } return false; } /** * What encoding types to accept and their priority values. * * @since 2.8.0 * * @param string $url * @param array $args * @return string Types of encoding to accept. */ public static function accept_encoding( $url, $args ) { $type = array(); $compression_enabled = self::is_available(); if ( ! $args['decompress'] ) { // Decompression specifically disabled. $compression_enabled = false; } elseif ( $args['stream'] ) { // Disable when streaming to file. $compression_enabled = false; } elseif ( isset( $args['limit_response_size'] ) ) { // If only partial content is being requested, we won't be able to decompress it. $compression_enabled = false; } if ( $compression_enabled ) { if ( function_exists( 'gzinflate' ) ) { $type[] = 'deflate;q=1.0'; } if ( function_exists( 'gzuncompress' ) ) { $type[] = 'compress;q=0.5'; } if ( function_exists( 'gzdecode' ) ) { $type[] = 'gzip;q=0.5'; } } /** * Filters the allowed encoding types. * * @since 3.6.0 * * @param string[] $type Array of what encoding types to accept and their priority values. * @param string $url URL of the HTTP request. * @param array $args HTTP request arguments. */ $type = apply_filters( 'wp_http_accept_encoding', $type, $url, $args ); return implode( ', ', $type ); } /** * What encoding the content used when it was compressed to send in the headers. * * @since 2.8.0 * * @return string Content-Encoding string to send in the header. */ public static function content_encoding() { return 'deflate'; } /** * Whether the content be decoded based on the headers. * * @since 2.8.0 * * @param array|string $headers All of the available headers. * @return bool */ public static function should_decode( $headers ) { if ( is_array( $headers ) ) { if ( array_key_exists( 'content-encoding', $headers ) && ! empty( $headers['content-encoding'] ) ) { return true; } } elseif ( is_string( $headers ) ) { return ( stripos( $headers, 'content-encoding:' ) !== false ); } return false; } /** * Whether decompression and compression are supported by the PHP version. * * Each function is tested instead of checking for the zlib extension, to * ensure that the functions all exist in the PHP version and aren't * disabled. * * @since 2.8.0 * * @return bool */ public static function is_available() { return ( function_exists( 'gzuncompress' ) || function_exists( 'gzdeflate' ) || function_exists( 'gzinflate' ) ); } } class-wp-xmlrpc-server.php 0000666 00000644627 15226762357 0011663 0 ustar 00 <?php /** * XML-RPC protocol support for WordPress. * * @package WordPress * @subpackage Publishing */ /** * WordPress XMLRPC server implementation. * * Implements compatibility for Blogger API, MetaWeblog API, MovableType, and * pingback. Additional WordPress API for managing comments, pages, posts, * options, etc. * * As of WordPress 3.5.0, XML-RPC is enabled by default. It can be disabled * via the {@see 'xmlrpc_enabled'} filter found in wp_xmlrpc_server::set_is_enabled(). * * @since 1.5.0 * * @see IXR_Server */ #[AllowDynamicProperties] class wp_xmlrpc_server extends IXR_Server { /** * Methods. * * @var array */ public $methods; /** * Blog options. * * @var array */ public $blog_options; /** * IXR_Error instance. * * @var IXR_Error */ public $error; /** * Flags that the user authentication has failed in this instance of wp_xmlrpc_server. * * @var bool */ protected $auth_failed = false; /** * Flags that XML-RPC is enabled * * @var bool */ private $is_enabled; /** * Registers all of the XMLRPC methods that XMLRPC server understands. * * Sets up server and method property. Passes XMLRPC methods through the * {@see 'xmlrpc_methods'} filter to allow plugins to extend or replace * XML-RPC methods. * * @since 1.5.0 */ public function __construct() { $this->methods = array( // WordPress API. 'wp.getUsersBlogs' => 'this:wp_getUsersBlogs', 'wp.newPost' => 'this:wp_newPost', 'wp.editPost' => 'this:wp_editPost', 'wp.deletePost' => 'this:wp_deletePost', 'wp.getPost' => 'this:wp_getPost', 'wp.getPosts' => 'this:wp_getPosts', 'wp.newTerm' => 'this:wp_newTerm', 'wp.editTerm' => 'this:wp_editTerm', 'wp.deleteTerm' => 'this:wp_deleteTerm', 'wp.getTerm' => 'this:wp_getTerm', 'wp.getTerms' => 'this:wp_getTerms', 'wp.getTaxonomy' => 'this:wp_getTaxonomy', 'wp.getTaxonomies' => 'this:wp_getTaxonomies', 'wp.getUser' => 'this:wp_getUser', 'wp.getUsers' => 'this:wp_getUsers', 'wp.getProfile' => 'this:wp_getProfile', 'wp.editProfile' => 'this:wp_editProfile', 'wp.getPage' => 'this:wp_getPage', 'wp.getPages' => 'this:wp_getPages', 'wp.newPage' => 'this:wp_newPage', 'wp.deletePage' => 'this:wp_deletePage', 'wp.editPage' => 'this:wp_editPage', 'wp.getPageList' => 'this:wp_getPageList', 'wp.getAuthors' => 'this:wp_getAuthors', 'wp.getCategories' => 'this:mw_getCategories', // Alias. 'wp.getTags' => 'this:wp_getTags', 'wp.newCategory' => 'this:wp_newCategory', 'wp.deleteCategory' => 'this:wp_deleteCategory', 'wp.suggestCategories' => 'this:wp_suggestCategories', 'wp.uploadFile' => 'this:mw_newMediaObject', // Alias. 'wp.deleteFile' => 'this:wp_deletePost', // Alias. 'wp.getCommentCount' => 'this:wp_getCommentCount', 'wp.getPostStatusList' => 'this:wp_getPostStatusList', 'wp.getPageStatusList' => 'this:wp_getPageStatusList', 'wp.getPageTemplates' => 'this:wp_getPageTemplates', 'wp.getOptions' => 'this:wp_getOptions', 'wp.setOptions' => 'this:wp_setOptions', 'wp.getComment' => 'this:wp_getComment', 'wp.getComments' => 'this:wp_getComments', 'wp.deleteComment' => 'this:wp_deleteComment', 'wp.editComment' => 'this:wp_editComment', 'wp.newComment' => 'this:wp_newComment', 'wp.getCommentStatusList' => 'this:wp_getCommentStatusList', 'wp.getMediaItem' => 'this:wp_getMediaItem', 'wp.getMediaLibrary' => 'this:wp_getMediaLibrary', 'wp.getPostFormats' => 'this:wp_getPostFormats', 'wp.getPostType' => 'this:wp_getPostType', 'wp.getPostTypes' => 'this:wp_getPostTypes', 'wp.getRevisions' => 'this:wp_getRevisions', 'wp.restoreRevision' => 'this:wp_restoreRevision', // Blogger API. 'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs', 'blogger.getUserInfo' => 'this:blogger_getUserInfo', 'blogger.getPost' => 'this:blogger_getPost', 'blogger.getRecentPosts' => 'this:blogger_getRecentPosts', 'blogger.newPost' => 'this:blogger_newPost', 'blogger.editPost' => 'this:blogger_editPost', 'blogger.deletePost' => 'this:blogger_deletePost', // MetaWeblog API (with MT extensions to structs). 'metaWeblog.newPost' => 'this:mw_newPost', 'metaWeblog.editPost' => 'this:mw_editPost', 'metaWeblog.getPost' => 'this:mw_getPost', 'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts', 'metaWeblog.getCategories' => 'this:mw_getCategories', 'metaWeblog.newMediaObject' => 'this:mw_newMediaObject', /* * MetaWeblog API aliases for Blogger API. * See http://www.xmlrpc.com/stories/storyReader$2460 */ 'metaWeblog.deletePost' => 'this:blogger_deletePost', 'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs', // MovableType API. 'mt.getCategoryList' => 'this:mt_getCategoryList', 'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles', 'mt.getPostCategories' => 'this:mt_getPostCategories', 'mt.setPostCategories' => 'this:mt_setPostCategories', 'mt.supportedMethods' => 'this:mt_supportedMethods', 'mt.supportedTextFilters' => 'this:mt_supportedTextFilters', 'mt.getTrackbackPings' => 'this:mt_getTrackbackPings', 'mt.publishPost' => 'this:mt_publishPost', // Pingback. 'pingback.ping' => 'this:pingback_ping', 'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks', 'demo.sayHello' => 'this:sayHello', 'demo.addTwoNumbers' => 'this:addTwoNumbers', ); $this->initialise_blog_option_info(); /** * Filters the methods exposed by the XML-RPC server. * * This filter can be used to add new methods, and remove built-in methods. * * @since 1.5.0 * * @param string[] $methods An array of XML-RPC methods, keyed by their methodName. */ $this->methods = apply_filters( 'xmlrpc_methods', $this->methods ); $this->set_is_enabled(); } /** * Sets wp_xmlrpc_server::$is_enabled property. * * Determines whether the xmlrpc server is enabled on this WordPress install * and set the is_enabled property accordingly. * * @since 5.7.3 */ private function set_is_enabled() { /* * Respect old get_option() filters left for back-compat when the 'enable_xmlrpc' * option was deprecated in 3.5.0. Use the {@see 'xmlrpc_enabled'} hook instead. */ $is_enabled = apply_filters( 'pre_option_enable_xmlrpc', false ); if ( false === $is_enabled ) { $is_enabled = apply_filters( 'option_enable_xmlrpc', true ); } /** * Filters whether XML-RPC methods requiring authentication are enabled. * * Contrary to the way it's named, this filter does not control whether XML-RPC is *fully* * enabled, rather, it only controls whether XML-RPC methods requiring authentication - * such as for publishing purposes - are enabled. * * Further, the filter does not control whether pingbacks or other custom endpoints that don't * require authentication are enabled. This behavior is expected, and due to how parity was matched * with the `enable_xmlrpc` UI option the filter replaced when it was introduced in 3.5. * * To disable XML-RPC methods that require authentication, use: * * add_filter( 'xmlrpc_enabled', '__return_false' ); * * For more granular control over all XML-RPC methods and requests, see the {@see 'xmlrpc_methods'} * and {@see 'xmlrpc_element_limit'} hooks. * * @since 3.5.0 * * @param bool $is_enabled Whether XML-RPC is enabled. Default true. */ $this->is_enabled = apply_filters( 'xmlrpc_enabled', $is_enabled ); } /** * Makes private/protected methods readable for backward compatibility. * * @since 4.0.0 * * @param string $name Method to call. * @param array $arguments Arguments to pass when calling. * @return array|IXR_Error|false Return value of the callback, false otherwise. */ public function __call( $name, $arguments ) { if ( '_multisite_getUsersBlogs' === $name ) { return $this->_multisite_getUsersBlogs( ...$arguments ); } return false; } /** * Serves the XML-RPC request. * * @since 2.9.0 */ public function serve_request() { $this->IXR_Server( $this->methods ); } /** * Tests XMLRPC API by saying, "Hello!" to client. * * @since 1.5.0 * * @return string Hello string response. */ public function sayHello() { return 'Hello!'; } /** * Tests XMLRPC API by adding two numbers for client. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 A number to add. * @type int $1 A second number to add. * } * @return int Sum of the two given numbers. */ public function addTwoNumbers( $args ) { $number1 = $args[0]; $number2 = $args[1]; return $number1 + $number2; } /** * Logs user in. * * @since 2.8.0 * * @param string $username User's username. * @param string $password User's password. * @return WP_User|false WP_User object if authentication passed, false otherwise. */ public function login( $username, #[\SensitiveParameter] $password ) { if ( ! $this->is_enabled ) { $this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.' ) ) ); return false; } if ( $this->auth_failed ) { $user = new WP_Error( 'login_prevented' ); } else { $user = wp_authenticate( $username, $password ); } if ( is_wp_error( $user ) ) { $this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) ); // Flag that authentication has failed once on this wp_xmlrpc_server instance. $this->auth_failed = true; /** * Filters the XML-RPC user login error message. * * @since 3.5.0 * * @param IXR_Error $error The XML-RPC error message. * @param WP_Error $user WP_Error object. */ $this->error = apply_filters( 'xmlrpc_login_error', $this->error, $user ); return false; } wp_set_current_user( $user->ID ); return $user; } /** * Checks user's credentials. Deprecated. * * @since 1.5.0 * @deprecated 2.8.0 Use wp_xmlrpc_server::login() * @see wp_xmlrpc_server::login() * * @param string $username User's username. * @param string $password User's password. * @return bool Whether authentication passed. */ public function login_pass_ok( $username, #[\SensitiveParameter] $password ) { return (bool) $this->login( $username, $password ); } /** * Escapes string or array of strings for database. * * @since 1.5.2 * * @param string|array $data Escape single string or array of strings. * @return string|void Returns with string is passed, alters by-reference * when array is passed. */ public function escape( &$data ) { if ( ! is_array( $data ) ) { return wp_slash( $data ); } foreach ( $data as &$v ) { if ( is_array( $v ) ) { $this->escape( $v ); } elseif ( ! is_object( $v ) ) { $v = wp_slash( $v ); } } } /** * Sends error response to client. * * Sends an XML error response to the client. If the endpoint is enabled * an HTTP 200 response is always sent per the XML-RPC specification. * * @since 5.7.3 * * @param IXR_Error|string $error Error code or an error object. * @param false $message Error message. Optional. */ public function error( $error, $message = false ) { // Accepts either an error object or an error code and message if ( $message && ! is_object( $error ) ) { $error = new IXR_Error( $error, $message ); } if ( ! $this->is_enabled ) { status_header( $error->code ); } $this->output( $error->getXml() ); } /** * Retrieves custom fields for post. * * @since 2.5.0 * * @param int $post_id Post ID. * @return array Custom fields, if exist. */ public function get_custom_fields( $post_id ) { $post_id = (int) $post_id; $custom_fields = array(); foreach ( (array) has_meta( $post_id ) as $meta ) { // Don't expose protected fields. if ( ! current_user_can( 'edit_post_meta', $post_id, $meta['meta_key'] ) ) { continue; } $custom_fields[] = array( 'id' => $meta['meta_id'], 'key' => $meta['meta_key'], 'value' => $meta['meta_value'], ); } return $custom_fields; } /** * Sets custom fields for post. * * @since 2.5.0 * * @param int $post_id Post ID. * @param array $fields Custom fields. */ public function set_custom_fields( $post_id, $fields ) { $post_id = (int) $post_id; foreach ( (array) $fields as $meta ) { if ( isset( $meta['id'] ) ) { $meta['id'] = (int) $meta['id']; $pmeta = get_metadata_by_mid( 'post', $meta['id'] ); if ( ! $pmeta || (int) $pmeta->post_id !== $post_id ) { continue; } if ( isset( $meta['key'] ) ) { $meta['key'] = wp_unslash( $meta['key'] ); if ( $meta['key'] !== $pmeta->meta_key ) { continue; } $meta['value'] = wp_unslash( $meta['value'] ); if ( current_user_can( 'edit_post_meta', $post_id, $meta['key'] ) ) { update_metadata_by_mid( 'post', $meta['id'], $meta['value'] ); } } elseif ( current_user_can( 'delete_post_meta', $post_id, $pmeta->meta_key ) ) { delete_metadata_by_mid( 'post', $meta['id'] ); } } elseif ( current_user_can( 'add_post_meta', $post_id, wp_unslash( $meta['key'] ) ) ) { add_post_meta( $post_id, $meta['key'], $meta['value'] ); } } } /** * Retrieves custom fields for a term. * * @since 4.9.0 * * @param int $term_id Term ID. * @return array Array of custom fields, if they exist. */ public function get_term_custom_fields( $term_id ) { $term_id = (int) $term_id; $custom_fields = array(); foreach ( (array) has_term_meta( $term_id ) as $meta ) { if ( ! current_user_can( 'edit_term_meta', $term_id ) ) { continue; } $custom_fields[] = array( 'id' => $meta['meta_id'], 'key' => $meta['meta_key'], 'value' => $meta['meta_value'], ); } return $custom_fields; } /** * Sets custom fields for a term. * * @since 4.9.0 * * @param int $term_id Term ID. * @param array $fields Custom fields. */ public function set_term_custom_fields( $term_id, $fields ) { $term_id = (int) $term_id; foreach ( (array) $fields as $meta ) { if ( isset( $meta['id'] ) ) { $meta['id'] = (int) $meta['id']; $pmeta = get_metadata_by_mid( 'term', $meta['id'] ); if ( isset( $meta['key'] ) ) { $meta['key'] = wp_unslash( $meta['key'] ); if ( $meta['key'] !== $pmeta->meta_key ) { continue; } $meta['value'] = wp_unslash( $meta['value'] ); if ( current_user_can( 'edit_term_meta', $term_id ) ) { update_metadata_by_mid( 'term', $meta['id'], $meta['value'] ); } } elseif ( current_user_can( 'delete_term_meta', $term_id ) ) { delete_metadata_by_mid( 'term', $meta['id'] ); } } elseif ( current_user_can( 'add_term_meta', $term_id ) ) { add_term_meta( $term_id, $meta['key'], $meta['value'] ); } } } /** * Sets up blog options property. * * Passes property through {@see 'xmlrpc_blog_options'} filter. * * @since 2.6.0 */ public function initialise_blog_option_info() { $this->blog_options = array( // Read-only options. 'software_name' => array( 'desc' => __( 'Software Name' ), 'readonly' => true, 'value' => 'WordPress', ), 'software_version' => array( 'desc' => __( 'Software Version' ), 'readonly' => true, 'value' => get_bloginfo( 'version' ), ), 'blog_url' => array( 'desc' => __( 'WordPress Address (URL)' ), 'readonly' => true, 'option' => 'siteurl', ), 'home_url' => array( 'desc' => __( 'Site Address (URL)' ), 'readonly' => true, 'option' => 'home', ), 'login_url' => array( 'desc' => __( 'Login Address (URL)' ), 'readonly' => true, 'value' => wp_login_url(), ), 'admin_url' => array( 'desc' => __( 'The URL to the admin area' ), 'readonly' => true, 'value' => get_admin_url(), ), 'image_default_link_type' => array( 'desc' => __( 'Image default link type' ), 'readonly' => true, 'option' => 'image_default_link_type', ), 'image_default_size' => array( 'desc' => __( 'Image default size' ), 'readonly' => true, 'option' => 'image_default_size', ), 'image_default_align' => array( 'desc' => __( 'Image default align' ), 'readonly' => true, 'option' => 'image_default_align', ), 'template' => array( 'desc' => __( 'Template' ), 'readonly' => true, 'option' => 'template', ), 'stylesheet' => array( 'desc' => __( 'Stylesheet' ), 'readonly' => true, 'option' => 'stylesheet', ), 'post_thumbnail' => array( 'desc' => __( 'Post Thumbnail' ), 'readonly' => true, 'value' => current_theme_supports( 'post-thumbnails' ), ), // Updatable options. 'time_zone' => array( 'desc' => __( 'Time Zone' ), 'readonly' => false, 'option' => 'gmt_offset', ), 'blog_title' => array( 'desc' => __( 'Site Title' ), 'readonly' => false, 'option' => 'blogname', ), 'blog_tagline' => array( 'desc' => __( 'Site Tagline' ), 'readonly' => false, 'option' => 'blogdescription', ), 'date_format' => array( 'desc' => __( 'Date Format' ), 'readonly' => false, 'option' => 'date_format', ), 'time_format' => array( 'desc' => __( 'Time Format' ), 'readonly' => false, 'option' => 'time_format', ), 'users_can_register' => array( 'desc' => __( 'Allow new users to sign up' ), 'readonly' => false, 'option' => 'users_can_register', ), 'thumbnail_size_w' => array( 'desc' => __( 'Thumbnail Width' ), 'readonly' => false, 'option' => 'thumbnail_size_w', ), 'thumbnail_size_h' => array( 'desc' => __( 'Thumbnail Height' ), 'readonly' => false, 'option' => 'thumbnail_size_h', ), 'thumbnail_crop' => array( 'desc' => __( 'Crop thumbnail to exact dimensions' ), 'readonly' => false, 'option' => 'thumbnail_crop', ), 'medium_size_w' => array( 'desc' => __( 'Medium size image width' ), 'readonly' => false, 'option' => 'medium_size_w', ), 'medium_size_h' => array( 'desc' => __( 'Medium size image height' ), 'readonly' => false, 'option' => 'medium_size_h', ), 'medium_large_size_w' => array( 'desc' => __( 'Medium-Large size image width' ), 'readonly' => false, 'option' => 'medium_large_size_w', ), 'medium_large_size_h' => array( 'desc' => __( 'Medium-Large size image height' ), 'readonly' => false, 'option' => 'medium_large_size_h', ), 'large_size_w' => array( 'desc' => __( 'Large size image width' ), 'readonly' => false, 'option' => 'large_size_w', ), 'large_size_h' => array( 'desc' => __( 'Large size image height' ), 'readonly' => false, 'option' => 'large_size_h', ), 'default_comment_status' => array( 'desc' => __( 'Allow people to submit comments on new posts.' ), 'readonly' => false, 'option' => 'default_comment_status', ), 'default_ping_status' => array( 'desc' => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new posts.' ), 'readonly' => false, 'option' => 'default_ping_status', ), ); /** * Filters the XML-RPC blog options property. * * @since 2.6.0 * * @param array $blog_options An array of XML-RPC blog options. */ $this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options ); } /** * Retrieves the blogs of the user. * * @since 2.6.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type string $0 Username. * @type string $1 Password. * } * @return array|IXR_Error Array contains: * - 'isAdmin' * - 'isPrimary' - whether the blog is the user's primary blog * - 'url' * - 'blogid' * - 'blogName' * - 'xmlrpc' - url of xmlrpc endpoint */ public function wp_getUsersBlogs( $args ) { if ( ! $this->minimum_args( $args, 2 ) ) { return $this->error; } // If this isn't on WPMU then just use blogger_getUsersBlogs(). if ( ! is_multisite() ) { array_unshift( $args, 1 ); return $this->blogger_getUsersBlogs( $args ); } $this->escape( $args ); $username = $args[0]; $password = $args[1]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** * Fires after the XML-RPC user has been authenticated but before the rest of * the method logic begins. * * All built-in XML-RPC methods use the action xmlrpc_call, with a parameter * equal to the method's name, e.g., wp.getUsersBlogs, wp.newPost, etc. * * @since 2.5.0 * @since 5.7.0 Added the `$args` and `$server` parameters. * * @param string $name The method name. * @param array|string $args The escaped arguments passed to the method. * @param wp_xmlrpc_server $server The XML-RPC server instance. */ do_action( 'xmlrpc_call', 'wp.getUsersBlogs', $args, $this ); $blogs = (array) get_blogs_of_user( $user->ID ); $struct = array(); $primary_blog_id = 0; $active_blog = get_active_blog_for_user( $user->ID ); if ( $active_blog ) { $primary_blog_id = (int) $active_blog->blog_id; } $current_network_id = get_current_network_id(); foreach ( $blogs as $blog ) { // Don't include blogs that aren't hosted at this site. if ( $blog->site_id !== $current_network_id ) { continue; } $blog_id = $blog->userblog_id; switch_to_blog( $blog_id ); $is_admin = current_user_can( 'manage_options' ); $is_primary = ( (int) $blog_id === $primary_blog_id ); $struct[] = array( 'isAdmin' => $is_admin, 'isPrimary' => $is_primary, 'url' => home_url( '/' ), 'blogid' => (string) $blog_id, 'blogName' => get_option( 'blogname' ), 'xmlrpc' => site_url( 'xmlrpc.php', 'rpc' ), ); restore_current_blog(); } return $struct; } /** * Checks if the method received at least the minimum number of arguments. * * @since 3.4.0 * * @param array $args An array of arguments to check. * @param int $count Minimum number of arguments. * @return bool True if `$args` contains at least `$count` arguments, false otherwise. */ protected function minimum_args( $args, $count ) { if ( ! is_array( $args ) || count( $args ) < $count ) { $this->error = new IXR_Error( 400, __( 'Insufficient arguments passed to this XML-RPC method.' ) ); return false; } return true; } /** * Prepares taxonomy data for return in an XML-RPC object. * * @param WP_Taxonomy $taxonomy The unprepared taxonomy data. * @param array $fields The subset of taxonomy fields to return. * @return array The prepared taxonomy data. */ protected function _prepare_taxonomy( $taxonomy, $fields ) { $_taxonomy = array( 'name' => $taxonomy->name, 'label' => $taxonomy->label, 'hierarchical' => (bool) $taxonomy->hierarchical, 'public' => (bool) $taxonomy->public, 'show_ui' => (bool) $taxonomy->show_ui, '_builtin' => (bool) $taxonomy->_builtin, ); if ( in_array( 'labels', $fields, true ) ) { $_taxonomy['labels'] = (array) $taxonomy->labels; } if ( in_array( 'cap', $fields, true ) ) { $_taxonomy['cap'] = (array) $taxonomy->cap; } if ( in_array( 'menu', $fields, true ) ) { $_taxonomy['show_in_menu'] = (bool) $taxonomy->show_in_menu; } if ( in_array( 'object_type', $fields, true ) ) { $_taxonomy['object_type'] = array_unique( (array) $taxonomy->object_type ); } /** * Filters XML-RPC-prepared data for the given taxonomy. * * @since 3.4.0 * * @param array $_taxonomy An array of taxonomy data. * @param WP_Taxonomy $taxonomy Taxonomy object. * @param array $fields The subset of taxonomy fields to return. */ return apply_filters( 'xmlrpc_prepare_taxonomy', $_taxonomy, $taxonomy, $fields ); } /** * Prepares term data for return in an XML-RPC object. * * @param array|object $term The unprepared term data. * @return array The prepared term data. */ protected function _prepare_term( $term ) { $_term = $term; if ( ! is_array( $_term ) ) { $_term = get_object_vars( $_term ); } // For integers which may be larger than XML-RPC supports ensure we return strings. $_term['term_id'] = (string) $_term['term_id']; $_term['term_group'] = (string) $_term['term_group']; $_term['term_taxonomy_id'] = (string) $_term['term_taxonomy_id']; $_term['parent'] = (string) $_term['parent']; // Count we are happy to return as an integer because people really shouldn't use terms that much. $_term['count'] = (int) $_term['count']; // Get term meta. $_term['custom_fields'] = $this->get_term_custom_fields( $_term['term_id'] ); /** * Filters XML-RPC-prepared data for the given term. * * @since 3.4.0 * * @param array $_term An array of term data. * @param array|object $term Term object or array. */ return apply_filters( 'xmlrpc_prepare_term', $_term, $term ); } /** * Converts a WordPress date string to an IXR_Date object. * * @param string $date Date string to convert. * @return IXR_Date IXR_Date object. */ protected function _convert_date( $date ) { if ( '0000-00-00 00:00:00' === $date ) { return new IXR_Date( '00000000T00:00:00Z' ); } return new IXR_Date( mysql2date( 'Ymd\TH:i:s', $date, false ) ); } /** * Converts a WordPress GMT date string to an IXR_Date object. * * @param string $date_gmt WordPress GMT date string. * @param string $date Date string. * @return IXR_Date IXR_Date object. */ protected function _convert_date_gmt( $date_gmt, $date ) { if ( '0000-00-00 00:00:00' !== $date && '0000-00-00 00:00:00' === $date_gmt ) { return new IXR_Date( get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $date, false ), 'Ymd\TH:i:s' ) ); } return $this->_convert_date( $date_gmt ); } /** * Prepares post data for return in an XML-RPC object. * * @param array $post The unprepared post data. * @param array $fields The subset of post type fields to return. * @return array The prepared post data. */ protected function _prepare_post( $post, $fields ) { // Holds the data for this post. built up based on $fields. $_post = array( 'post_id' => (string) $post['ID'] ); // Prepare common post fields. $post_fields = array( 'post_title' => $post['post_title'], 'post_date' => $this->_convert_date( $post['post_date'] ), 'post_date_gmt' => $this->_convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ), 'post_modified' => $this->_convert_date( $post['post_modified'] ), 'post_modified_gmt' => $this->_convert_date_gmt( $post['post_modified_gmt'], $post['post_modified'] ), 'post_status' => $post['post_status'], 'post_type' => $post['post_type'], 'post_name' => $post['post_name'], 'post_author' => $post['post_author'], 'post_password' => $post['post_password'], 'post_excerpt' => $post['post_excerpt'], 'post_content' => $post['post_content'], 'post_parent' => (string) $post['post_parent'], 'post_mime_type' => $post['post_mime_type'], 'link' => get_permalink( $post['ID'] ), 'guid' => $post['guid'], 'menu_order' => (int) $post['menu_order'], 'comment_status' => $post['comment_status'], 'ping_status' => $post['ping_status'], 'sticky' => ( 'post' === $post['post_type'] && is_sticky( $post['ID'] ) ), ); // Thumbnail. $post_fields['post_thumbnail'] = array(); $thumbnail_id = get_post_thumbnail_id( $post['ID'] ); if ( $thumbnail_id ) { $thumbnail_size = current_theme_supports( 'post-thumbnail' ) ? 'post-thumbnail' : 'thumbnail'; $post_fields['post_thumbnail'] = $this->_prepare_media_item( get_post( $thumbnail_id ), $thumbnail_size ); } // Consider future posts as published. if ( 'future' === $post_fields['post_status'] ) { $post_fields['post_status'] = 'publish'; } // Fill in blank post format. $post_fields['post_format'] = get_post_format( $post['ID'] ); if ( empty( $post_fields['post_format'] ) ) { $post_fields['post_format'] = 'standard'; } // Merge requested $post_fields fields into $_post. if ( in_array( 'post', $fields, true ) ) { $_post = array_merge( $_post, $post_fields ); } else { $requested_fields = array_intersect_key( $post_fields, array_flip( $fields ) ); $_post = array_merge( $_post, $requested_fields ); } $all_taxonomy_fields = in_array( 'taxonomies', $fields, true ); if ( $all_taxonomy_fields || in_array( 'terms', $fields, true ) ) { $post_type_taxonomies = get_object_taxonomies( $post['post_type'], 'names' ); $terms = wp_get_object_terms( $post['ID'], $post_type_taxonomies ); $_post['terms'] = array(); foreach ( $terms as $term ) { $_post['terms'][] = $this->_prepare_term( $term ); } } if ( in_array( 'custom_fields', $fields, true ) ) { $_post['custom_fields'] = $this->get_custom_fields( $post['ID'] ); } if ( in_array( 'enclosure', $fields, true ) ) { $_post['enclosure'] = array(); $enclosures = (array) get_post_meta( $post['ID'], 'enclosure' ); if ( ! empty( $enclosures ) ) { $encdata = explode( "\n", $enclosures[0] ); $_post['enclosure']['url'] = trim( htmlspecialchars( $encdata[0] ) ); $_post['enclosure']['length'] = (int) trim( $encdata[1] ); $_post['enclosure']['type'] = trim( $encdata[2] ); } } /** * Filters XML-RPC-prepared date for the given post. * * @since 3.4.0 * * @param array $_post An array of modified post data. * @param array $post An array of post data. * @param array $fields An array of post fields. */ return apply_filters( 'xmlrpc_prepare_post', $_post, $post, $fields ); } /** * Prepares post data for return in an XML-RPC object. * * @since 3.4.0 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object. * * @param WP_Post_Type $post_type Post type object. * @param array $fields The subset of post fields to return. * @return array The prepared post type data. */ protected function _prepare_post_type( $post_type, $fields ) { $_post_type = array( 'name' => $post_type->name, 'label' => $post_type->label, 'hierarchical' => (bool) $post_type->hierarchical, 'public' => (bool) $post_type->public, 'show_ui' => (bool) $post_type->show_ui, '_builtin' => (bool) $post_type->_builtin, 'has_archive' => (bool) $post_type->has_archive, 'supports' => get_all_post_type_supports( $post_type->name ), ); if ( in_array( 'labels', $fields, true ) ) { $_post_type['labels'] = (array) $post_type->labels; } if ( in_array( 'cap', $fields, true ) ) { $_post_type['cap'] = (array) $post_type->cap; $_post_type['map_meta_cap'] = (bool) $post_type->map_meta_cap; } if ( in_array( 'menu', $fields, true ) ) { $_post_type['menu_position'] = (int) $post_type->menu_position; $_post_type['menu_icon'] = $post_type->menu_icon; $_post_type['show_in_menu'] = (bool) $post_type->show_in_menu; } if ( in_array( 'taxonomies', $fields, true ) ) { $_post_type['taxonomies'] = get_object_taxonomies( $post_type->name, 'names' ); } /** * Filters XML-RPC-prepared date for the given post type. * * @since 3.4.0 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object. * * @param array $_post_type An array of post type data. * @param WP_Post_Type $post_type Post type object. */ return apply_filters( 'xmlrpc_prepare_post_type', $_post_type, $post_type ); } /** * Prepares media item data for return in an XML-RPC object. * * @param WP_Post $media_item The unprepared media item data. * @param string $thumbnail_size The image size to use for the thumbnail URL. * @return array The prepared media item data. */ protected function _prepare_media_item( $media_item, $thumbnail_size = 'thumbnail' ) { $_media_item = array( 'attachment_id' => (string) $media_item->ID, 'date_created_gmt' => $this->_convert_date_gmt( $media_item->post_date_gmt, $media_item->post_date ), 'parent' => $media_item->post_parent, 'link' => wp_get_attachment_url( $media_item->ID ), 'title' => $media_item->post_title, 'caption' => $media_item->post_excerpt, 'description' => $media_item->post_content, 'metadata' => wp_get_attachment_metadata( $media_item->ID ), 'type' => $media_item->post_mime_type, 'alt' => get_post_meta( $media_item->ID, '_wp_attachment_image_alt', true ), ); $thumbnail_src = image_downsize( $media_item->ID, $thumbnail_size ); if ( $thumbnail_src ) { $_media_item['thumbnail'] = $thumbnail_src[0]; } else { $_media_item['thumbnail'] = $_media_item['link']; } /** * Filters XML-RPC-prepared data for the given media item. * * @since 3.4.0 * * @param array $_media_item An array of media item data. * @param WP_Post $media_item Media item object. * @param string $thumbnail_size Image size. */ return apply_filters( 'xmlrpc_prepare_media_item', $_media_item, $media_item, $thumbnail_size ); } /** * Prepares page data for return in an XML-RPC object. * * @param WP_Post $page The unprepared page data. * @return array The prepared page data. */ protected function _prepare_page( $page ) { // Get all of the page content and link. $full_page = get_extended( $page->post_content ); $link = get_permalink( $page->ID ); // Get info the page parent if there is one. $parent_title = ''; if ( ! empty( $page->post_parent ) ) { $parent = get_post( $page->post_parent ); $parent_title = $parent->post_title; } // Determine comment and ping settings. $allow_comments = comments_open( $page->ID ) ? 1 : 0; $allow_pings = pings_open( $page->ID ) ? 1 : 0; // Format page date. $page_date = $this->_convert_date( $page->post_date ); $page_date_gmt = $this->_convert_date_gmt( $page->post_date_gmt, $page->post_date ); // Pull the categories info together. $categories = array(); if ( is_object_in_taxonomy( 'page', 'category' ) ) { foreach ( wp_get_post_categories( $page->ID ) as $cat_id ) { $categories[] = get_cat_name( $cat_id ); } } // Get the author info. $author = get_userdata( $page->post_author ); $page_template = get_page_template_slug( $page->ID ); if ( empty( $page_template ) ) { $page_template = 'default'; } $_page = array( 'dateCreated' => $page_date, 'userid' => $page->post_author, 'page_id' => $page->ID, 'page_status' => $page->post_status, 'description' => $full_page['main'], 'title' => $page->post_title, 'link' => $link, 'permaLink' => $link, 'categories' => $categories, 'excerpt' => $page->post_excerpt, 'text_more' => $full_page['extended'], 'mt_allow_comments' => $allow_comments, 'mt_allow_pings' => $allow_pings, 'wp_slug' => $page->post_name, 'wp_password' => $page->post_password, 'wp_author' => $author->display_name, 'wp_page_parent_id' => $page->post_parent, 'wp_page_parent_title' => $parent_title, 'wp_page_order' => $page->menu_order, 'wp_author_id' => (string) $author->ID, 'wp_author_display_name' => $author->display_name, 'date_created_gmt' => $page_date_gmt, 'custom_fields' => $this->get_custom_fields( $page->ID ), 'wp_page_template' => $page_template, ); /** * Filters XML-RPC-prepared data for the given page. * * @since 3.4.0 * * @param array $_page An array of page data. * @param WP_Post $page Page object. */ return apply_filters( 'xmlrpc_prepare_page', $_page, $page ); } /** * Prepares comment data for return in an XML-RPC object. * * @param WP_Comment $comment The unprepared comment data. * @return array The prepared comment data. */ protected function _prepare_comment( $comment ) { // Format page date. $comment_date_gmt = $this->_convert_date_gmt( $comment->comment_date_gmt, $comment->comment_date ); if ( '0' === $comment->comment_approved ) { $comment_status = 'hold'; } elseif ( 'spam' === $comment->comment_approved ) { $comment_status = 'spam'; } elseif ( '1' === $comment->comment_approved ) { $comment_status = 'approve'; } else { $comment_status = $comment->comment_approved; } $_comment = array( 'date_created_gmt' => $comment_date_gmt, 'user_id' => $comment->user_id, 'comment_id' => $comment->comment_ID, 'parent' => $comment->comment_parent, 'status' => $comment_status, 'content' => $comment->comment_content, 'link' => get_comment_link( $comment ), 'post_id' => $comment->comment_post_ID, 'post_title' => get_the_title( $comment->comment_post_ID ), 'author' => $comment->comment_author, 'author_url' => $comment->comment_author_url, 'author_email' => $comment->comment_author_email, 'author_ip' => $comment->comment_author_IP, 'type' => $comment->comment_type, ); /** * Filters XML-RPC-prepared data for the given comment. * * @since 3.4.0 * * @param array $_comment An array of prepared comment data. * @param WP_Comment $comment Comment object. */ return apply_filters( 'xmlrpc_prepare_comment', $_comment, $comment ); } /** * Prepares user data for return in an XML-RPC object. * * @param WP_User $user The unprepared user object. * @param array $fields The subset of user fields to return. * @return array The prepared user data. */ protected function _prepare_user( $user, $fields ) { $_user = array( 'user_id' => (string) $user->ID ); $user_fields = array( 'username' => $user->user_login, 'first_name' => $user->user_firstname, 'last_name' => $user->user_lastname, 'registered' => $this->_convert_date( $user->user_registered ), 'bio' => $user->user_description, 'email' => $user->user_email, 'nickname' => $user->nickname, 'nicename' => $user->user_nicename, 'url' => $user->user_url, 'display_name' => $user->display_name, 'roles' => $user->roles, ); if ( in_array( 'all', $fields, true ) ) { $_user = array_merge( $_user, $user_fields ); } else { if ( in_array( 'basic', $fields, true ) ) { $basic_fields = array( 'username', 'email', 'registered', 'display_name', 'nicename' ); $fields = array_merge( $fields, $basic_fields ); } $requested_fields = array_intersect_key( $user_fields, array_flip( $fields ) ); $_user = array_merge( $_user, $requested_fields ); } /** * Filters XML-RPC-prepared data for the given user. * * @since 3.5.0 * * @param array $_user An array of user data. * @param WP_User $user User object. * @param array $fields An array of user fields. */ return apply_filters( 'xmlrpc_prepare_user', $_user, $user, $fields ); } /** * Creates a new post for any registered post type. * * @since 3.4.0 * * @link https://en.wikipedia.org/wiki/RSS_enclosure for information on RSS enclosures. * * @param array $args { * Method arguments. Note: top-level arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 { * Content struct for adding a new post. See wp_insert_post() for information on * additional post fields * * @type string $post_type Post type. Default 'post'. * @type string $post_status Post status. Default 'draft' * @type string $post_title Post title. * @type int $post_author Post author ID. * @type string $post_excerpt Post excerpt. * @type string $post_content Post content. * @type string $post_date_gmt Post date in GMT. * @type string $post_date Post date. * @type string $post_password Post password (20-character limit). * @type string $comment_status Post comment enabled status. Accepts 'open' or 'closed'. * @type string $ping_status Post ping status. Accepts 'open' or 'closed'. * @type bool $sticky Whether the post should be sticky. Automatically false if * `$post_status` is 'private'. * @type int $post_thumbnail ID of an image to use as the post thumbnail/featured image. * @type array $custom_fields Array of meta key/value pairs to add to the post. * @type array $terms Associative array with taxonomy names as keys and arrays * of term IDs as values. * @type array $terms_names Associative array with taxonomy names as keys and arrays * of term names as values. * @type array $enclosure { * Array of feed enclosure data to add to post meta. * * @type string $url URL for the feed enclosure. * @type int $length Size in bytes of the enclosure. * @type string $type Mime-type for the enclosure. * } * } * } * @return int|IXR_Error Post ID on success, IXR_Error instance otherwise. */ public function wp_newPost( $args ) { if ( ! $this->minimum_args( $args, 4 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $content_struct = $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } // Convert the date field back to IXR form. if ( isset( $content_struct['post_date'] ) && ! ( $content_struct['post_date'] instanceof IXR_Date ) ) { $content_struct['post_date'] = $this->_convert_date( $content_struct['post_date'] ); } /* * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct, * since _insert_post() will ignore the non-GMT date if the GMT date is set. */ if ( isset( $content_struct['post_date_gmt'] ) && ! ( $content_struct['post_date_gmt'] instanceof IXR_Date ) ) { if ( '0000-00-00 00:00:00' === $content_struct['post_date_gmt'] || isset( $content_struct['post_date'] ) ) { unset( $content_struct['post_date_gmt'] ); } else { $content_struct['post_date_gmt'] = $this->_convert_date( $content_struct['post_date_gmt'] ); } } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.newPost', $args, $this ); unset( $content_struct['ID'] ); return $this->_insert_post( $user, $content_struct ); } /** * Helper method for filtering out elements from an array. * * @since 3.4.0 * * @param int $count Number to compare to one. * @return bool True if the number is greater than one, false otherwise. */ private function _is_greater_than_one( $count ) { return $count > 1; } /** * Encapsulates the logic for sticking a post and determining if * the user has permission to do so. * * @since 4.3.0 * * @param array $post_data * @param bool $update * @return void|IXR_Error */ private function _toggle_sticky( $post_data, $update = false ) { $post_type = get_post_type_object( $post_data['post_type'] ); // Private and password-protected posts cannot be stickied. if ( 'private' === $post_data['post_status'] || ! empty( $post_data['post_password'] ) ) { // Error if the client tried to stick the post, otherwise, silently unstick. if ( ! empty( $post_data['sticky'] ) ) { return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) ); } if ( $update ) { unstick_post( $post_data['ID'] ); } } elseif ( isset( $post_data['sticky'] ) ) { if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to make posts sticky.' ) ); } $sticky = wp_validate_boolean( $post_data['sticky'] ); if ( $sticky ) { stick_post( $post_data['ID'] ); } else { unstick_post( $post_data['ID'] ); } } } /** * Helper method for wp_newPost() and wp_editPost(), containing shared logic. * * @since 3.4.0 * * @see wp_insert_post() * * @param WP_User $user The post author if post_author isn't set in $content_struct. * @param array|IXR_Error $content_struct Post data to insert. * @return IXR_Error|string */ protected function _insert_post( $user, $content_struct ) { $defaults = array( 'post_status' => 'draft', 'post_type' => 'post', 'post_author' => 0, 'post_password' => '', 'post_excerpt' => '', 'post_content' => '', 'post_title' => '', 'post_date' => '', 'post_date_gmt' => '', 'post_format' => null, 'post_name' => null, 'post_thumbnail' => null, 'post_parent' => 0, 'ping_status' => '', 'comment_status' => '', 'custom_fields' => null, 'terms_names' => null, 'terms' => null, 'sticky' => null, 'enclosure' => null, 'ID' => null, ); $post_data = wp_parse_args( array_intersect_key( $content_struct, $defaults ), $defaults ); $post_type = get_post_type_object( $post_data['post_type'] ); if ( ! $post_type ) { return new IXR_Error( 403, __( 'Invalid post type.' ) ); } $update = ! empty( $post_data['ID'] ); if ( $update ) { if ( ! get_post( $post_data['ID'] ) ) { return new IXR_Error( 401, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'edit_post', $post_data['ID'] ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); } if ( get_post_type( $post_data['ID'] ) !== $post_data['post_type'] ) { return new IXR_Error( 401, __( 'The post type may not be changed.' ) ); } } else { if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) ); } } switch ( $post_data['post_status'] ) { case 'draft': case 'pending': break; case 'private': if ( ! current_user_can( $post_type->cap->publish_posts ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to create private posts in this post type.' ) ); } break; case 'publish': case 'future': if ( ! current_user_can( $post_type->cap->publish_posts ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts in this post type.' ) ); } break; default: if ( ! get_post_status_object( $post_data['post_status'] ) ) { $post_data['post_status'] = 'draft'; } break; } if ( ! empty( $post_data['post_password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to create password protected posts in this post type.' ) ); } $post_data['post_author'] = absint( $post_data['post_author'] ); if ( ! empty( $post_data['post_author'] ) && $post_data['post_author'] !== $user->ID ) { if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) ); } $author = get_userdata( $post_data['post_author'] ); if ( ! $author ) { return new IXR_Error( 404, __( 'Invalid author ID.' ) ); } } else { $post_data['post_author'] = $user->ID; } if ( 'open' !== $post_data['comment_status'] && 'closed' !== $post_data['comment_status'] ) { unset( $post_data['comment_status'] ); } if ( 'open' !== $post_data['ping_status'] && 'closed' !== $post_data['ping_status'] ) { unset( $post_data['ping_status'] ); } // Do some timestamp voodoo. if ( ! empty( $post_data['post_date_gmt'] ) ) { // We know this is supposed to be GMT, so we're going to slap that Z on there by force. $date_created = rtrim( $post_data['post_date_gmt']->getIso(), 'Z' ) . 'Z'; } elseif ( ! empty( $post_data['post_date'] ) ) { $date_created = $post_data['post_date']->getIso(); } // Default to not flagging the post date to be edited unless it's intentional. $post_data['edit_date'] = false; if ( ! empty( $date_created ) ) { $post_data['post_date'] = iso8601_to_datetime( $date_created ); $post_data['post_date_gmt'] = iso8601_to_datetime( $date_created, 'gmt' ); // Flag the post date to be edited. $post_data['edit_date'] = true; } if ( ! isset( $post_data['ID'] ) ) { $post_data['ID'] = get_default_post_to_edit( $post_data['post_type'], true )->ID; } $post_id = $post_data['ID']; if ( 'post' === $post_data['post_type'] ) { $error = $this->_toggle_sticky( $post_data, $update ); if ( $error ) { return $error; } } if ( isset( $post_data['post_thumbnail'] ) ) { // Empty value deletes, non-empty value adds/updates. if ( ! $post_data['post_thumbnail'] ) { delete_post_thumbnail( $post_id ); } elseif ( ! get_post( absint( $post_data['post_thumbnail'] ) ) ) { return new IXR_Error( 404, __( 'Invalid attachment ID.' ) ); } set_post_thumbnail( $post_id, $post_data['post_thumbnail'] ); unset( $content_struct['post_thumbnail'] ); } if ( isset( $post_data['custom_fields'] ) ) { $this->set_custom_fields( $post_id, $post_data['custom_fields'] ); } if ( isset( $post_data['terms'] ) || isset( $post_data['terms_names'] ) ) { $post_type_taxonomies = get_object_taxonomies( $post_data['post_type'], 'objects' ); // Accumulate term IDs from terms and terms_names. $terms = array(); // First validate the terms specified by ID. if ( isset( $post_data['terms'] ) && is_array( $post_data['terms'] ) ) { $taxonomies = array_keys( $post_data['terms'] ); // Validating term IDs. foreach ( $taxonomies as $taxonomy ) { if ( ! array_key_exists( $taxonomy, $post_type_taxonomies ) ) { return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) ); } if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->assign_terms ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) ); } $term_ids = $post_data['terms'][ $taxonomy ]; $terms[ $taxonomy ] = array(); foreach ( $term_ids as $term_id ) { $term = get_term_by( 'id', $term_id, $taxonomy ); if ( ! $term ) { return new IXR_Error( 403, __( 'Invalid term ID.' ) ); } $terms[ $taxonomy ][] = (int) $term_id; } } } // Now validate terms specified by name. if ( isset( $post_data['terms_names'] ) && is_array( $post_data['terms_names'] ) ) { $taxonomies = array_keys( $post_data['terms_names'] ); foreach ( $taxonomies as $taxonomy ) { if ( ! array_key_exists( $taxonomy, $post_type_taxonomies ) ) { return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) ); } if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->assign_terms ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) ); } /* * For hierarchical taxonomies, we can't assign a term when multiple terms * in the hierarchy share the same name. */ $ambiguous_terms = array(); if ( is_taxonomy_hierarchical( $taxonomy ) ) { $tax_term_names = get_terms( array( 'taxonomy' => $taxonomy, 'fields' => 'names', 'hide_empty' => false, ) ); // Count the number of terms with the same name. $tax_term_names_count = array_count_values( $tax_term_names ); // Filter out non-ambiguous term names. $ambiguous_tax_term_counts = array_filter( $tax_term_names_count, array( $this, '_is_greater_than_one' ) ); $ambiguous_terms = array_keys( $ambiguous_tax_term_counts ); } $term_names = $post_data['terms_names'][ $taxonomy ]; foreach ( $term_names as $term_name ) { if ( in_array( $term_name, $ambiguous_terms, true ) ) { return new IXR_Error( 401, __( 'Ambiguous term name used in a hierarchical taxonomy. Please use term ID instead.' ) ); } $term = get_term_by( 'name', $term_name, $taxonomy ); if ( ! $term ) { // Term doesn't exist, so check that the user is allowed to create new terms. if ( ! current_user_can( $post_type_taxonomies[ $taxonomy ]->cap->edit_terms ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a term to one of the given taxonomies.' ) ); } // Create the new term. $term_info = wp_insert_term( $term_name, $taxonomy ); if ( is_wp_error( $term_info ) ) { return new IXR_Error( 500, $term_info->get_error_message() ); } $terms[ $taxonomy ][] = (int) $term_info['term_id']; } else { $terms[ $taxonomy ][] = (int) $term->term_id; } } } } $post_data['tax_input'] = $terms; unset( $post_data['terms'], $post_data['terms_names'] ); } if ( isset( $post_data['post_format'] ) ) { $format = set_post_format( $post_id, $post_data['post_format'] ); if ( is_wp_error( $format ) ) { return new IXR_Error( 500, $format->get_error_message() ); } unset( $post_data['post_format'] ); } // Handle enclosures. $enclosure = isset( $post_data['enclosure'] ) ? $post_data['enclosure'] : null; $this->add_enclosure_if_new( $post_id, $enclosure ); $this->attach_uploads( $post_id, $post_data['post_content'] ); /** * Filters post data array to be inserted via XML-RPC. * * @since 3.4.0 * * @param array $post_data Parsed array of post data. * @param array $content_struct Post data array. */ $post_data = apply_filters( 'xmlrpc_wp_insert_post_data', $post_data, $content_struct ); // Remove all null values to allow for using the insert/update post default values for those keys instead. $post_data = array_filter( $post_data, static function ( $value ) { return null !== $value; } ); $post_id = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true ); if ( is_wp_error( $post_id ) ) { return new IXR_Error( 500, $post_id->get_error_message() ); } if ( ! $post_id ) { if ( $update ) { return new IXR_Error( 401, __( 'Sorry, the post could not be updated.' ) ); } else { return new IXR_Error( 401, __( 'Sorry, the post could not be created.' ) ); } } return (string) $post_id; } /** * Edits a post for any registered post type. * * The $content_struct parameter only needs to contain fields that * should be changed. All other fields will retain their existing values. * * @since 3.4.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Post ID. * @type array $4 Extra content arguments. * } * @return true|IXR_Error True on success, IXR_Error on failure. */ public function wp_editPost( $args ) { if ( ! $this->minimum_args( $args, 5 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $post_id = (int) $args[3]; $content_struct = $args[4]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.editPost', $args, $this ); $post = get_post( $post_id, ARRAY_A ); if ( empty( $post['ID'] ) ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( isset( $content_struct['if_not_modified_since'] ) ) { // If the post has been modified since the date provided, return an error. if ( mysql2date( 'U', $post['post_modified_gmt'] ) > $content_struct['if_not_modified_since']->getTimestamp() ) { return new IXR_Error( 409, __( 'There is a revision of this post that is more recent.' ) ); } } // Convert the date field back to IXR form. $post['post_date'] = $this->_convert_date( $post['post_date'] ); /* * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct, * since _insert_post() will ignore the non-GMT date if the GMT date is set. */ if ( '0000-00-00 00:00:00' === $post['post_date_gmt'] || isset( $content_struct['post_date'] ) ) { unset( $post['post_date_gmt'] ); } else { $post['post_date_gmt'] = $this->_convert_date( $post['post_date_gmt'] ); } /* * If the API client did not provide 'post_date', then we must not perpetuate the value that * was stored in the database, or it will appear to be an intentional edit. Conveying it here * as if it was coming from the API client will cause an otherwise zeroed out 'post_date_gmt' * to get set with the value that was originally stored in the database when the draft was created. */ if ( ! isset( $content_struct['post_date'] ) ) { unset( $post['post_date'] ); } $this->escape( $post ); $merged_content_struct = array_merge( $post, $content_struct ); $retval = $this->_insert_post( $user, $merged_content_struct ); if ( $retval instanceof IXR_Error ) { return $retval; } return true; } /** * Deletes a post for any registered post type. * * @since 3.4.0 * * @see wp_delete_post() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Post ID. * } * @return true|IXR_Error True on success, IXR_Error instance on failure. */ public function wp_deletePost( $args ) { if ( ! $this->minimum_args( $args, 4 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $post_id = (int) $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.deletePost', $args, $this ); $post = get_post( $post_id, ARRAY_A ); if ( empty( $post['ID'] ) ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'delete_post', $post_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) ); } $result = wp_delete_post( $post_id ); if ( ! $result ) { return new IXR_Error( 500, __( 'Sorry, the post could not be deleted.' ) ); } return true; } /** * Retrieves a post. * * @since 3.4.0 * * The optional $fields parameter specifies what fields will be included * in the response array. This should be a list of field names. 'post_id' will * always be included in the response regardless of the value of $fields. * * Instead of, or in addition to, individual field names, conceptual group * names can be used to specify multiple fields. The available conceptual * groups are 'post' (all basic fields), 'taxonomies', 'custom_fields', * and 'enclosure'. * * @see get_post() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Post ID. * @type array $4 Optional. The subset of post type fields to return. * } * @return array|IXR_Error Array contains (based on $fields parameter): * - 'post_id' * - 'post_title' * - 'post_date' * - 'post_date_gmt' * - 'post_modified' * - 'post_modified_gmt' * - 'post_status' * - 'post_type' * - 'post_name' * - 'post_author' * - 'post_password' * - 'post_excerpt' * - 'post_content' * - 'link' * - 'comment_status' * - 'ping_status' * - 'sticky' * - 'custom_fields' * - 'terms' * - 'categories' * - 'tags' * - 'enclosure' */ public function wp_getPost( $args ) { if ( ! $this->minimum_args( $args, 4 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $post_id = (int) $args[3]; if ( isset( $args[4] ) ) { $fields = $args[4]; } else { /** * Filters the default post query fields used by the given XML-RPC method. * * @since 3.4.0 * * @param array $fields An array of post fields to retrieve. By default, * contains 'post', 'terms', and 'custom_fields'. * @param string $method Method name. */ $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' ); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getPost', $args, $this ); $post = get_post( $post_id, ARRAY_A ); if ( empty( $post['ID'] ) ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'edit_post', $post_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); } return $this->_prepare_post( $post, $fields ); } /** * Retrieves posts. * * @since 3.4.0 * * @see wp_get_recent_posts() * @see wp_getPost() for more on `$fields` * @see get_posts() for more on `$filter` values * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Optional. Modifies the query used to retrieve posts. Accepts 'post_type', * 'post_status', 'number', 'offset', 'orderby', 's', and 'order'. * Default empty array. * @type array $4 Optional. The subset of post type fields to return in the response array. * } * @return array|IXR_Error Array containing a collection of posts. */ public function wp_getPosts( $args ) { if ( ! $this->minimum_args( $args, 3 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $filter = isset( $args[3] ) ? $args[3] : array(); if ( isset( $args[4] ) ) { $fields = $args[4]; } else { /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' ); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getPosts', $args, $this ); $query = array(); if ( isset( $filter['post_type'] ) ) { $post_type = get_post_type_object( $filter['post_type'] ); if ( ! ( (bool) $post_type ) ) { return new IXR_Error( 403, __( 'Invalid post type.' ) ); } } else { $post_type = get_post_type_object( 'post' ); } if ( ! current_user_can( $post_type->cap->edit_posts ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) ); } $query['post_type'] = $post_type->name; if ( isset( $filter['post_status'] ) ) { $query['post_status'] = $filter['post_status']; } if ( isset( $filter['number'] ) ) { $query['numberposts'] = absint( $filter['number'] ); } if ( isset( $filter['offset'] ) ) { $query['offset'] = absint( $filter['offset'] ); } if ( isset( $filter['orderby'] ) ) { $query['orderby'] = $filter['orderby']; if ( isset( $filter['order'] ) ) { $query['order'] = $filter['order']; } } if ( isset( $filter['s'] ) ) { $query['s'] = $filter['s']; } $posts_list = wp_get_recent_posts( $query ); if ( ! $posts_list ) { return array(); } // Holds all the posts data. $struct = array(); foreach ( $posts_list as $post ) { if ( ! current_user_can( 'edit_post', $post['ID'] ) ) { continue; } $struct[] = $this->_prepare_post( $post, $fields ); } return $struct; } /** * Creates a new term. * * @since 3.4.0 * * @see wp_insert_term() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Content struct for adding a new term. The struct must contain * the term 'name' and 'taxonomy'. Optional accepted values include * 'parent', 'description', and 'slug'. * } * @return int|IXR_Error The term ID on success, or an IXR_Error object on failure. */ public function wp_newTerm( $args ) { if ( ! $this->minimum_args( $args, 4 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $content_struct = $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.newTerm', $args, $this ); if ( ! taxonomy_exists( $content_struct['taxonomy'] ) ) { return new IXR_Error( 403, __( 'Invalid taxonomy.' ) ); } $taxonomy = get_taxonomy( $content_struct['taxonomy'] ); if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to create terms in this taxonomy.' ) ); } $taxonomy = (array) $taxonomy; // Hold the data of the term. $term_data = array(); $term_data['name'] = trim( $content_struct['name'] ); if ( empty( $term_data['name'] ) ) { return new IXR_Error( 403, __( 'The term name cannot be empty.' ) ); } if ( isset( $content_struct['parent'] ) ) { if ( ! $taxonomy['hierarchical'] ) { return new IXR_Error( 403, __( 'This taxonomy is not hierarchical.' ) ); } $parent_term_id = (int) $content_struct['parent']; $parent_term = get_term( $parent_term_id, $taxonomy['name'] ); if ( is_wp_error( $parent_term ) ) { return new IXR_Error( 500, $parent_term->get_error_message() ); } if ( ! $parent_term ) { return new IXR_Error( 403, __( 'Parent term does not exist.' ) ); } $term_data['parent'] = $content_struct['parent']; } if ( isset( $content_struct['description'] ) ) { $term_data['description'] = $content_struct['description']; } if ( isset( $content_struct['slug'] ) ) { $term_data['slug'] = $content_struct['slug']; } $term = wp_insert_term( $term_data['name'], $taxonomy['name'], $term_data ); if ( is_wp_error( $term ) ) { return new IXR_Error( 500, $term->get_error_message() ); } if ( ! $term ) { return new IXR_Error( 500, __( 'Sorry, the term could not be created.' ) ); } // Add term meta. if ( isset( $content_struct['custom_fields'] ) ) { $this->set_term_custom_fields( $term['term_id'], $content_struct['custom_fields'] ); } return (string) $term['term_id']; } /** * Edits a term. * * @since 3.4.0 * * @see wp_update_term() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Term ID. * @type array $4 Content struct for editing a term. The struct must contain the * term 'taxonomy'. Optional accepted values include 'name', 'parent', * 'description', and 'slug'. * } * @return true|IXR_Error True on success, IXR_Error instance on failure. */ public function wp_editTerm( $args ) { if ( ! $this->minimum_args( $args, 5 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $term_id = (int) $args[3]; $content_struct = $args[4]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.editTerm', $args, $this ); if ( ! taxonomy_exists( $content_struct['taxonomy'] ) ) { return new IXR_Error( 403, __( 'Invalid taxonomy.' ) ); } $taxonomy = get_taxonomy( $content_struct['taxonomy'] ); $taxonomy = (array) $taxonomy; // Hold the data of the term. $term_data = array(); $term = get_term( $term_id, $content_struct['taxonomy'] ); if ( is_wp_error( $term ) ) { return new IXR_Error( 500, $term->get_error_message() ); } if ( ! $term ) { return new IXR_Error( 404, __( 'Invalid term ID.' ) ); } if ( ! current_user_can( 'edit_term', $term_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this term.' ) ); } if ( isset( $content_struct['name'] ) ) { $term_data['name'] = trim( $content_struct['name'] ); if ( empty( $term_data['name'] ) ) { return new IXR_Error( 403, __( 'The term name cannot be empty.' ) ); } } if ( ! empty( $content_struct['parent'] ) ) { if ( ! $taxonomy['hierarchical'] ) { return new IXR_Error( 403, __( 'Cannot set parent term, taxonomy is not hierarchical.' ) ); } $parent_term_id = (int) $content_struct['parent']; $parent_term = get_term( $parent_term_id, $taxonomy['name'] ); if ( is_wp_error( $parent_term ) ) { return new IXR_Error( 500, $parent_term->get_error_message() ); } if ( ! $parent_term ) { return new IXR_Error( 403, __( 'Parent term does not exist.' ) ); } $term_data['parent'] = $content_struct['parent']; } if ( isset( $content_struct['description'] ) ) { $term_data['description'] = $content_struct['description']; } if ( isset( $content_struct['slug'] ) ) { $term_data['slug'] = $content_struct['slug']; } $term = wp_update_term( $term_id, $taxonomy['name'], $term_data ); if ( is_wp_error( $term ) ) { return new IXR_Error( 500, $term->get_error_message() ); } if ( ! $term ) { return new IXR_Error( 500, __( 'Sorry, editing the term failed.' ) ); } // Update term meta. if ( isset( $content_struct['custom_fields'] ) ) { $this->set_term_custom_fields( $term_id, $content_struct['custom_fields'] ); } return true; } /** * Deletes a term. * * @since 3.4.0 * * @see wp_delete_term() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type string $3 Taxonomy name. * @type int $4 Term ID. * } * @return true|IXR_Error True on success, IXR_Error instance on failure. */ public function wp_deleteTerm( $args ) { if ( ! $this->minimum_args( $args, 5 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $taxonomy = $args[3]; $term_id = (int) $args[4]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.deleteTerm', $args, $this ); if ( ! taxonomy_exists( $taxonomy ) ) { return new IXR_Error( 403, __( 'Invalid taxonomy.' ) ); } $taxonomy = get_taxonomy( $taxonomy ); $term = get_term( $term_id, $taxonomy->name ); if ( is_wp_error( $term ) ) { return new IXR_Error( 500, $term->get_error_message() ); } if ( ! $term ) { return new IXR_Error( 404, __( 'Invalid term ID.' ) ); } if ( ! current_user_can( 'delete_term', $term_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this term.' ) ); } $result = wp_delete_term( $term_id, $taxonomy->name ); if ( is_wp_error( $result ) ) { return new IXR_Error( 500, $result->get_error_message() ); } if ( ! $result ) { return new IXR_Error( 500, __( 'Sorry, deleting the term failed.' ) ); } return $result; } /** * Retrieves a term. * * @since 3.4.0 * * @see get_term() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type string $3 Taxonomy name. * @type int $4 Term ID. * } * @return array|IXR_Error IXR_Error on failure, array on success, containing: * - 'term_id' * - 'name' * - 'slug' * - 'term_group' * - 'term_taxonomy_id' * - 'taxonomy' * - 'description' * - 'parent' * - 'count' */ public function wp_getTerm( $args ) { if ( ! $this->minimum_args( $args, 5 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $taxonomy = $args[3]; $term_id = (int) $args[4]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getTerm', $args, $this ); if ( ! taxonomy_exists( $taxonomy ) ) { return new IXR_Error( 403, __( 'Invalid taxonomy.' ) ); } $taxonomy = get_taxonomy( $taxonomy ); $term = get_term( $term_id, $taxonomy->name, ARRAY_A ); if ( is_wp_error( $term ) ) { return new IXR_Error( 500, $term->get_error_message() ); } if ( ! $term ) { return new IXR_Error( 404, __( 'Invalid term ID.' ) ); } if ( ! current_user_can( 'assign_term', $term_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign this term.' ) ); } return $this->_prepare_term( $term ); } /** * Retrieves all terms for a taxonomy. * * @since 3.4.0 * * The optional $filter parameter modifies the query used to retrieve terms. * Accepted keys are 'number', 'offset', 'orderby', 'order', 'hide_empty', and 'search'. * * @see get_terms() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type string $3 Taxonomy name. * @type array $4 Optional. Modifies the query used to retrieve posts. Accepts 'number', * 'offset', 'orderby', 'order', 'hide_empty', and 'search'. Default empty array. * } * @return array|IXR_Error An associative array of terms data on success, IXR_Error instance otherwise. */ public function wp_getTerms( $args ) { if ( ! $this->minimum_args( $args, 4 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $taxonomy = $args[3]; $filter = isset( $args[4] ) ? $args[4] : array(); $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getTerms', $args, $this ); if ( ! taxonomy_exists( $taxonomy ) ) { return new IXR_Error( 403, __( 'Invalid taxonomy.' ) ); } $taxonomy = get_taxonomy( $taxonomy ); if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) ); } $query = array( 'taxonomy' => $taxonomy->name ); if ( isset( $filter['number'] ) ) { $query['number'] = absint( $filter['number'] ); } if ( isset( $filter['offset'] ) ) { $query['offset'] = absint( $filter['offset'] ); } if ( isset( $filter['orderby'] ) ) { $query['orderby'] = $filter['orderby']; if ( isset( $filter['order'] ) ) { $query['order'] = $filter['order']; } } if ( isset( $filter['hide_empty'] ) ) { $query['hide_empty'] = $filter['hide_empty']; } else { $query['get'] = 'all'; } if ( isset( $filter['search'] ) ) { $query['search'] = $filter['search']; } $terms = get_terms( $query ); if ( is_wp_error( $terms ) ) { return new IXR_Error( 500, $terms->get_error_message() ); } $struct = array(); foreach ( $terms as $term ) { $struct[] = $this->_prepare_term( $term ); } return $struct; } /** * Retrieves a taxonomy. * * @since 3.4.0 * * @see get_taxonomy() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type string $3 Taxonomy name. * @type array $4 Optional. Array of taxonomy fields to limit to in the return. * Accepts 'labels', 'cap', 'menu', and 'object_type'. * Default empty array. * } * @return array|IXR_Error An array of taxonomy data on success, IXR_Error instance otherwise. */ public function wp_getTaxonomy( $args ) { if ( ! $this->minimum_args( $args, 4 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $taxonomy = $args[3]; if ( isset( $args[4] ) ) { $fields = $args[4]; } else { /** * Filters the default taxonomy query fields used by the given XML-RPC method. * * @since 3.4.0 * * @param array $fields An array of taxonomy fields to retrieve. By default, * contains 'labels', 'cap', and 'object_type'. * @param string $method The method name. */ $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomy' ); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getTaxonomy', $args, $this ); if ( ! taxonomy_exists( $taxonomy ) ) { return new IXR_Error( 403, __( 'Invalid taxonomy.' ) ); } $taxonomy = get_taxonomy( $taxonomy ); if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) ); } return $this->_prepare_taxonomy( $taxonomy, $fields ); } /** * Retrieves all taxonomies. * * @since 3.4.0 * * @see get_taxonomies() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Optional. An array of arguments for retrieving taxonomies. * @type array $4 Optional. The subset of taxonomy fields to return. * } * @return array|IXR_Error An associative array of taxonomy data with returned fields determined * by `$fields`, or an IXR_Error instance on failure. */ public function wp_getTaxonomies( $args ) { if ( ! $this->minimum_args( $args, 3 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $filter = isset( $args[3] ) ? $args[3] : array( 'public' => true ); if ( isset( $args[4] ) ) { $fields = $args[4]; } else { /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomies' ); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getTaxonomies', $args, $this ); $taxonomies = get_taxonomies( $filter, 'objects' ); // Holds all the taxonomy data. $struct = array(); foreach ( $taxonomies as $taxonomy ) { // Capability check for post types. if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) { continue; } $struct[] = $this->_prepare_taxonomy( $taxonomy, $fields ); } return $struct; } /** * Retrieves a user. * * The optional $fields parameter specifies what fields will be included * in the response array. This should be a list of field names. 'user_id' will * always be included in the response regardless of the value of $fields. * * Instead of, or in addition to, individual field names, conceptual group * names can be used to specify multiple fields. The available conceptual * groups are 'basic' and 'all'. * * @uses get_userdata() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 User ID. * @type array $4 Optional. Array of fields to return. * } * @return array|IXR_Error Array contains (based on $fields parameter): * - 'user_id' * - 'username' * - 'first_name' * - 'last_name' * - 'registered' * - 'bio' * - 'email' * - 'nickname' * - 'nicename' * - 'url' * - 'display_name' * - 'roles' */ public function wp_getUser( $args ) { if ( ! $this->minimum_args( $args, 4 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user_id = (int) $args[3]; if ( isset( $args[4] ) ) { $fields = $args[4]; } else { /** * Filters the default user query fields used by the given XML-RPC method. * * @since 3.5.0 * * @param array $fields An array of user fields to retrieve. By default, contains 'all'. * @param string $method The method name. */ $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUser' ); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getUser', $args, $this ); if ( ! current_user_can( 'edit_user', $user_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this user.' ) ); } $user_data = get_userdata( $user_id ); if ( ! $user_data ) { return new IXR_Error( 404, __( 'Invalid user ID.' ) ); } return $this->_prepare_user( $user_data, $fields ); } /** * Retrieves users. * * The optional $filter parameter modifies the query used to retrieve users. * Accepted keys are 'number' (default: 50), 'offset' (default: 0), 'role', * 'who', 'orderby', and 'order'. * * The optional $fields parameter specifies what fields will be included * in the response array. * * @uses get_users() * @see wp_getUser() for more on $fields and return values * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Optional. Arguments for the user query. * @type array $4 Optional. Fields to return. * } * @return array|IXR_Error users data */ public function wp_getUsers( $args ) { if ( ! $this->minimum_args( $args, 3 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $filter = isset( $args[3] ) ? $args[3] : array(); if ( isset( $args[4] ) ) { $fields = $args[4]; } else { /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUsers' ); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getUsers', $args, $this ); if ( ! current_user_can( 'list_users' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to list users.' ) ); } $query = array( 'fields' => 'all_with_meta' ); $query['number'] = ( isset( $filter['number'] ) ) ? absint( $filter['number'] ) : 50; $query['offset'] = ( isset( $filter['offset'] ) ) ? absint( $filter['offset'] ) : 0; if ( isset( $filter['orderby'] ) ) { $query['orderby'] = $filter['orderby']; if ( isset( $filter['order'] ) ) { $query['order'] = $filter['order']; } } if ( isset( $filter['role'] ) ) { if ( get_role( $filter['role'] ) === null ) { return new IXR_Error( 403, __( 'Invalid role.' ) ); } $query['role'] = $filter['role']; } if ( isset( $filter['who'] ) ) { $query['who'] = $filter['who']; } $users = get_users( $query ); $_users = array(); foreach ( $users as $user_data ) { if ( current_user_can( 'edit_user', $user_data->ID ) ) { $_users[] = $this->_prepare_user( $user_data, $fields ); } } return $_users; } /** * Retrieves information about the requesting user. * * @uses get_userdata() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username * @type string $2 Password * @type array $3 Optional. Fields to return. * } * @return array|IXR_Error (@see wp_getUser) */ public function wp_getProfile( $args ) { if ( ! $this->minimum_args( $args, 3 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; if ( isset( $args[3] ) ) { $fields = $args[3]; } else { /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getProfile' ); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getProfile', $args, $this ); if ( ! current_user_can( 'edit_user', $user->ID ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) ); } $user_data = get_userdata( $user->ID ); return $this->_prepare_user( $user_data, $fields ); } /** * Edits user's profile. * * @uses wp_update_user() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Content struct. It can optionally contain: * - 'first_name' * - 'last_name' * - 'website' * - 'display_name' * - 'nickname' * - 'nicename' * - 'bio' * } * @return true|IXR_Error True, on success. */ public function wp_editProfile( $args ) { if ( ! $this->minimum_args( $args, 4 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $content_struct = $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.editProfile', $args, $this ); if ( ! current_user_can( 'edit_user', $user->ID ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) ); } // Holds data of the user. $user_data = array(); $user_data['ID'] = $user->ID; // Only set the user details if they were given. if ( isset( $content_struct['first_name'] ) ) { $user_data['first_name'] = $content_struct['first_name']; } if ( isset( $content_struct['last_name'] ) ) { $user_data['last_name'] = $content_struct['last_name']; } if ( isset( $content_struct['url'] ) ) { $user_data['user_url'] = $content_struct['url']; } if ( isset( $content_struct['display_name'] ) ) { $user_data['display_name'] = $content_struct['display_name']; } if ( isset( $content_struct['nickname'] ) ) { $user_data['nickname'] = $content_struct['nickname']; } if ( isset( $content_struct['nicename'] ) ) { $user_data['user_nicename'] = $content_struct['nicename']; } if ( isset( $content_struct['bio'] ) ) { $user_data['description'] = $content_struct['bio']; } $result = wp_update_user( $user_data ); if ( is_wp_error( $result ) ) { return new IXR_Error( 500, $result->get_error_message() ); } if ( ! $result ) { return new IXR_Error( 500, __( 'Sorry, the user could not be updated.' ) ); } return true; } /** * Retrieves a page. * * @since 2.2.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type int $1 Page ID. * @type string $2 Username. * @type string $3 Password. * } * @return array|IXR_Error */ public function wp_getPage( $args ) { $this->escape( $args ); $page_id = (int) $args[1]; $username = $args[2]; $password = $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } $page = get_post( $page_id ); if ( ! $page ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'edit_page', $page_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getPage', $args, $this ); // If we found the page then format the data. if ( $page->ID && ( 'page' === $page->post_type ) ) { return $this->_prepare_page( $page ); } else { // If the page doesn't exist, indicate that. return new IXR_Error( 404, __( 'Sorry, no such page.' ) ); } } /** * Retrieves Pages. * * @since 2.2.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Optional. Number of pages. Default 10. * } * @return array|IXR_Error */ public function wp_getPages( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $num_pages = isset( $args[3] ) ? (int) $args[3] : 10; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_pages' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getPages', $args, $this ); $pages = get_posts( array( 'post_type' => 'page', 'post_status' => 'any', 'numberposts' => $num_pages, ) ); $num_pages = count( $pages ); // If we have pages, put together their info. if ( $num_pages >= 1 ) { $pages_struct = array(); foreach ( $pages as $page ) { if ( current_user_can( 'edit_page', $page->ID ) ) { $pages_struct[] = $this->_prepare_page( $page ); } } return $pages_struct; } return array(); } /** * Creates a new page. * * @since 2.2.0 * * @see wp_xmlrpc_server::mw_newPost() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Content struct. * } * @return int|IXR_Error */ public function wp_newPage( $args ) { // Items not escaped here will be escaped in wp_newPost(). $username = $this->escape( $args[1] ); $password = $this->escape( $args[2] ); $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.newPage', $args, $this ); // Mark this as content for a page. $args[3]['post_type'] = 'page'; // Let mw_newPost() do all of the heavy lifting. return $this->mw_newPost( $args ); } /** * Deletes a page. * * @since 2.2.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Page ID. * } * @return true|IXR_Error True, if success. */ public function wp_deletePage( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $page_id = (int) $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.deletePage', $args, $this ); /* * Get the current page based on the 'page_id' and * make sure it is a page and not a post. */ $actual_page = get_post( $page_id, ARRAY_A ); if ( ! $actual_page || ( 'page' !== $actual_page['post_type'] ) ) { return new IXR_Error( 404, __( 'Sorry, no such page.' ) ); } // Make sure the user can delete pages. if ( ! current_user_can( 'delete_page', $page_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this page.' ) ); } // Attempt to delete the page. $result = wp_delete_post( $page_id ); if ( ! $result ) { return new IXR_Error( 500, __( 'Failed to delete the page.' ) ); } /** * Fires after a page has been successfully deleted via XML-RPC. * * @since 3.4.0 * * @param int $page_id ID of the deleted page. * @param array $args An array of arguments to delete the page. */ do_action( 'xmlrpc_call_success_wp_deletePage', $page_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase return true; } /** * Edits a page. * * @since 2.2.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type int $1 Page ID. * @type string $2 Username. * @type string $3 Password. * @type string $4 Content. * @type int $5 Publish flag. 0 for draft, 1 for publish. * } * @return array|IXR_Error */ public function wp_editPage( $args ) { // Items will be escaped in mw_editPost(). $page_id = (int) $args[1]; $username = $args[2]; $password = $args[3]; $content = $args[4]; $publish = $args[5]; $escaped_username = $this->escape( $username ); $escaped_password = $this->escape( $password ); $user = $this->login( $escaped_username, $escaped_password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.editPage', $args, $this ); // Get the page data and make sure it is a page. $actual_page = get_post( $page_id, ARRAY_A ); if ( ! $actual_page || ( 'page' !== $actual_page['post_type'] ) ) { return new IXR_Error( 404, __( 'Sorry, no such page.' ) ); } // Make sure the user is allowed to edit pages. if ( ! current_user_can( 'edit_page', $page_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) ); } // Mark this as content for a page. $content['post_type'] = 'page'; // Arrange args in the way mw_editPost() understands. $args = array( $page_id, $username, $password, $content, $publish, ); // Let mw_editPost() do all of the heavy lifting. return $this->mw_editPost( $args ); } /** * Retrieves page list. * * @since 2.2.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function wp_getPageList( $args ) { global $wpdb; $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_pages' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getPageList', $args, $this ); // Get list of page IDs and titles. $page_list = $wpdb->get_results( " SELECT ID page_id, post_title page_title, post_parent page_parent_id, post_date_gmt, post_date, post_status FROM {$wpdb->posts} WHERE post_type = 'page' ORDER BY ID " ); // The date needs to be formatted properly. $num_pages = count( $page_list ); for ( $i = 0; $i < $num_pages; $i++ ) { $page_list[ $i ]->dateCreated = $this->_convert_date( $page_list[ $i ]->post_date ); $page_list[ $i ]->date_created_gmt = $this->_convert_date_gmt( $page_list[ $i ]->post_date_gmt, $page_list[ $i ]->post_date ); unset( $page_list[ $i ]->post_date_gmt ); unset( $page_list[ $i ]->post_date ); unset( $page_list[ $i ]->post_status ); } return $page_list; } /** * Retrieves authors list. * * @since 2.2.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function wp_getAuthors( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_posts' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getAuthors', $args, $this ); $authors = array(); foreach ( get_users( array( 'fields' => array( 'ID', 'user_login', 'display_name' ) ) ) as $user ) { $authors[] = array( 'user_id' => $user->ID, 'user_login' => $user->user_login, 'display_name' => $user->display_name, ); } return $authors; } /** * Gets the list of all tags. * * @since 2.7.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function wp_getTags( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_posts' ) ) { return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view tags.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getKeywords', $args, $this ); $tags = array(); $all_tags = get_tags(); if ( $all_tags ) { foreach ( (array) $all_tags as $tag ) { $struct = array(); $struct['tag_id'] = $tag->term_id; $struct['name'] = $tag->name; $struct['count'] = $tag->count; $struct['slug'] = $tag->slug; $struct['html_url'] = esc_html( get_tag_link( $tag->term_id ) ); $struct['rss_url'] = esc_html( get_tag_feed_link( $tag->term_id ) ); $tags[] = $struct; } } return $tags; } /** * Creates a new category. * * @since 2.2.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Category. * } * @return int|IXR_Error Category ID. */ public function wp_newCategory( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $category = $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.newCategory', $args, $this ); // Make sure the user is allowed to add a category. if ( ! current_user_can( 'manage_categories' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a category.' ) ); } /* * If no slug was provided, make it empty * so that WordPress will generate one. */ if ( empty( $category['slug'] ) ) { $category['slug'] = ''; } /* * If no parent_id was provided, make it empty * so that it will be a top-level page (no parent). */ if ( ! isset( $category['parent_id'] ) ) { $category['parent_id'] = ''; } // If no description was provided, make it empty. if ( empty( $category['description'] ) ) { $category['description'] = ''; } $new_category = array( 'cat_name' => $category['name'], 'category_nicename' => $category['slug'], 'category_parent' => $category['parent_id'], 'category_description' => $category['description'], ); $cat_id = wp_insert_category( $new_category, true ); if ( is_wp_error( $cat_id ) ) { if ( 'term_exists' === $cat_id->get_error_code() ) { return (int) $cat_id->get_error_data(); } else { return new IXR_Error( 500, __( 'Sorry, the category could not be created.' ) ); } } elseif ( ! $cat_id ) { return new IXR_Error( 500, __( 'Sorry, the category could not be created.' ) ); } /** * Fires after a new category has been successfully created via XML-RPC. * * @since 3.4.0 * * @param int $cat_id ID of the new category. * @param array $args An array of new category arguments. */ do_action( 'xmlrpc_call_success_wp_newCategory', $cat_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase return $cat_id; } /** * Deletes a category. * * @since 2.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Category ID. * } * @return bool|IXR_Error See wp_delete_term() for return info. */ public function wp_deleteCategory( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $category_id = (int) $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.deleteCategory', $args, $this ); if ( ! current_user_can( 'delete_term', $category_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this category.' ) ); } $status = wp_delete_term( $category_id, 'category' ); if ( true === $status ) { /** * Fires after a category has been successfully deleted via XML-RPC. * * @since 3.4.0 * * @param int $category_id ID of the deleted category. * @param array $args An array of arguments to delete the category. */ do_action( 'xmlrpc_call_success_wp_deleteCategory', $category_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase } return $status; } /** * Retrieves category list. * * @since 2.2.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Category * @type int $4 Max number of results. * } * @return array|IXR_Error */ public function wp_suggestCategories( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $category = $args[3]; $max_results = (int) $args[4]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_posts' ) ) { return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.suggestCategories', $args, $this ); $category_suggestions = array(); $args = array( 'get' => 'all', 'number' => $max_results, 'name__like' => $category, ); foreach ( (array) get_categories( $args ) as $cat ) { $category_suggestions[] = array( 'category_id' => $cat->term_id, 'category_name' => $cat->name, ); } return $category_suggestions; } /** * Retrieves a comment. * * @since 2.7.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Comment ID. * } * @return array|IXR_Error */ public function wp_getComment( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $comment_id = (int) $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getComment', $args, $this ); $comment = get_comment( $comment_id ); if ( ! $comment ) { return new IXR_Error( 404, __( 'Invalid comment ID.' ) ); } if ( ! current_user_can( 'edit_comment', $comment_id ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) ); } return $this->_prepare_comment( $comment ); } /** * Retrieves comments. * * Besides the common blog_id (unused), username, and password arguments, * it takes a filter array as the last argument. * * Accepted 'filter' keys are 'status', 'post_id', 'offset', and 'number'. * * The defaults are as follows: * - 'status' - Default is ''. Filter by status (e.g., 'approve', 'hold') * - 'post_id' - Default is ''. The post where the comment is posted. * Empty string shows all comments. * - 'number' - Default is 10. Total number of media items to retrieve. * - 'offset' - Default is 0. See WP_Query::query() for more. * * @since 2.7.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Optional. Query arguments. * } * @return array|IXR_Error Array containing a collection of comments. * See wp_xmlrpc_server::wp_getComment() for a description * of each item contents. */ public function wp_getComments( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $struct = isset( $args[3] ) ? $args[3] : array(); $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getComments', $args, $this ); if ( isset( $struct['status'] ) ) { $status = $struct['status']; } else { $status = ''; } if ( ! current_user_can( 'moderate_comments' ) && 'approve' !== $status ) { return new IXR_Error( 401, __( 'Invalid comment status.' ) ); } $post_id = ''; if ( isset( $struct['post_id'] ) ) { $post_id = absint( $struct['post_id'] ); } $post_type = ''; if ( isset( $struct['post_type'] ) ) { $post_type_object = get_post_type_object( $struct['post_type'] ); if ( ! $post_type_object || ! post_type_supports( $post_type_object->name, 'comments' ) ) { return new IXR_Error( 404, __( 'Invalid post type.' ) ); } $post_type = $struct['post_type']; } $offset = 0; if ( isset( $struct['offset'] ) ) { $offset = absint( $struct['offset'] ); } $number = 10; if ( isset( $struct['number'] ) ) { $number = absint( $struct['number'] ); } $comments = get_comments( array( 'status' => $status, 'post_id' => $post_id, 'offset' => $offset, 'number' => $number, 'post_type' => $post_type, ) ); $comments_struct = array(); if ( is_array( $comments ) ) { foreach ( $comments as $comment ) { $comments_struct[] = $this->_prepare_comment( $comment ); } } return $comments_struct; } /** * Deletes a comment. * * By default, the comment will be moved to the Trash instead of deleted. * See wp_delete_comment() for more information on this behavior. * * @since 2.7.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Comment ID. * } * @return bool|IXR_Error See wp_delete_comment(). */ public function wp_deleteComment( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $comment_id = (int) $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! get_comment( $comment_id ) ) { return new IXR_Error( 404, __( 'Invalid comment ID.' ) ); } if ( ! current_user_can( 'edit_comment', $comment_id ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to delete this comment.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.deleteComment', $args, $this ); $status = wp_delete_comment( $comment_id ); if ( true === $status ) { /** * Fires after a comment has been successfully deleted via XML-RPC. * * @since 3.4.0 * * @param int $comment_id ID of the deleted comment. * @param array $args An array of arguments to delete the comment. */ do_action( 'xmlrpc_call_success_wp_deleteComment', $comment_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase } return $status; } /** * Edits a comment. * * Besides the common blog_id (unused), username, and password arguments, * it takes a comment_id integer and a content_struct array as the last argument. * * The allowed keys in the content_struct array are: * - 'author' * - 'author_url' * - 'author_email' * - 'content' * - 'date_created_gmt' * - 'status'. Common statuses are 'approve', 'hold', 'spam'. See get_comment_statuses() for more details. * * @since 2.7.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Comment ID. * @type array $4 Content structure. * } * @return true|IXR_Error True, on success. */ public function wp_editComment( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $comment_id = (int) $args[3]; $content_struct = $args[4]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! get_comment( $comment_id ) ) { return new IXR_Error( 404, __( 'Invalid comment ID.' ) ); } if ( ! current_user_can( 'edit_comment', $comment_id ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.editComment', $args, $this ); $comment = array( 'comment_ID' => $comment_id, ); if ( isset( $content_struct['status'] ) ) { $statuses = get_comment_statuses(); $statuses = array_keys( $statuses ); if ( ! in_array( $content_struct['status'], $statuses, true ) ) { return new IXR_Error( 401, __( 'Invalid comment status.' ) ); } $comment['comment_approved'] = $content_struct['status']; } // Do some timestamp voodoo. if ( ! empty( $content_struct['date_created_gmt'] ) ) { // We know this is supposed to be GMT, so we're going to slap that Z on there by force. $date_created = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z'; $comment['comment_date'] = get_date_from_gmt( $date_created ); $comment['comment_date_gmt'] = iso8601_to_datetime( $date_created, 'gmt' ); } if ( isset( $content_struct['content'] ) ) { $comment['comment_content'] = $content_struct['content']; } if ( isset( $content_struct['author'] ) ) { $comment['comment_author'] = $content_struct['author']; } if ( isset( $content_struct['author_url'] ) ) { $comment['comment_author_url'] = $content_struct['author_url']; } if ( isset( $content_struct['author_email'] ) ) { $comment['comment_author_email'] = $content_struct['author_email']; } $result = wp_update_comment( $comment, true ); if ( is_wp_error( $result ) ) { return new IXR_Error( 500, $result->get_error_message() ); } if ( ! $result ) { return new IXR_Error( 500, __( 'Sorry, the comment could not be updated.' ) ); } /** * Fires after a comment has been successfully updated via XML-RPC. * * @since 3.4.0 * * @param int $comment_id ID of the updated comment. * @param array $args An array of arguments to update the comment. */ do_action( 'xmlrpc_call_success_wp_editComment', $comment_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase return true; } /** * Creates a new comment. * * @since 2.7.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type string|int $3 Post ID or URL. * @type array $4 Content structure. * } * @return int|IXR_Error See wp_new_comment(). */ public function wp_newComment( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $post = $args[3]; $content_struct = $args[4]; /** * Filters whether to allow anonymous comments over XML-RPC. * * @since 2.7.0 * * @param bool $allow Whether to allow anonymous commenting via XML-RPC. * Default false. */ $allow_anon = apply_filters( 'xmlrpc_allow_anonymous_comments', false ); $user = $this->login( $username, $password ); if ( ! $user ) { $logged_in = false; if ( $allow_anon && get_option( 'comment_registration' ) ) { return new IXR_Error( 403, __( 'Sorry, you must be logged in to comment.' ) ); } elseif ( ! $allow_anon ) { return $this->error; } } else { $logged_in = true; } if ( is_numeric( $post ) ) { $post_id = absint( $post ); } else { $post_id = url_to_postid( $post ); } if ( ! $post_id ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! get_post( $post_id ) ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! comments_open( $post_id ) ) { return new IXR_Error( 403, __( 'Sorry, comments are closed for this item.' ) ); } if ( 'publish' === get_post_status( $post_id ) && ! current_user_can( 'edit_post', $post_id ) && post_password_required( $post_id ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) ); } if ( 'private' === get_post_status( $post_id ) && ! current_user_can( 'read_post', $post_id ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) ); } $comment = array( 'comment_post_ID' => $post_id, 'comment_content' => trim( $content_struct['content'] ), ); if ( $logged_in ) { $display_name = $user->display_name; $user_email = $user->user_email; $user_url = $user->user_url; $comment['comment_author'] = $this->escape( $display_name ); $comment['comment_author_email'] = $this->escape( $user_email ); $comment['comment_author_url'] = $this->escape( $user_url ); $comment['user_id'] = $user->ID; } else { $comment['comment_author'] = ''; if ( isset( $content_struct['author'] ) ) { $comment['comment_author'] = $content_struct['author']; } $comment['comment_author_email'] = ''; if ( isset( $content_struct['author_email'] ) ) { $comment['comment_author_email'] = $content_struct['author_email']; } $comment['comment_author_url'] = ''; if ( isset( $content_struct['author_url'] ) ) { $comment['comment_author_url'] = $content_struct['author_url']; } $comment['user_id'] = 0; if ( get_option( 'require_name_email' ) ) { if ( strlen( $comment['comment_author_email'] ) < 6 || '' === $comment['comment_author'] ) { return new IXR_Error( 403, __( 'Comment author name and email are required.' ) ); } elseif ( ! is_email( $comment['comment_author_email'] ) ) { return new IXR_Error( 403, __( 'A valid email address is required.' ) ); } } } $comment['comment_parent'] = isset( $content_struct['comment_parent'] ) ? absint( $content_struct['comment_parent'] ) : 0; /** This filter is documented in wp-includes/comment.php */ $allow_empty = apply_filters( 'allow_empty_comment', false, $comment ); if ( ! $allow_empty && '' === $comment['comment_content'] ) { return new IXR_Error( 403, __( 'Comment is required.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.newComment', $args, $this ); $comment_id = wp_new_comment( $comment, true ); if ( is_wp_error( $comment_id ) ) { return new IXR_Error( 403, $comment_id->get_error_message() ); } if ( ! $comment_id ) { return new IXR_Error( 403, __( 'An error occurred while processing your comment. Please ensure all fields are filled correctly and try again.' ) ); } /** * Fires after a new comment has been successfully created via XML-RPC. * * @since 3.4.0 * * @param int $comment_id ID of the new comment. * @param array $args An array of new comment arguments. */ do_action( 'xmlrpc_call_success_wp_newComment', $comment_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase return $comment_id; } /** * Retrieves all of the comment status. * * @since 2.7.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function wp_getCommentStatusList( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'publish_posts' ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getCommentStatusList', $args, $this ); return get_comment_statuses(); } /** * Retrieves comment counts. * * @since 2.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Post ID. * } * @return array|IXR_Error */ public function wp_getCommentCount( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $post_id = (int) $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } $post = get_post( $post_id, ARRAY_A ); if ( empty( $post['ID'] ) ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'edit_post', $post_id ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details of this post.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getCommentCount', $args, $this ); $count = wp_count_comments( $post_id ); return array( 'approved' => $count->approved, 'awaiting_moderation' => $count->moderated, 'spam' => $count->spam, 'total_comments' => $count->total_comments, ); } /** * Retrieves post statuses. * * @since 2.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function wp_getPostStatusList( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_posts' ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getPostStatusList', $args, $this ); return get_post_statuses(); } /** * Retrieves page statuses. * * @since 2.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function wp_getPageStatusList( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_pages' ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getPageStatusList', $args, $this ); return get_page_statuses(); } /** * Retrieves page templates. * * @since 2.6.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function wp_getPageTemplates( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_pages' ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) ); } $templates = get_page_templates(); $templates['Default'] = 'default'; return $templates; } /** * Retrieves blog options. * * @since 2.6.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Optional. Options. * } * @return array|IXR_Error */ public function wp_getOptions( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $options = isset( $args[3] ) ? (array) $args[3] : array(); $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } // If no specific options where asked for, return all of them. if ( count( $options ) === 0 ) { $options = array_keys( $this->blog_options ); } return $this->_getOptions( $options ); } /** * Retrieves blog options value from list. * * @since 2.6.0 * * @param array $options Options to retrieve. * @return array */ public function _getOptions( $options ) { $data = array(); $can_manage = current_user_can( 'manage_options' ); foreach ( $options as $option ) { if ( array_key_exists( $option, $this->blog_options ) ) { $data[ $option ] = $this->blog_options[ $option ]; // Is the value static or dynamic? if ( isset( $data[ $option ]['option'] ) ) { $data[ $option ]['value'] = get_option( $data[ $option ]['option'] ); unset( $data[ $option ]['option'] ); } if ( ! $can_manage ) { $data[ $option ]['readonly'] = true; } } } return $data; } /** * Updates blog options. * * @since 2.6.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Options. * } * @return array|IXR_Error */ public function wp_setOptions( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $options = (array) $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'manage_options' ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to update options.' ) ); } $option_names = array(); foreach ( $options as $o_name => $o_value ) { $option_names[] = $o_name; if ( ! array_key_exists( $o_name, $this->blog_options ) ) { continue; } if ( $this->blog_options[ $o_name ]['readonly'] ) { continue; } update_option( $this->blog_options[ $o_name ]['option'], wp_unslash( $o_value ) ); } // Now return the updated values. return $this->_getOptions( $option_names ); } /** * Retrieves a media item by ID. * * @since 3.1.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Attachment ID. * } * @return array|IXR_Error Associative array contains: * - 'date_created_gmt' * - 'parent' * - 'link' * - 'thumbnail' * - 'title' * - 'caption' * - 'description' * - 'metadata' */ public function wp_getMediaItem( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $attachment_id = (int) $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'upload_files' ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to upload files.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getMediaItem', $args, $this ); $attachment = get_post( $attachment_id ); if ( ! $attachment || 'attachment' !== $attachment->post_type ) { return new IXR_Error( 404, __( 'Invalid attachment ID.' ) ); } return $this->_prepare_media_item( $attachment ); } /** * Retrieves a collection of media library items (or attachments). * * Besides the common blog_id (unused), username, and password arguments, * it takes a filter array as the last argument. * * Accepted 'filter' keys are 'parent_id', 'mime_type', 'offset', and 'number'. * * The defaults are as follows: * - 'number' - Default is 5. Total number of media items to retrieve. * - 'offset' - Default is 0. See WP_Query::query() for more. * - 'parent_id' - Default is ''. The post where the media item is attached. * Empty string shows all media items. 0 shows unattached media items. * - 'mime_type' - Default is ''. Filter by mime type (e.g., 'image/jpeg', 'application/pdf') * * @since 3.1.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Optional. Query arguments. * } * @return array|IXR_Error Array containing a collection of media items. * See wp_xmlrpc_server::wp_getMediaItem() for a description * of each item contents. */ public function wp_getMediaLibrary( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $struct = isset( $args[3] ) ? $args[3] : array(); $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'upload_files' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getMediaLibrary', $args, $this ); $parent_id = ( isset( $struct['parent_id'] ) ) ? absint( $struct['parent_id'] ) : ''; $mime_type = ( isset( $struct['mime_type'] ) ) ? $struct['mime_type'] : ''; $offset = ( isset( $struct['offset'] ) ) ? absint( $struct['offset'] ) : 0; $number = ( isset( $struct['number'] ) ) ? absint( $struct['number'] ) : -1; $attachments = get_posts( array( 'post_type' => 'attachment', 'post_parent' => $parent_id, 'offset' => $offset, 'numberposts' => $number, 'post_mime_type' => $mime_type, ) ); $attachments_struct = array(); foreach ( $attachments as $attachment ) { $attachments_struct[] = $this->_prepare_media_item( $attachment ); } return $attachments_struct; } /** * Retrieves a list of post formats used by the site. * * @since 3.1.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error List of post formats, otherwise IXR_Error object. */ public function wp_getPostFormats( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_posts' ) ) { return new IXR_Error( 403, __( 'Sorry, you are not allowed to access details about this site.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getPostFormats', $args, $this ); $formats = get_post_format_strings(); // Find out if they want a list of currently supports formats. if ( isset( $args[3] ) && is_array( $args[3] ) ) { if ( $args[3]['show-supported'] ) { if ( current_theme_supports( 'post-formats' ) ) { $supported = get_theme_support( 'post-formats' ); $data = array(); $data['all'] = $formats; $data['supported'] = $supported[0]; $formats = $data; } } } return $formats; } /** * Retrieves a post type. * * @since 3.4.0 * * @see get_post_type_object() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type string $3 Post type name. * @type array $4 Optional. Fields to fetch. * } * @return array|IXR_Error Array contains: * - 'labels' * - 'description' * - 'capability_type' * - 'cap' * - 'map_meta_cap' * - 'hierarchical' * - 'menu_position' * - 'taxonomies' * - 'supports' */ public function wp_getPostType( $args ) { if ( ! $this->minimum_args( $args, 4 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $post_type_name = $args[3]; if ( isset( $args[4] ) ) { $fields = $args[4]; } else { /** * Filters the default post type query fields used by the given XML-RPC method. * * @since 3.4.0 * * @param array $fields An array of post type fields to retrieve. By default, * contains 'labels', 'cap', and 'taxonomies'. * @param string $method The method name. */ $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostType' ); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getPostType', $args, $this ); if ( ! post_type_exists( $post_type_name ) ) { return new IXR_Error( 403, __( 'Invalid post type.' ) ); } $post_type = get_post_type_object( $post_type_name ); if ( ! current_user_can( $post_type->cap->edit_posts ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) ); } return $this->_prepare_post_type( $post_type, $fields ); } /** * Retrieves post types. * * @since 3.4.0 * * @see get_post_types() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Optional. Query arguments. * @type array $4 Optional. Fields to fetch. * } * @return array|IXR_Error */ public function wp_getPostTypes( $args ) { if ( ! $this->minimum_args( $args, 3 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $filter = isset( $args[3] ) ? $args[3] : array( 'public' => true ); if ( isset( $args[4] ) ) { $fields = $args[4]; } else { /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostTypes' ); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getPostTypes', $args, $this ); $post_types = get_post_types( $filter, 'objects' ); $struct = array(); foreach ( $post_types as $post_type ) { if ( ! current_user_can( $post_type->cap->edit_posts ) ) { continue; } $struct[ $post_type->name ] = $this->_prepare_post_type( $post_type, $fields ); } return $struct; } /** * Retrieves revisions for a specific post. * * @since 3.5.0 * * The optional $fields parameter specifies what fields will be included * in the response array. * * @uses wp_get_post_revisions() * @see wp_getPost() for more on $fields * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Post ID. * @type array $4 Optional. Fields to fetch. * } * @return array|IXR_Error Array containing a collection of posts. */ public function wp_getRevisions( $args ) { if ( ! $this->minimum_args( $args, 4 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $post_id = (int) $args[3]; if ( isset( $args[4] ) ) { $fields = $args[4]; } else { /** * Filters the default revision query fields used by the given XML-RPC method. * * @since 3.5.0 * * @param array $field An array of revision fields to retrieve. By default, * contains 'post_date' and 'post_date_gmt'. * @param string $method The method name. */ $fields = apply_filters( 'xmlrpc_default_revision_fields', array( 'post_date', 'post_date_gmt' ), 'wp.getRevisions' ); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getRevisions', $args, $this ); $post = get_post( $post_id ); if ( ! $post ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'edit_post', $post_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) ); } // Check if revisions are enabled. if ( ! wp_revisions_enabled( $post ) ) { return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) ); } $revisions = wp_get_post_revisions( $post_id ); if ( ! $revisions ) { return array(); } $struct = array(); foreach ( $revisions as $revision ) { if ( ! current_user_can( 'read_post', $revision->ID ) ) { continue; } // Skip autosaves. if ( wp_is_post_autosave( $revision ) ) { continue; } $struct[] = $this->_prepare_post( get_object_vars( $revision ), $fields ); } return $struct; } /** * Restores a post revision. * * @since 3.5.0 * * @uses wp_restore_post_revision() * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Revision ID. * } * @return bool|IXR_Error false if there was an error restoring, true if success. */ public function wp_restoreRevision( $args ) { if ( ! $this->minimum_args( $args, 3 ) ) { return $this->error; } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $revision_id = (int) $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.restoreRevision', $args, $this ); $revision = wp_get_post_revision( $revision_id ); if ( ! $revision ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( wp_is_post_autosave( $revision ) ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } $post = get_post( $revision->post_parent ); if ( ! $post ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'edit_post', $revision->post_parent ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); } // Check if revisions are disabled. if ( ! wp_revisions_enabled( $post ) ) { return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) ); } $post = wp_restore_post_revision( $revision_id ); return (bool) $post; } /* * Blogger API functions. * Specs on http://plant.blogger.com/api and https://groups.yahoo.com/group/bloggerDev/ */ /** * Retrieves blogs that user owns. * * Will make more sense once we support multiple blogs. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function blogger_getUsersBlogs( $args ) { if ( ! $this->minimum_args( $args, 3 ) ) { return $this->error; } if ( is_multisite() ) { return $this->_multisite_getUsersBlogs( $args ); } $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'blogger.getUsersBlogs', $args, $this ); $is_admin = current_user_can( 'manage_options' ); $struct = array( 'isAdmin' => $is_admin, 'url' => get_option( 'home' ) . '/', 'blogid' => '1', 'blogName' => get_option( 'blogname' ), 'xmlrpc' => site_url( 'xmlrpc.php', 'rpc' ), ); return array( $struct ); } /** * Private function for retrieving a users blogs for multisite setups. * * @since 3.0.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ protected function _multisite_getUsersBlogs( $args ) { $current_blog = get_site(); $domain = $current_blog->domain; $path = $current_blog->path . 'xmlrpc.php'; $blogs = $this->wp_getUsersBlogs( $args ); if ( $blogs instanceof IXR_Error ) { return $blogs; } if ( $_SERVER['HTTP_HOST'] === $domain && $_SERVER['REQUEST_URI'] === $path ) { return $blogs; } else { foreach ( (array) $blogs as $blog ) { if ( str_contains( $blog['url'], $_SERVER['HTTP_HOST'] ) ) { return array( $blog ); } } return array(); } } /** * Retrieves user's data. * * Gives your client some info about you, so you don't have to. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function blogger_getUserInfo( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_posts' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to access user data on this site.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'blogger.getUserInfo', $args, $this ); $struct = array( 'nickname' => $user->nickname, 'userid' => $user->ID, 'url' => $user->user_url, 'lastname' => $user->last_name, 'firstname' => $user->first_name, ); return $struct; } /** * Retrieves a post. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type int $1 Post ID. * @type string $2 Username. * @type string $3 Password. * } * @return array|IXR_Error */ public function blogger_getPost( $args ) { $this->escape( $args ); $post_id = (int) $args[1]; $username = $args[2]; $password = $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } $post_data = get_post( $post_id, ARRAY_A ); if ( ! $post_data ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'edit_post', $post_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'blogger.getPost', $args, $this ); $categories = implode( ',', wp_get_post_categories( $post_id ) ); $content = '<title>' . wp_unslash( $post_data['post_title'] ) . '</title>'; $content .= '<category>' . $categories . '</category>'; $content .= wp_unslash( $post_data['post_content'] ); $struct = array( 'userid' => $post_data['post_author'], 'dateCreated' => $this->_convert_date( $post_data['post_date'] ), 'content' => $content, 'postid' => (string) $post_data['ID'], ); return $struct; } /** * Retrieves the list of recent posts. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type string $0 App key (unused). * @type int $1 Blog ID (unused). * @type string $2 Username. * @type string $3 Password. * @type int $4 Optional. Number of posts. * } * @return array|IXR_Error */ public function blogger_getRecentPosts( $args ) { $this->escape( $args ); // $args[0] = appkey - ignored. $username = $args[2]; $password = $args[3]; if ( isset( $args[4] ) ) { $query = array( 'numberposts' => absint( $args[4] ) ); } else { $query = array(); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_posts' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'blogger.getRecentPosts', $args, $this ); $posts_list = wp_get_recent_posts( $query ); if ( ! $posts_list ) { $this->error = new IXR_Error( 500, __( 'No posts found or an error occurred while retrieving posts.' ) ); return $this->error; } $recent_posts = array(); foreach ( $posts_list as $entry ) { if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) { continue; } $post_date = $this->_convert_date( $entry['post_date'] ); $categories = implode( ',', wp_get_post_categories( $entry['ID'] ) ); $content = '<title>' . wp_unslash( $entry['post_title'] ) . '</title>'; $content .= '<category>' . $categories . '</category>'; $content .= wp_unslash( $entry['post_content'] ); $recent_posts[] = array( 'userid' => $entry['post_author'], 'dateCreated' => $post_date, 'content' => $content, 'postid' => (string) $entry['ID'], ); } return $recent_posts; } /** * Deprecated. * * @since 1.5.0 * @deprecated 3.5.0 * * @param array $args Unused. * @return IXR_Error Error object. */ public function blogger_getTemplate( $args ) { return new IXR_Error( 403, __( 'Sorry, this method is not supported.' ) ); } /** * Deprecated. * * @since 1.5.0 * @deprecated 3.5.0 * * @param array $args Unused. * @return IXR_Error Error object. */ public function blogger_setTemplate( $args ) { return new IXR_Error( 403, __( 'Sorry, this method is not supported.' ) ); } /** * Creates a new post. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type string $0 App key (unused). * @type int $1 Blog ID (unused). * @type string $2 Username. * @type string $3 Password. * @type string $4 Content. * @type int $5 Publish flag. 0 for draft, 1 for publish. * } * @return int|IXR_Error */ public function blogger_newPost( $args ) { $this->escape( $args ); $username = $args[2]; $password = $args[3]; $content = $args[4]; $publish = $args[5]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'blogger.newPost', $args, $this ); $cap = ( $publish ) ? 'publish_posts' : 'edit_posts'; if ( ! current_user_can( get_post_type_object( 'post' )->cap->create_posts ) || ! current_user_can( $cap ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) ); } $post_status = ( $publish ) ? 'publish' : 'draft'; $post_author = $user->ID; $post_title = xmlrpc_getposttitle( $content ); $post_category = xmlrpc_getpostcategory( $content ); $post_content = xmlrpc_removepostdata( $content ); $post_date = current_time( 'mysql' ); $post_date_gmt = current_time( 'mysql', true ); $post_data = compact( 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status' ); $post_id = wp_insert_post( $post_data ); if ( is_wp_error( $post_id ) ) { return new IXR_Error( 500, $post_id->get_error_message() ); } if ( ! $post_id ) { return new IXR_Error( 500, __( 'Sorry, the post could not be created.' ) ); } $this->attach_uploads( $post_id, $post_content ); /** * Fires after a new post has been successfully created via the XML-RPC Blogger API. * * @since 3.4.0 * * @param int $post_id ID of the new post. * @param array $args An array of new post arguments. */ do_action( 'xmlrpc_call_success_blogger_newPost', $post_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase return $post_id; } /** * Edits a post. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type int $1 Post ID. * @type string $2 Username. * @type string $3 Password. * @type string $4 Content * @type int $5 Publish flag. 0 for draft, 1 for publish. * } * @return true|IXR_Error true when done. */ public function blogger_editPost( $args ) { $this->escape( $args ); $post_id = (int) $args[1]; $username = $args[2]; $password = $args[3]; $content = $args[4]; $publish = $args[5]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'blogger.editPost', $args, $this ); $actual_post = get_post( $post_id, ARRAY_A ); if ( ! $actual_post || 'post' !== $actual_post['post_type'] ) { return new IXR_Error( 404, __( 'Sorry, no such post.' ) ); } $this->escape( $actual_post ); if ( ! current_user_can( 'edit_post', $post_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); } if ( 'publish' === $actual_post['post_status'] && ! current_user_can( 'publish_posts' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) ); } $postdata = array(); $postdata['ID'] = $actual_post['ID']; $postdata['post_content'] = xmlrpc_removepostdata( $content ); $postdata['post_title'] = xmlrpc_getposttitle( $content ); $postdata['post_category'] = xmlrpc_getpostcategory( $content ); $postdata['post_status'] = $actual_post['post_status']; $postdata['post_excerpt'] = $actual_post['post_excerpt']; $postdata['post_status'] = $publish ? 'publish' : 'draft'; $result = wp_update_post( $postdata ); if ( ! $result ) { return new IXR_Error( 500, __( 'Sorry, the post could not be updated.' ) ); } $this->attach_uploads( $actual_post['ID'], $postdata['post_content'] ); /** * Fires after a post has been successfully updated via the XML-RPC Blogger API. * * @since 3.4.0 * * @param int $post_id ID of the updated post. * @param array $args An array of arguments for the post to edit. */ do_action( 'xmlrpc_call_success_blogger_editPost', $post_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase return true; } /** * Deletes a post. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type int $1 Post ID. * @type string $2 Username. * @type string $3 Password. * } * @return true|IXR_Error True when post is deleted. */ public function blogger_deletePost( $args ) { $this->escape( $args ); $post_id = (int) $args[1]; $username = $args[2]; $password = $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'blogger.deletePost', $args, $this ); $actual_post = get_post( $post_id, ARRAY_A ); if ( ! $actual_post || 'post' !== $actual_post['post_type'] ) { return new IXR_Error( 404, __( 'Sorry, no such post.' ) ); } if ( ! current_user_can( 'delete_post', $post_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) ); } $result = wp_delete_post( $post_id ); if ( ! $result ) { return new IXR_Error( 500, __( 'Sorry, the post could not be deleted.' ) ); } /** * Fires after a post has been successfully deleted via the XML-RPC Blogger API. * * @since 3.4.0 * * @param int $post_id ID of the deleted post. * @param array $args An array of arguments to delete the post. */ do_action( 'xmlrpc_call_success_blogger_deletePost', $post_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase return true; } /* * MetaWeblog API functions. * Specs on wherever Dave Winer wants them to be. */ /** * Creates a new post. * * The 'content_struct' argument must contain: * - title * - description * - mt_excerpt * - mt_text_more * - mt_keywords * - mt_tb_ping_urls * - categories * * Also, it can optionally contain: * - wp_slug * - wp_password * - wp_page_parent_id * - wp_page_order * - wp_author_id * - post_status | page_status - can be 'draft', 'private', 'publish', or 'pending' * - mt_allow_comments - can be 'open' or 'closed' * - mt_allow_pings - can be 'open' or 'closed' * - date_created_gmt * - dateCreated * - wp_post_thumbnail * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Content structure. * @type int $4 Optional. Publish flag. 0 for draft, 1 for publish. Default 0. * } * @return int|IXR_Error */ public function mw_newPost( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $content_struct = $args[3]; $publish = isset( $args[4] ) ? $args[4] : 0; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'metaWeblog.newPost', $args, $this ); $page_template = ''; if ( ! empty( $content_struct['post_type'] ) ) { if ( 'page' === $content_struct['post_type'] ) { if ( $publish ) { $cap = 'publish_pages'; } elseif ( isset( $content_struct['page_status'] ) && 'publish' === $content_struct['page_status'] ) { $cap = 'publish_pages'; } else { $cap = 'edit_pages'; } $error_message = __( 'Sorry, you are not allowed to publish pages on this site.' ); $post_type = 'page'; if ( ! empty( $content_struct['wp_page_template'] ) ) { $page_template = $content_struct['wp_page_template']; } } elseif ( 'post' === $content_struct['post_type'] ) { if ( $publish ) { $cap = 'publish_posts'; } elseif ( isset( $content_struct['post_status'] ) && 'publish' === $content_struct['post_status'] ) { $cap = 'publish_posts'; } else { $cap = 'edit_posts'; } $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' ); $post_type = 'post'; } else { // No other 'post_type' values are allowed here. return new IXR_Error( 401, __( 'Invalid post type.' ) ); } } else { if ( $publish ) { $cap = 'publish_posts'; } elseif ( isset( $content_struct['post_status'] ) && 'publish' === $content_struct['post_status'] ) { $cap = 'publish_posts'; } else { $cap = 'edit_posts'; } $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' ); $post_type = 'post'; } if ( ! current_user_can( get_post_type_object( $post_type )->cap->create_posts ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts on this site.' ) ); } if ( ! current_user_can( $cap ) ) { return new IXR_Error( 401, $error_message ); } // Check for a valid post format if one was given. if ( isset( $content_struct['wp_post_format'] ) ) { $content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] ); if ( ! array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) { return new IXR_Error( 404, __( 'Invalid post format.' ) ); } } // Let WordPress generate the 'post_name' (slug) unless // one has been provided. $post_name = null; if ( isset( $content_struct['wp_slug'] ) ) { $post_name = $content_struct['wp_slug']; } // Only use a password if one was given. $post_password = ''; if ( isset( $content_struct['wp_password'] ) ) { $post_password = $content_struct['wp_password']; } // Only set a post parent if one was given. $post_parent = 0; if ( isset( $content_struct['wp_page_parent_id'] ) ) { $post_parent = $content_struct['wp_page_parent_id']; } // Only set the 'menu_order' if it was given. $menu_order = 0; if ( isset( $content_struct['wp_page_order'] ) ) { $menu_order = $content_struct['wp_page_order']; } $post_author = $user->ID; // If an author ID was provided then use it instead. if ( isset( $content_struct['wp_author_id'] ) && ( $user->ID !== (int) $content_struct['wp_author_id'] ) ) { switch ( $post_type ) { case 'post': if ( ! current_user_can( 'edit_others_posts' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) ); } break; case 'page': if ( ! current_user_can( 'edit_others_pages' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to create pages as this user.' ) ); } break; default: return new IXR_Error( 401, __( 'Invalid post type.' ) ); } $author = get_userdata( $content_struct['wp_author_id'] ); if ( ! $author ) { return new IXR_Error( 404, __( 'Invalid author ID.' ) ); } $post_author = $content_struct['wp_author_id']; } $post_title = isset( $content_struct['title'] ) ? $content_struct['title'] : ''; $post_content = isset( $content_struct['description'] ) ? $content_struct['description'] : ''; $post_status = $publish ? 'publish' : 'draft'; if ( isset( $content_struct[ "{$post_type}_status" ] ) ) { switch ( $content_struct[ "{$post_type}_status" ] ) { case 'draft': case 'pending': case 'private': case 'publish': $post_status = $content_struct[ "{$post_type}_status" ]; break; default: // Deliberably left empty. break; } } $post_excerpt = isset( $content_struct['mt_excerpt'] ) ? $content_struct['mt_excerpt'] : ''; $post_more = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : ''; $tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : array(); if ( isset( $content_struct['mt_allow_comments'] ) ) { if ( ! is_numeric( $content_struct['mt_allow_comments'] ) ) { switch ( $content_struct['mt_allow_comments'] ) { case 'closed': $comment_status = 'closed'; break; case 'open': $comment_status = 'open'; break; default: $comment_status = get_default_comment_status( $post_type ); break; } } else { switch ( (int) $content_struct['mt_allow_comments'] ) { case 0: case 2: $comment_status = 'closed'; break; case 1: $comment_status = 'open'; break; default: $comment_status = get_default_comment_status( $post_type ); break; } } } else { $comment_status = get_default_comment_status( $post_type ); } if ( isset( $content_struct['mt_allow_pings'] ) ) { if ( ! is_numeric( $content_struct['mt_allow_pings'] ) ) { switch ( $content_struct['mt_allow_pings'] ) { case 'closed': $ping_status = 'closed'; break; case 'open': $ping_status = 'open'; break; default: $ping_status = get_default_comment_status( $post_type, 'pingback' ); break; } } else { switch ( (int) $content_struct['mt_allow_pings'] ) { case 0: $ping_status = 'closed'; break; case 1: $ping_status = 'open'; break; default: $ping_status = get_default_comment_status( $post_type, 'pingback' ); break; } } } else { $ping_status = get_default_comment_status( $post_type, 'pingback' ); } if ( $post_more ) { $post_content .= '<!--more-->' . $post_more; } $to_ping = ''; if ( isset( $content_struct['mt_tb_ping_urls'] ) ) { $to_ping = $content_struct['mt_tb_ping_urls']; if ( is_array( $to_ping ) ) { $to_ping = implode( ' ', $to_ping ); } } // Do some timestamp voodoo. if ( ! empty( $content_struct['date_created_gmt'] ) ) { // We know this is supposed to be GMT, so we're going to slap that Z on there by force. $date_created = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z'; } elseif ( ! empty( $content_struct['dateCreated'] ) ) { $date_created = $content_struct['dateCreated']->getIso(); } $post_date = ''; $post_date_gmt = ''; if ( ! empty( $date_created ) ) { $post_date = iso8601_to_datetime( $date_created ); $post_date_gmt = iso8601_to_datetime( $date_created, 'gmt' ); } $post_category = array(); if ( isset( $content_struct['categories'] ) ) { $catnames = $content_struct['categories']; if ( is_array( $catnames ) ) { foreach ( $catnames as $cat ) { $post_category[] = get_cat_ID( $cat ); } } } $postdata = compact( 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'to_ping', 'post_type', 'post_name', 'post_password', 'post_parent', 'menu_order', 'tags_input', 'page_template' ); $post_id = get_default_post_to_edit( $post_type, true )->ID; $postdata['ID'] = $post_id; // Only posts can be sticky. if ( 'post' === $post_type && isset( $content_struct['sticky'] ) ) { $data = $postdata; $data['sticky'] = $content_struct['sticky']; $error = $this->_toggle_sticky( $data ); if ( $error ) { return $error; } } if ( isset( $content_struct['custom_fields'] ) ) { $this->set_custom_fields( $post_id, $content_struct['custom_fields'] ); } if ( isset( $content_struct['wp_post_thumbnail'] ) ) { if ( set_post_thumbnail( $post_id, $content_struct['wp_post_thumbnail'] ) === false ) { return new IXR_Error( 404, __( 'Invalid attachment ID.' ) ); } unset( $content_struct['wp_post_thumbnail'] ); } // Handle enclosures. $enclosure = isset( $content_struct['enclosure'] ) ? $content_struct['enclosure'] : null; $this->add_enclosure_if_new( $post_id, $enclosure ); $this->attach_uploads( $post_id, $post_content ); /* * Handle post formats if assigned, value is validated earlier * in this function. */ if ( isset( $content_struct['wp_post_format'] ) ) { set_post_format( $post_id, $content_struct['wp_post_format'] ); } $post_id = wp_insert_post( $postdata, true ); if ( is_wp_error( $post_id ) ) { return new IXR_Error( 500, $post_id->get_error_message() ); } if ( ! $post_id ) { return new IXR_Error( 500, __( 'Sorry, the post could not be created.' ) ); } /** * Fires after a new post has been successfully created via the XML-RPC MovableType API. * * @since 3.4.0 * * @param int $post_id ID of the new post. * @param array $args An array of arguments to create the new post. */ do_action( 'xmlrpc_call_success_mw_newPost', $post_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase return (string) $post_id; } /** * Adds an enclosure to a post if it's new. * * @since 2.8.0 * * @param int $post_id Post ID. * @param array $enclosure Enclosure data. */ public function add_enclosure_if_new( $post_id, $enclosure ) { if ( is_array( $enclosure ) && isset( $enclosure['url'] ) && isset( $enclosure['length'] ) && isset( $enclosure['type'] ) ) { $encstring = $enclosure['url'] . "\n" . $enclosure['length'] . "\n" . $enclosure['type'] . "\n"; $found = false; $enclosures = get_post_meta( $post_id, 'enclosure' ); if ( $enclosures ) { foreach ( $enclosures as $enc ) { // This method used to omit the trailing new line. #23219 if ( rtrim( $enc, "\n" ) === rtrim( $encstring, "\n" ) ) { $found = true; break; } } } if ( ! $found ) { add_post_meta( $post_id, 'enclosure', $encstring ); } } } /** * Attaches an upload to a post. * * @since 2.1.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $post_id Post ID. * @param string $post_content Post Content for attachment. */ public function attach_uploads( $post_id, $post_content ) { global $wpdb; // Find any unattached files. $attachments = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts} WHERE post_parent = '0' AND post_type = 'attachment'" ); if ( is_array( $attachments ) ) { foreach ( $attachments as $file ) { if ( ! empty( $file->guid ) && str_contains( $post_content, $file->guid ) ) { $wpdb->update( $wpdb->posts, array( 'post_parent' => $post_id ), array( 'ID' => $file->ID ) ); } } } } /** * Edits a post. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Post ID. * @type string $1 Username. * @type string $2 Password. * @type array $3 Content structure. * @type int $4 Optional. Publish flag. 0 for draft, 1 for publish. Default 0. * } * @return true|IXR_Error True on success. */ public function mw_editPost( $args ) { $this->escape( $args ); $post_id = (int) $args[0]; $username = $args[1]; $password = $args[2]; $content_struct = $args[3]; $publish = isset( $args[4] ) ? $args[4] : 0; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'metaWeblog.editPost', $args, $this ); $postdata = get_post( $post_id, ARRAY_A ); /* * If there is no post data for the give post ID, stop now and return an error. * Otherwise a new post will be created (which was the old behavior). */ if ( ! $postdata || empty( $postdata['ID'] ) ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'edit_post', $post_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); } // Use wp.editPost to edit post types other than post and page. if ( ! in_array( $postdata['post_type'], array( 'post', 'page' ), true ) ) { return new IXR_Error( 401, __( 'Invalid post type.' ) ); } // Thwart attempt to change the post type. if ( ! empty( $content_struct['post_type'] ) && ( $content_struct['post_type'] !== $postdata['post_type'] ) ) { return new IXR_Error( 401, __( 'The post type may not be changed.' ) ); } // Check for a valid post format if one was given. if ( isset( $content_struct['wp_post_format'] ) ) { $content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] ); if ( ! array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) { return new IXR_Error( 404, __( 'Invalid post format.' ) ); } } $this->escape( $postdata ); $post_id = $postdata['ID']; $post_content = $postdata['post_content']; $post_title = $postdata['post_title']; $post_excerpt = $postdata['post_excerpt']; $post_password = $postdata['post_password']; $post_parent = $postdata['post_parent']; $post_type = $postdata['post_type']; $menu_order = $postdata['menu_order']; $ping_status = $postdata['ping_status']; $comment_status = $postdata['comment_status']; // Let WordPress manage slug if none was provided. $post_name = $postdata['post_name']; if ( isset( $content_struct['wp_slug'] ) ) { $post_name = $content_struct['wp_slug']; } // Only use a password if one was given. if ( isset( $content_struct['wp_password'] ) ) { $post_password = $content_struct['wp_password']; } // Only set a post parent if one was given. if ( isset( $content_struct['wp_page_parent_id'] ) ) { $post_parent = $content_struct['wp_page_parent_id']; } // Only set the 'menu_order' if it was given. if ( isset( $content_struct['wp_page_order'] ) ) { $menu_order = $content_struct['wp_page_order']; } $page_template = ''; if ( ! empty( $content_struct['wp_page_template'] ) && 'page' === $post_type ) { $page_template = $content_struct['wp_page_template']; } $post_author = $postdata['post_author']; // If an author ID was provided then use it instead. if ( isset( $content_struct['wp_author_id'] ) ) { // Check permissions if attempting to switch author to or from another user. if ( $user->ID !== (int) $content_struct['wp_author_id'] || $user->ID !== (int) $post_author ) { switch ( $post_type ) { case 'post': if ( ! current_user_can( 'edit_others_posts' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the post author as this user.' ) ); } break; case 'page': if ( ! current_user_can( 'edit_others_pages' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the page author as this user.' ) ); } break; default: return new IXR_Error( 401, __( 'Invalid post type.' ) ); } $post_author = $content_struct['wp_author_id']; } } if ( isset( $content_struct['mt_allow_comments'] ) ) { if ( ! is_numeric( $content_struct['mt_allow_comments'] ) ) { switch ( $content_struct['mt_allow_comments'] ) { case 'closed': $comment_status = 'closed'; break; case 'open': $comment_status = 'open'; break; default: $comment_status = get_default_comment_status( $post_type ); break; } } else { switch ( (int) $content_struct['mt_allow_comments'] ) { case 0: case 2: $comment_status = 'closed'; break; case 1: $comment_status = 'open'; break; default: $comment_status = get_default_comment_status( $post_type ); break; } } } if ( isset( $content_struct['mt_allow_pings'] ) ) { if ( ! is_numeric( $content_struct['mt_allow_pings'] ) ) { switch ( $content_struct['mt_allow_pings'] ) { case 'closed': $ping_status = 'closed'; break; case 'open': $ping_status = 'open'; break; default: $ping_status = get_default_comment_status( $post_type, 'pingback' ); break; } } else { switch ( (int) $content_struct['mt_allow_pings'] ) { case 0: $ping_status = 'closed'; break; case 1: $ping_status = 'open'; break; default: $ping_status = get_default_comment_status( $post_type, 'pingback' ); break; } } } if ( isset( $content_struct['title'] ) ) { $post_title = $content_struct['title']; } if ( isset( $content_struct['description'] ) ) { $post_content = $content_struct['description']; } $post_category = array(); if ( isset( $content_struct['categories'] ) ) { $catnames = $content_struct['categories']; if ( is_array( $catnames ) ) { foreach ( $catnames as $cat ) { $post_category[] = get_cat_ID( $cat ); } } } if ( isset( $content_struct['mt_excerpt'] ) ) { $post_excerpt = $content_struct['mt_excerpt']; } $post_more = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : ''; $post_status = $publish ? 'publish' : 'draft'; if ( isset( $content_struct[ "{$post_type}_status" ] ) ) { switch ( $content_struct[ "{$post_type}_status" ] ) { case 'draft': case 'pending': case 'private': case 'publish': $post_status = $content_struct[ "{$post_type}_status" ]; break; default: $post_status = $publish ? 'publish' : 'draft'; break; } } $tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : array(); if ( 'publish' === $post_status || 'private' === $post_status ) { if ( 'page' === $post_type && ! current_user_can( 'publish_pages' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this page.' ) ); } elseif ( ! current_user_can( 'publish_posts' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) ); } } if ( $post_more ) { $post_content = $post_content . '<!--more-->' . $post_more; } $to_ping = ''; if ( isset( $content_struct['mt_tb_ping_urls'] ) ) { $to_ping = $content_struct['mt_tb_ping_urls']; if ( is_array( $to_ping ) ) { $to_ping = implode( ' ', $to_ping ); } } // Do some timestamp voodoo. if ( ! empty( $content_struct['date_created_gmt'] ) ) { // We know this is supposed to be GMT, so we're going to slap that Z on there by force. $date_created = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z'; } elseif ( ! empty( $content_struct['dateCreated'] ) ) { $date_created = $content_struct['dateCreated']->getIso(); } // Default to not flagging the post date to be edited unless it's intentional. $edit_date = false; if ( ! empty( $date_created ) ) { $post_date = iso8601_to_datetime( $date_created ); $post_date_gmt = iso8601_to_datetime( $date_created, 'gmt' ); // Flag the post date to be edited. $edit_date = true; } else { $post_date = $postdata['post_date']; $post_date_gmt = $postdata['post_date_gmt']; } $newpost = array( 'ID' => $post_id, ); $newpost += compact( 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'edit_date', 'post_date', 'post_date_gmt', 'to_ping', 'post_name', 'post_password', 'post_parent', 'menu_order', 'post_author', 'tags_input', 'page_template' ); // We've got all the data -- post it. $result = wp_update_post( $newpost, true ); if ( is_wp_error( $result ) ) { return new IXR_Error( 500, $result->get_error_message() ); } if ( ! $result ) { return new IXR_Error( 500, __( 'Sorry, the post could not be updated.' ) ); } // Only posts can be sticky. if ( 'post' === $post_type && isset( $content_struct['sticky'] ) ) { $data = $newpost; $data['sticky'] = $content_struct['sticky']; $data['post_type'] = 'post'; $error = $this->_toggle_sticky( $data, true ); if ( $error ) { return $error; } } if ( isset( $content_struct['custom_fields'] ) ) { $this->set_custom_fields( $post_id, $content_struct['custom_fields'] ); } if ( isset( $content_struct['wp_post_thumbnail'] ) ) { // Empty value deletes, non-empty value adds/updates. if ( empty( $content_struct['wp_post_thumbnail'] ) ) { delete_post_thumbnail( $post_id ); } else { if ( set_post_thumbnail( $post_id, $content_struct['wp_post_thumbnail'] ) === false ) { return new IXR_Error( 404, __( 'Invalid attachment ID.' ) ); } } unset( $content_struct['wp_post_thumbnail'] ); } // Handle enclosures. $enclosure = isset( $content_struct['enclosure'] ) ? $content_struct['enclosure'] : null; $this->add_enclosure_if_new( $post_id, $enclosure ); $this->attach_uploads( $post_id, $post_content ); // Handle post formats if assigned, validation is handled earlier in this function. if ( isset( $content_struct['wp_post_format'] ) ) { set_post_format( $post_id, $content_struct['wp_post_format'] ); } /** * Fires after a post has been successfully updated via the XML-RPC MovableType API. * * @since 3.4.0 * * @param int $post_id ID of the updated post. * @param array $args An array of arguments to update the post. */ do_action( 'xmlrpc_call_success_mw_editPost', $post_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase return true; } /** * Retrieves a post. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Post ID. * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function mw_getPost( $args ) { $this->escape( $args ); $post_id = (int) $args[0]; $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } $postdata = get_post( $post_id, ARRAY_A ); if ( ! $postdata ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'edit_post', $post_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'metaWeblog.getPost', $args, $this ); if ( '' !== $postdata['post_date'] ) { $post_date = $this->_convert_date( $postdata['post_date'] ); $post_date_gmt = $this->_convert_date_gmt( $postdata['post_date_gmt'], $postdata['post_date'] ); $post_modified = $this->_convert_date( $postdata['post_modified'] ); $post_modified_gmt = $this->_convert_date_gmt( $postdata['post_modified_gmt'], $postdata['post_modified'] ); $categories = array(); $cat_ids = wp_get_post_categories( $post_id ); foreach ( $cat_ids as $cat_id ) { $categories[] = get_cat_name( $cat_id ); } $tagnames = array(); $tags = wp_get_post_tags( $post_id ); if ( ! empty( $tags ) ) { foreach ( $tags as $tag ) { $tagnames[] = $tag->name; } $tagnames = implode( ', ', $tagnames ); } else { $tagnames = ''; } $post = get_extended( $postdata['post_content'] ); $link = get_permalink( $postdata['ID'] ); // Get the author info. $author = get_userdata( $postdata['post_author'] ); $allow_comments = ( 'open' === $postdata['comment_status'] ) ? 1 : 0; $allow_pings = ( 'open' === $postdata['ping_status'] ) ? 1 : 0; // Consider future posts as published. if ( 'future' === $postdata['post_status'] ) { $postdata['post_status'] = 'publish'; } // Get post format. $post_format = get_post_format( $post_id ); if ( empty( $post_format ) ) { $post_format = 'standard'; } $sticky = false; if ( is_sticky( $post_id ) ) { $sticky = true; } $enclosure = array(); foreach ( (array) get_post_custom( $post_id ) as $key => $val ) { if ( 'enclosure' === $key ) { foreach ( (array) $val as $enc ) { $encdata = explode( "\n", $enc ); $enclosure['url'] = trim( htmlspecialchars( $encdata[0] ) ); $enclosure['length'] = (int) trim( $encdata[1] ); $enclosure['type'] = trim( $encdata[2] ); break 2; } } } $resp = array( 'dateCreated' => $post_date, 'userid' => $postdata['post_author'], 'postid' => $postdata['ID'], 'description' => $post['main'], 'title' => $postdata['post_title'], 'link' => $link, 'permaLink' => $link, // Commented out because no other tool seems to use this. // 'content' => $entry['post_content'], 'categories' => $categories, 'mt_excerpt' => $postdata['post_excerpt'], 'mt_text_more' => $post['extended'], 'wp_more_text' => $post['more_text'], 'mt_allow_comments' => $allow_comments, 'mt_allow_pings' => $allow_pings, 'mt_keywords' => $tagnames, 'wp_slug' => $postdata['post_name'], 'wp_password' => $postdata['post_password'], 'wp_author_id' => (string) $author->ID, 'wp_author_display_name' => $author->display_name, 'date_created_gmt' => $post_date_gmt, 'post_status' => $postdata['post_status'], 'custom_fields' => $this->get_custom_fields( $post_id ), 'wp_post_format' => $post_format, 'sticky' => $sticky, 'date_modified' => $post_modified, 'date_modified_gmt' => $post_modified_gmt, ); if ( ! empty( $enclosure ) ) { $resp['enclosure'] = $enclosure; } $resp['wp_post_thumbnail'] = get_post_thumbnail_id( $postdata['ID'] ); return $resp; } else { return new IXR_Error( 404, __( 'Sorry, no such post.' ) ); } } /** * Retrieves list of recent posts. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Optional. Number of posts. * } * @return array|IXR_Error */ public function mw_getRecentPosts( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; if ( isset( $args[3] ) ) { $query = array( 'numberposts' => absint( $args[3] ) ); } else { $query = array(); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_posts' ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'metaWeblog.getRecentPosts', $args, $this ); $posts_list = wp_get_recent_posts( $query ); if ( ! $posts_list ) { return array(); } $recent_posts = array(); foreach ( $posts_list as $entry ) { if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) { continue; } $post_date = $this->_convert_date( $entry['post_date'] ); $post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] ); $post_modified = $this->_convert_date( $entry['post_modified'] ); $post_modified_gmt = $this->_convert_date_gmt( $entry['post_modified_gmt'], $entry['post_modified'] ); $categories = array(); $cat_ids = wp_get_post_categories( $entry['ID'] ); foreach ( $cat_ids as $cat_id ) { $categories[] = get_cat_name( $cat_id ); } $tagnames = array(); $tags = wp_get_post_tags( $entry['ID'] ); if ( ! empty( $tags ) ) { foreach ( $tags as $tag ) { $tagnames[] = $tag->name; } $tagnames = implode( ', ', $tagnames ); } else { $tagnames = ''; } $post = get_extended( $entry['post_content'] ); $link = get_permalink( $entry['ID'] ); // Get the post author info. $author = get_userdata( $entry['post_author'] ); $allow_comments = ( 'open' === $entry['comment_status'] ) ? 1 : 0; $allow_pings = ( 'open' === $entry['ping_status'] ) ? 1 : 0; // Consider future posts as published. if ( 'future' === $entry['post_status'] ) { $entry['post_status'] = 'publish'; } // Get post format. $post_format = get_post_format( $entry['ID'] ); if ( empty( $post_format ) ) { $post_format = 'standard'; } $recent_posts[] = array( 'dateCreated' => $post_date, 'userid' => $entry['post_author'], 'postid' => (string) $entry['ID'], 'description' => $post['main'], 'title' => $entry['post_title'], 'link' => $link, 'permaLink' => $link, // Commented out because no other tool seems to use this. // 'content' => $entry['post_content'], 'categories' => $categories, 'mt_excerpt' => $entry['post_excerpt'], 'mt_text_more' => $post['extended'], 'wp_more_text' => $post['more_text'], 'mt_allow_comments' => $allow_comments, 'mt_allow_pings' => $allow_pings, 'mt_keywords' => $tagnames, 'wp_slug' => $entry['post_name'], 'wp_password' => $entry['post_password'], 'wp_author_id' => (string) $author->ID, 'wp_author_display_name' => $author->display_name, 'date_created_gmt' => $post_date_gmt, 'post_status' => $entry['post_status'], 'custom_fields' => $this->get_custom_fields( $entry['ID'] ), 'wp_post_format' => $post_format, 'date_modified' => $post_modified, 'date_modified_gmt' => $post_modified_gmt, 'sticky' => ( 'post' === $entry['post_type'] && is_sticky( $entry['ID'] ) ), 'wp_post_thumbnail' => get_post_thumbnail_id( $entry['ID'] ), ); } return $recent_posts; } /** * Retrieves the list of categories on a given blog. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function mw_getCategories( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_posts' ) ) { return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'metaWeblog.getCategories', $args, $this ); $categories_struct = array(); $cats = get_categories( array( 'get' => 'all' ) ); if ( $cats ) { foreach ( $cats as $cat ) { $struct = array(); $struct['categoryId'] = $cat->term_id; $struct['parentId'] = $cat->parent; $struct['description'] = $cat->name; $struct['categoryDescription'] = $cat->description; $struct['categoryName'] = $cat->name; $struct['htmlUrl'] = esc_html( get_category_link( $cat->term_id ) ); $struct['rssUrl'] = esc_html( get_category_feed_link( $cat->term_id, 'rss2' ) ); $categories_struct[] = $struct; } } return $categories_struct; } /** * Uploads a file, following your settings. * * Adapted from a patch by Johann Richard. * * @link http://mycvs.org/archives/2004/06/30/file-upload-to-wordpress-in-ecto/ * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type array $3 Data. * } * @return array|IXR_Error */ public function mw_newMediaObject( $args ) { $username = $this->escape( $args[1] ); $password = $this->escape( $args[2] ); $data = $args[3]; $name = sanitize_file_name( $data['name'] ); $type = $data['type']; $bits = $data['bits']; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'metaWeblog.newMediaObject', $args, $this ); if ( ! current_user_can( 'upload_files' ) ) { $this->error = new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) ); return $this->error; } if ( is_multisite() && upload_is_user_over_quota( false ) ) { $this->error = new IXR_Error( 401, sprintf( /* translators: %s: Allowed space allocation. */ __( 'Sorry, you have used your space allocation of %s. Please delete some files to upload more files.' ), size_format( get_space_allowed() * MB_IN_BYTES ) ) ); return $this->error; } /** * Filters whether to preempt the XML-RPC media upload. * * Returning a truthy value will effectively short-circuit the media upload, * returning that value as a 500 error instead. * * @since 2.1.0 * * @param bool $error Whether to pre-empt the media upload. Default false. */ $upload_err = apply_filters( 'pre_upload_error', false ); if ( $upload_err ) { return new IXR_Error( 500, $upload_err ); } $upload = wp_upload_bits( $name, null, $bits ); if ( ! empty( $upload['error'] ) ) { /* translators: 1: File name, 2: Error message. */ $error_string = sprintf( __( 'Could not write file %1$s (%2$s).' ), $name, $upload['error'] ); return new IXR_Error( 500, $error_string ); } // Construct the attachment array. $post_id = 0; if ( ! empty( $data['post_id'] ) ) { $post_id = (int) $data['post_id']; if ( ! current_user_can( 'edit_post', $post_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); } } $attachment = array( 'post_title' => $name, 'post_content' => '', 'post_type' => 'attachment', 'post_parent' => $post_id, 'post_mime_type' => $type, 'guid' => $upload['url'], ); // Save the data. $attachment_id = wp_insert_attachment( $attachment, $upload['file'], $post_id ); wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $upload['file'] ) ); /** * Fires after a new attachment has been added via the XML-RPC MovableType API. * * @since 3.4.0 * * @param int $attachment_id ID of the new attachment. * @param array $args An array of arguments to add the attachment. */ do_action( 'xmlrpc_call_success_mw_newMediaObject', $attachment_id, $args ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase $struct = $this->_prepare_media_item( get_post( $attachment_id ) ); // Deprecated values. $struct['id'] = $struct['attachment_id']; $struct['file'] = $struct['title']; $struct['url'] = $struct['link']; return $struct; } /* * MovableType API functions. * Specs archive on https://web.archive.org/web/20050220091302/http://www.movabletype.org/docs/mtmanual_programmatic.html */ /** * Retrieves the post titles of recent posts. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * @type int $3 Optional. Number of posts. * } * @return array|IXR_Error */ public function mt_getRecentPostTitles( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; if ( isset( $args[3] ) ) { $query = array( 'numberposts' => absint( $args[3] ) ); } else { $query = array(); } $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'mt.getRecentPostTitles', $args, $this ); $posts_list = wp_get_recent_posts( $query ); if ( ! $posts_list ) { $this->error = new IXR_Error( 500, __( 'No posts found or an error occurred while retrieving posts.' ) ); return $this->error; } $recent_posts = array(); foreach ( $posts_list as $entry ) { if ( ! current_user_can( 'edit_post', $entry['ID'] ) ) { continue; } $post_date = $this->_convert_date( $entry['post_date'] ); $post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] ); $recent_posts[] = array( 'dateCreated' => $post_date, 'userid' => $entry['post_author'], 'postid' => (string) $entry['ID'], 'title' => $entry['post_title'], 'post_status' => $entry['post_status'], 'date_created_gmt' => $post_date_gmt, ); } return $recent_posts; } /** * Retrieves the list of all categories on a blog. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Blog ID (unused). * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function mt_getCategoryList( $args ) { $this->escape( $args ); $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! current_user_can( 'edit_posts' ) ) { return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'mt.getCategoryList', $args, $this ); $categories_struct = array(); $cats = get_categories( array( 'hide_empty' => 0, 'hierarchical' => 0, ) ); if ( $cats ) { foreach ( $cats as $cat ) { $struct = array(); $struct['categoryId'] = $cat->term_id; $struct['categoryName'] = $cat->name; $categories_struct[] = $struct; } } return $categories_struct; } /** * Retrieves post categories. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Post ID. * @type string $1 Username. * @type string $2 Password. * } * @return array|IXR_Error */ public function mt_getPostCategories( $args ) { $this->escape( $args ); $post_id = (int) $args[0]; $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } if ( ! get_post( $post_id ) ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'edit_post', $post_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'mt.getPostCategories', $args, $this ); $categories = array(); $cat_ids = wp_get_post_categories( (int) $post_id ); // First listed category will be the primary category. $is_primary = true; foreach ( $cat_ids as $cat_id ) { $categories[] = array( 'categoryName' => get_cat_name( $cat_id ), 'categoryId' => (string) $cat_id, 'isPrimary' => $is_primary, ); $is_primary = false; } return $categories; } /** * Sets categories for a post. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Post ID. * @type string $1 Username. * @type string $2 Password. * @type array $3 Categories. * } * @return true|IXR_Error True on success. */ public function mt_setPostCategories( $args ) { $this->escape( $args ); $post_id = (int) $args[0]; $username = $args[1]; $password = $args[2]; $categories = $args[3]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'mt.setPostCategories', $args, $this ); if ( ! get_post( $post_id ) ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'edit_post', $post_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) ); } $cat_ids = array(); foreach ( $categories as $cat ) { $cat_ids[] = $cat['categoryId']; } wp_set_post_categories( $post_id, $cat_ids ); return true; } /** * Retrieves an array of methods supported by this server. * * @since 1.5.0 * * @return array */ public function mt_supportedMethods() { /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'mt.supportedMethods', array(), $this ); return array_keys( $this->methods ); } /** * Retrieves an empty array because we don't support per-post text filters. * * @since 1.5.0 */ public function mt_supportedTextFilters() { /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'mt.supportedTextFilters', array(), $this ); /** * Filters the MoveableType text filters list for XML-RPC. * * @since 2.2.0 * * @param array $filters An array of text filters. */ return apply_filters( 'xmlrpc_text_filters', array() ); } /** * Retrieves trackbacks sent to a given post. * * @since 1.5.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $post_id * @return array|IXR_Error */ public function mt_getTrackbackPings( $post_id ) { global $wpdb; /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'mt.getTrackbackPings', $post_id, $this ); $actual_post = get_post( $post_id, ARRAY_A ); if ( ! $actual_post ) { return new IXR_Error( 404, __( 'Sorry, no such post.' ) ); } $comments = $wpdb->get_results( $wpdb->prepare( "SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ) ); if ( ! $comments ) { return array(); } $trackback_pings = array(); foreach ( $comments as $comment ) { if ( 'trackback' === $comment->comment_type ) { $content = $comment->comment_content; $title = substr( $content, 8, ( strpos( $content, '</strong>' ) - 8 ) ); $trackback_pings[] = array( 'pingTitle' => $title, 'pingURL' => $comment->comment_author_url, 'pingIP' => $comment->comment_author_IP, ); } } return $trackback_pings; } /** * Sets a post's publish status to 'publish'. * * @since 1.5.0 * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type int $0 Post ID. * @type string $1 Username. * @type string $2 Password. * } * @return int|IXR_Error */ public function mt_publishPost( $args ) { $this->escape( $args ); $post_id = (int) $args[0]; $username = $args[1]; $password = $args[2]; $user = $this->login( $username, $password ); if ( ! $user ) { return $this->error; } /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'mt.publishPost', $args, $this ); $postdata = get_post( $post_id, ARRAY_A ); if ( ! $postdata ) { return new IXR_Error( 404, __( 'Invalid post ID.' ) ); } if ( ! current_user_can( 'publish_posts' ) || ! current_user_can( 'edit_post', $post_id ) ) { return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) ); } $postdata['post_status'] = 'publish'; // Retain old categories. $postdata['post_category'] = wp_get_post_categories( $post_id ); $this->escape( $postdata ); return wp_update_post( $postdata ); } /* * Pingback functions. * Specs on www.hixie.ch/specs/pingback/pingback */ /** * Retrieves a pingback and registers it. * * @since 1.5.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param array $args { * Method arguments. Note: arguments must be ordered as documented. * * @type string $0 URL of page linked from. * @type string $1 URL of page linked to. * } * @return string|IXR_Error */ public function pingback_ping( $args ) { global $wpdb; /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'pingback.ping', $args, $this ); $this->escape( $args ); $pagelinkedfrom = str_replace( '&', '&', $args[0] ); $pagelinkedto = str_replace( '&', '&', $args[1] ); $pagelinkedto = str_replace( '&', '&', $pagelinkedto ); /** * Filters the pingback source URI. * * @since 3.6.0 * * @param string $pagelinkedfrom URI of the page linked from. * @param string $pagelinkedto URI of the page linked to. */ $pagelinkedfrom = apply_filters( 'pingback_ping_source_uri', $pagelinkedfrom, $pagelinkedto ); if ( ! $pagelinkedfrom ) { return $this->pingback_error( 0, __( 'A valid URL was not provided.' ) ); } // Check if the page linked to is on our site. $pos1 = strpos( $pagelinkedto, str_replace( array( 'http://www.', 'http://', 'https://www.', 'https://' ), '', get_option( 'home' ) ) ); if ( ! $pos1 ) { return $this->pingback_error( 0, __( 'Is there no link to us?' ) ); } /* * Let's find which post is linked to. * FIXME: Does url_to_postid() cover all these cases already? * If so, then let's use it and drop the old code. */ $urltest = parse_url( $pagelinkedto ); $post_id = url_to_postid( $pagelinkedto ); if ( $post_id ) { // $way } elseif ( isset( $urltest['path'] ) && preg_match( '#p/[0-9]{1,}#', $urltest['path'], $match ) ) { // The path defines the post_ID (archives/p/XXXX). $blah = explode( '/', $match[0] ); $post_id = (int) $blah[1]; } elseif ( isset( $urltest['query'] ) && preg_match( '#p=[0-9]{1,}#', $urltest['query'], $match ) ) { // The query string defines the post_ID (?p=XXXX). $blah = explode( '=', $match[0] ); $post_id = (int) $blah[1]; } elseif ( isset( $urltest['fragment'] ) ) { // An #anchor is there, it's either... if ( (int) $urltest['fragment'] ) { // ...an integer #XXXX (simplest case), $post_id = (int) $urltest['fragment']; } elseif ( preg_match( '/post-[0-9]+/', $urltest['fragment'] ) ) { // ...a post ID in the form 'post-###', $post_id = preg_replace( '/[^0-9]+/', '', $urltest['fragment'] ); } elseif ( is_string( $urltest['fragment'] ) ) { // ...or a string #title, a little more complicated. $title = preg_replace( '/[^a-z0-9]/i', '.', $urltest['fragment'] ); $sql = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_title RLIKE %s", $title ); $post_id = $wpdb->get_var( $sql ); if ( ! $post_id ) { // Returning unknown error '0' is better than die()'ing. return $this->pingback_error( 0, '' ); } } } else { // TODO: Attempt to extract a post ID from the given URL. return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either does not exist, or it is not a pingback-enabled resource.' ) ); } $post_id = (int) $post_id; $post = get_post( $post_id ); if ( ! $post ) { // Post not found. return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either does not exist, or it is not a pingback-enabled resource.' ) ); } if ( url_to_postid( $pagelinkedfrom ) === $post_id ) { return $this->pingback_error( 0, __( 'The source URL and the target URL cannot both point to the same resource.' ) ); } // Check if pings are on. if ( ! pings_open( $post ) ) { return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either does not exist, or it is not a pingback-enabled resource.' ) ); } // Let's check that the remote site didn't already pingback this entry. if ( $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_author_url = %s", $post_id, $pagelinkedfrom ) ) ) { return $this->pingback_error( 48, __( 'The pingback has already been registered.' ) ); } /* * The remote site may have sent the pingback before it finished publishing its own content * containing this pingback URL. If that happens then it won't be immediately possible to fetch * the pinging post; adding a small delay reduces the likelihood of this happening. * * While there are more robust methods than calling `sleep()` here (because `sleep()` merely * mitigates the risk of requesting the remote post before it's available), this is effective * enough for most cases and avoids introducing more complexity into this code. * * One way to improve the reliability of this code might be to add failure-handling to the remote * fetch and retry up to a set number of times if it receives a 404. This could also handle 401 and * 403 responses to differentiate the "does not exist" failure from the "may not access" failure. */ sleep( 1 ); $remote_ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] ); /** This filter is documented in wp-includes/class-wp-http.php */ $user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), $pagelinkedfrom ); // Let's check the remote site. $http_api_args = array( 'timeout' => 10, 'redirection' => 0, 'limit_response_size' => 153600, // 150 KB 'user-agent' => "$user_agent; verifying pingback from $remote_ip", 'headers' => array( 'X-Pingback-Forwarded-For' => $remote_ip, ), ); $request = wp_safe_remote_get( $pagelinkedfrom, $http_api_args ); $remote_source = wp_remote_retrieve_body( $request ); $remote_source_original = $remote_source; if ( ! $remote_source ) { return $this->pingback_error( 16, __( 'The source URL does not exist.' ) ); } /** * Filters the pingback remote source. * * @since 2.5.0 * * @param string $remote_source Response source for the page linked from. * @param string $pagelinkedto URL of the page linked to. */ $remote_source = apply_filters( 'pre_remote_source', $remote_source, $pagelinkedto ); // Work around bug in strip_tags(): $remote_source = str_replace( '<!DOC', '<DOC', $remote_source ); $remote_source = preg_replace( '/[\r\n\t ]+/', ' ', $remote_source ); // normalize spaces $remote_source = preg_replace( '/<\/*(h1|h2|h3|h4|h5|h6|p|th|td|li|dt|dd|pre|caption|input|textarea|button|body)[^>]*>/', "\n\n", $remote_source ); preg_match( '|<title>([^<]*?)</title>|is', $remote_source, $matchtitle ); $title = isset( $matchtitle[1] ) ? $matchtitle[1] : ''; if ( empty( $title ) ) { return $this->pingback_error( 32, __( 'A title on that page cannot be found.' ) ); } // Remove all script and style tags including their content. $remote_source = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $remote_source ); // Just keep the tag we need. $remote_source = strip_tags( $remote_source, '<a>' ); $p = explode( "\n\n", $remote_source ); $preg_target = preg_quote( $pagelinkedto, '|' ); foreach ( $p as $para ) { if ( str_contains( $para, $pagelinkedto ) ) { // It exists, but is it a link? preg_match( '|<a[^>]+?' . $preg_target . '[^>]*>([^>]+?)</a>|', $para, $context ); // If the URL isn't in a link context, keep looking. if ( empty( $context ) ) { continue; } /* * We're going to use this fake tag to mark the context in a bit. * The marker is needed in case the link text appears more than once in the paragraph. */ $excerpt = preg_replace( '|\</?wpcontext\>|', '', $para ); // prevent really long link text if ( strlen( $context[1] ) > 100 ) { $context[1] = substr( $context[1], 0, 100 ) . '…'; } $marker = '<wpcontext>' . $context[1] . '</wpcontext>'; // Set up our marker. $excerpt = str_replace( $context[0], $marker, $excerpt ); // Swap out the link for our marker. $excerpt = strip_tags( $excerpt, '<wpcontext>' ); // Strip all tags but our context marker. $excerpt = trim( $excerpt ); $preg_marker = preg_quote( $marker, '|' ); $excerpt = preg_replace( "|.*?\s(.{0,100}$preg_marker.{0,100})\s.*|s", '$1', $excerpt ); $excerpt = strip_tags( $excerpt ); // YES, again, to remove the marker wrapper. break; } } if ( empty( $context ) ) { // Link to target not found. return $this->pingback_error( 17, __( 'The source URL does not contain a link to the target URL, and so cannot be used as a source.' ) ); } $pagelinkedfrom = str_replace( '&', '&', $pagelinkedfrom ); $context = '[…] ' . esc_html( $excerpt ) . ' […]'; $pagelinkedfrom = $this->escape( $pagelinkedfrom ); $comment_post_id = (int) $post_id; $comment_author = $title; $comment_author_email = ''; $this->escape( $comment_author ); $comment_author_url = $pagelinkedfrom; $comment_content = $context; $this->escape( $comment_content ); $comment_type = 'pingback'; $commentdata = array( 'comment_post_ID' => $comment_post_id, ); $commentdata += compact( 'comment_author', 'comment_author_url', 'comment_author_email', 'comment_content', 'comment_type', 'remote_source', 'remote_source_original' ); $comment_id = wp_new_comment( $commentdata ); if ( is_wp_error( $comment_id ) ) { return $this->pingback_error( 0, $comment_id->get_error_message() ); } /** * Fires after a post pingback has been sent. * * @since 0.71 * * @param int $comment_id Comment ID. */ do_action( 'pingback_post', $comment_id ); /* translators: 1: URL of the page linked from, 2: URL of the page linked to. */ return sprintf( __( 'Pingback from %1$s to %2$s registered. Keep the web talking! :-)' ), $pagelinkedfrom, $pagelinkedto ); } /** * Retrieves an array of URLs that pingbacked the given URL. * * Specs on http://www.aquarionics.com/misc/archives/blogite/0198.html * * @since 1.5.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $url * @return array|IXR_Error */ public function pingback_extensions_getPingbacks( $url ) { global $wpdb; /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'pingback.extensions.getPingbacks', $url, $this ); $url = $this->escape( $url ); $post_id = url_to_postid( $url ); if ( ! $post_id ) { // We aren't sure that the resource is available and/or pingback enabled. return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either does not exist, or it is not a pingback-enabled resource.' ) ); } $actual_post = get_post( $post_id, ARRAY_A ); if ( ! $actual_post ) { // No such post = resource not found. return $this->pingback_error( 32, __( 'The specified target URL does not exist.' ) ); } $comments = $wpdb->get_results( $wpdb->prepare( "SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ) ); if ( ! $comments ) { return array(); } $pingbacks = array(); foreach ( $comments as $comment ) { if ( 'pingback' === $comment->comment_type ) { $pingbacks[] = $comment->comment_author_url; } } return $pingbacks; } /** * Sends a pingback error based on the given error code and message. * * @since 3.6.0 * * @param int $code Error code. * @param string $message Error message. * @return IXR_Error Error object. */ protected function pingback_error( $code, $message ) { /** * Filters the XML-RPC pingback error return. * * @since 3.5.1 * * @param IXR_Error $error An IXR_Error object containing the error code and message. */ return apply_filters( 'xmlrpc_pingback_error', new IXR_Error( $code, $message ) ); } } https-detection-20260605121151.php 0000666 00000013341 15226762357 0011775 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260605172432.php 0000666 00000013341 15226762357 0012005 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260605205851.php 0000666 00000011205 15226762357 0012017 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-customize-cropped-image-control.php 0000666 00000002663 15226762357 0015250 0 ustar 00 <?php /** * Customize API: WP_Customize_Cropped_Image_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Cropped Image Control class. * * @since 4.3.0 * * @see WP_Customize_Image_Control */ class WP_Customize_Cropped_Image_Control extends WP_Customize_Image_Control { /** * Control type. * * @since 4.3.0 * @var string */ public $type = 'cropped_image'; /** * Suggested width for cropped image. * * @since 4.3.0 * @var int */ public $width = 150; /** * Suggested height for cropped image. * * @since 4.3.0 * @var int */ public $height = 150; /** * Whether the width is flexible. * * @since 4.3.0 * @var bool */ public $flex_width = false; /** * Whether the height is flexible. * * @since 4.3.0 * @var bool */ public $flex_height = false; /** * Enqueue control related scripts/styles. * * @since 4.3.0 */ public function enqueue() { wp_enqueue_script( 'customize-views' ); parent::enqueue(); } /** * Refresh the parameters passed to the JavaScript via JSON. * * @since 4.3.0 * * @see WP_Customize_Control::to_json() */ public function to_json() { parent::to_json(); $this->json['width'] = absint( $this->width ); $this->json['height'] = absint( $this->height ); $this->json['flex_width'] = absint( $this->flex_width ); $this->json['flex_height'] = absint( $this->flex_height ); } } https-migration-20260606042005.php 0000666 00000011205 15226762357 0012006 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-customize-nav-menu-auto-add-control.php 0000666 00000002142 15226762357 0015746 0 ustar 00 <?php /** * Customize API: WP_Customize_Nav_Menu_Auto_Add_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize control to represent the auto_add field for a given menu. * * @since 4.3.0 * * @see WP_Customize_Control */ class WP_Customize_Nav_Menu_Auto_Add_Control extends WP_Customize_Control { /** * Type of control, used by JS. * * @since 4.3.0 * @var string */ public $type = 'nav_menu_auto_add'; /** * No-op since we're using JS template. * * @since 4.3.0 */ protected function render_content() {} /** * Render the Underscore template for this control. * * @since 4.3.0 */ protected function content_template() { ?> <# var elementId = _.uniqueId( 'customize-nav-menu-auto-add-control-' ); #> <span class="customize-control-title"><?php _e( 'Menu Options' ); ?></span> <span class="customize-inside-control-row"> <input id="{{ elementId }}" type="checkbox" class="auto_add" /> <label for="{{ elementId }}"> <?php _e( 'Automatically add new top-level pages to this menu' ); ?> </label> </span> <?php } } theme-previews.php 0000666 00000005536 15226762357 0010255 0 ustar 00 <?php /** * Theme previews using the Site Editor for block themes. * * @package WordPress */ /** * Filters the blog option to return the path for the previewed theme. * * @since 6.3.0 * * @param string $current_stylesheet The current theme's stylesheet or template path. * @return string The previewed theme's stylesheet or template path. */ function wp_get_theme_preview_path( $current_stylesheet = null ) { if ( ! current_user_can( 'switch_themes' ) ) { return $current_stylesheet; } $preview_stylesheet = ! empty( $_GET['wp_theme_preview'] ) ? sanitize_text_field( wp_unslash( $_GET['wp_theme_preview'] ) ) : null; $wp_theme = wp_get_theme( $preview_stylesheet ); if ( ! is_wp_error( $wp_theme->errors() ) ) { if ( current_filter() === 'template' ) { $theme_path = $wp_theme->get_template(); } else { $theme_path = $wp_theme->get_stylesheet(); } return sanitize_text_field( $theme_path ); } return $current_stylesheet; } /** * Adds a middleware to `apiFetch` to set the theme for the preview. * This adds a `wp_theme_preview` URL parameter to API requests from the Site Editor, so they also respond as if the theme is set to the value of the parameter. * * @since 6.3.0 */ function wp_attach_theme_preview_middleware() { // Don't allow non-admins to preview themes. if ( ! current_user_can( 'switch_themes' ) ) { return; } wp_add_inline_script( 'wp-api-fetch', sprintf( 'wp.apiFetch.use( wp.apiFetch.createThemePreviewMiddleware( %s ) );', wp_json_encode( sanitize_text_field( wp_unslash( $_GET['wp_theme_preview'] ) ), JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) ), 'after' ); } /** * Set a JavaScript constant for theme activation. * * Sets the JavaScript global WP_BLOCK_THEME_ACTIVATE_NONCE containing the nonce * required to activate a theme. For use within the site editor. * * @see https://github.com/WordPress/gutenberg/pull/41836 * * @since 6.3.0 * @access private */ function wp_block_theme_activate_nonce() { $nonce_handle = 'switch-theme_' . wp_get_theme_preview_path(); ?> <script type="text/javascript"> window.WP_BLOCK_THEME_ACTIVATE_NONCE = <?php echo wp_json_encode( wp_create_nonce( $nonce_handle ), JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); ?>; </script> <?php } /** * Add filters and actions to enable Block Theme Previews in the Site Editor. * * The filters and actions should be added after `pluggable.php` is included as they may * trigger code that uses `current_user_can()` which requires functionality from `pluggable.php`. * * @since 6.3.2 */ function wp_initialize_theme_preview_hooks() { if ( ! empty( $_GET['wp_theme_preview'] ) ) { add_filter( 'stylesheet', 'wp_get_theme_preview_path' ); add_filter( 'template', 'wp_get_theme_preview_path' ); add_action( 'init', 'wp_attach_theme_preview_middleware' ); add_action( 'admin_head', 'wp_block_theme_activate_nonce' ); } } class-wp-theme-json-resolver.php 0000666 00000105632 15226762357 0012746 0 ustar 00 <?php /** * WP_Theme_JSON_Resolver class * * @package WordPress * @subpackage Theme * @since 5.8.0 */ /** * Class that abstracts the processing of the different data sources * for site-level config and offers an API to work with them. * * This class is for internal core usage and is not supposed to be used by extenders (plugins and/or themes). * This is a low-level API that may need to do breaking changes. Please, * use get_global_settings(), get_global_styles(), and get_global_stylesheet() instead. * * @access private */ #[AllowDynamicProperties] class WP_Theme_JSON_Resolver { /** * Container for keep track of registered blocks. * * @since 6.1.0 * @var array */ protected static $blocks_cache = array( 'core' => array(), 'blocks' => array(), 'theme' => array(), 'user' => array(), ); /** * Container for data coming from core. * * @since 5.8.0 * @var WP_Theme_JSON */ protected static $core = null; /** * Container for data coming from the blocks. * * @since 6.1.0 * @var WP_Theme_JSON */ protected static $blocks = null; /** * Container for data coming from the theme. * * @since 5.8.0 * @var WP_Theme_JSON */ protected static $theme = null; /** * Container for data coming from the user. * * @since 5.9.0 * @var WP_Theme_JSON */ protected static $user = null; /** * Stores the ID of the custom post type * that holds the user data. * * @since 5.9.0 * @var int */ protected static $user_custom_post_type_id = null; /** * Container to keep loaded i18n schema for `theme.json`. * * @since 5.8.0 As `$theme_json_i18n`. * @since 5.9.0 Renamed from `$theme_json_i18n` to `$i18n_schema`. * @var array */ protected static $i18n_schema = null; /** * `theme.json` file cache. * * @since 6.1.0 * @var array */ protected static $theme_json_file_cache = array(); /** * Processes a file that adheres to the theme.json schema * and returns an array with its contents, or a void array if none found. * * @since 5.8.0 * @since 6.1.0 Added caching. * * @param string $file_path Path to file. Empty if no file. * @return array Contents that adhere to the theme.json schema. */ protected static function read_json_file( $file_path ) { if ( $file_path ) { if ( array_key_exists( $file_path, static::$theme_json_file_cache ) ) { return static::$theme_json_file_cache[ $file_path ]; } $decoded_file = wp_json_file_decode( $file_path, array( 'associative' => true ) ); if ( is_array( $decoded_file ) ) { static::$theme_json_file_cache[ $file_path ] = $decoded_file; return static::$theme_json_file_cache[ $file_path ]; } } return array(); } /** * Returns a data structure used in theme.json translation. * * @since 5.8.0 * @deprecated 5.9.0 * * @return array An array of theme.json fields that are translatable and the keys that are translatable. */ public static function get_fields_to_translate() { _deprecated_function( __METHOD__, '5.9.0' ); return array(); } /** * Given a theme.json structure modifies it in place to update certain values * by its translated strings according to the language set by the user. * * @since 5.8.0 * * @param array $theme_json The theme.json to translate. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return array Returns the modified $theme_json_structure. */ protected static function translate( $theme_json, $domain = 'default' ) { if ( null === static::$i18n_schema ) { $i18n_schema = wp_json_file_decode( __DIR__ . '/theme-i18n.json' ); static::$i18n_schema = null === $i18n_schema ? array() : $i18n_schema; } return translate_settings_using_i18n_schema( static::$i18n_schema, $theme_json, $domain ); } /** * Returns core's origin config. * * @since 5.8.0 * * @return WP_Theme_JSON Entity that holds core data. */ public static function get_core_data() { if ( null !== static::$core && static::has_same_registered_blocks( 'core' ) ) { return static::$core; } $config = static::read_json_file( __DIR__ . '/theme.json' ); $config = static::translate( $config ); /** * Filters the default data provided by WordPress for global styles & settings. * * @since 6.1.0 * * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. */ $theme_json = apply_filters( 'wp_theme_json_data_default', new WP_Theme_JSON_Data( $config, 'default' ) ); /* * Backward compatibility for extenders returning a WP_Theme_JSON_Data * compatible class that is not a WP_Theme_JSON_Data object. */ if ( $theme_json instanceof WP_Theme_JSON_Data ) { static::$core = $theme_json->get_theme_json(); } else { $config = $theme_json->get_data(); static::$core = new WP_Theme_JSON( $config, 'default' ); } return static::$core; } /** * Checks whether the registered blocks were already processed for this origin. * * @since 6.1.0 * * @param string $origin Data source for which to cache the blocks. * Valid values are 'core', 'blocks', 'theme', and 'user'. * @return bool True on success, false otherwise. */ protected static function has_same_registered_blocks( $origin ) { // Bail out if the origin is invalid. if ( ! isset( static::$blocks_cache[ $origin ] ) ) { return false; } $registry = WP_Block_Type_Registry::get_instance(); $blocks = $registry->get_all_registered(); // Is there metadata for all currently registered blocks? $block_diff = array_diff_key( $blocks, static::$blocks_cache[ $origin ] ); if ( empty( $block_diff ) ) { return true; } foreach ( $blocks as $block_name => $block_type ) { static::$blocks_cache[ $origin ][ $block_name ] = true; } return false; } /** * Returns the theme's data. * * Data from theme.json will be backfilled from existing * theme supports, if any. Note that if the same data * is present in theme.json and in theme supports, * the theme.json takes precedence. * * @since 5.8.0 * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed. * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports. * @since 6.6.0 Add support for 'default-font-sizes' and 'default-spacing-sizes' theme supports. * Added registration and merging of block style variations from partial theme.json files and the block styles registry. * * @param array $deprecated Deprecated. Not used. * @param array $options { * Options arguments. * * @type bool $with_supports Whether to include theme supports in the data. Default true. * } * @return WP_Theme_JSON Entity that holds theme data. */ public static function get_theme_data( $deprecated = array(), $options = array() ) { if ( ! empty( $deprecated ) ) { _deprecated_argument( __METHOD__, '5.9.0' ); } $options = wp_parse_args( $options, array( 'with_supports' => true ) ); if ( null === static::$theme || ! static::has_same_registered_blocks( 'theme' ) ) { $wp_theme = wp_get_theme(); $theme_json_file = $wp_theme->get_file_path( 'theme.json' ); if ( is_readable( $theme_json_file ) ) { $theme_json_data = static::read_json_file( $theme_json_file ); $theme_json_data = static::translate( $theme_json_data, $wp_theme->get( 'TextDomain' ) ); } else { $theme_json_data = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ); } /* * Register variations defined by theme partials (theme.json files in the styles directory). * This is required so the variations pass sanitization of theme.json data. */ $variations = static::get_style_variations( 'block' ); wp_register_block_style_variations_from_theme_json_partials( $variations ); /* * Source variations from the block registry and block style variation files. Then, merge them into the existing theme.json data. * * In case the same style properties are defined in several sources, this is how we should resolve the values, * from higher to lower priority: * * - styles.blocks.blockType.variations from theme.json * - styles.variations from theme.json * - variations from block style variation files * - variations from block styles registry * * See test_add_registered_block_styles_to_theme_data and test_unwraps_block_style_variations. * */ $theme_json_data = static::inject_variations_from_block_style_variation_files( $theme_json_data, $variations ); $theme_json_data = static::inject_variations_from_block_styles_registry( $theme_json_data ); /** * Filters the data provided by the theme for global styles and settings. * * @since 6.1.0 * * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. */ $theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data( $theme_json_data, 'theme' ) ); /* * Backward compatibility for extenders returning a WP_Theme_JSON_Data * compatible class that is not a WP_Theme_JSON_Data object. */ if ( $theme_json instanceof WP_Theme_JSON_Data ) { static::$theme = $theme_json->get_theme_json(); } else { $config = $theme_json->get_data(); static::$theme = new WP_Theme_JSON( $config ); } if ( $wp_theme->parent() ) { // Get parent theme.json. $parent_theme_json_file = $wp_theme->parent()->get_file_path( 'theme.json' ); if ( $theme_json_file !== $parent_theme_json_file && is_readable( $parent_theme_json_file ) ) { $parent_theme_json_data = static::read_json_file( $parent_theme_json_file ); $parent_theme_json_data = static::translate( $parent_theme_json_data, $wp_theme->parent()->get( 'TextDomain' ) ); $parent_theme = new WP_Theme_JSON( $parent_theme_json_data ); /* * Merge the child theme.json into the parent theme.json. * The child theme takes precedence over the parent. */ $parent_theme->merge( static::$theme ); static::$theme = $parent_theme; } } } if ( ! $options['with_supports'] ) { return static::$theme; } /* * We want the presets and settings declared in theme.json * to override the ones declared via theme supports. * So we take theme supports, transform it to theme.json shape * and merge the static::$theme upon that. */ $theme_support_data = WP_Theme_JSON::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); if ( ! wp_theme_has_theme_json() ) { /* * Unlike block themes, classic themes without a theme.json disable * default presets when custom preset theme support is added. This * behavior can be overridden by using the corresponding default * preset theme support. */ $theme_support_data['settings']['color']['defaultPalette'] = ! isset( $theme_support_data['settings']['color']['palette'] ) || current_theme_supports( 'default-color-palette' ); $theme_support_data['settings']['color']['defaultGradients'] = ! isset( $theme_support_data['settings']['color']['gradients'] ) || current_theme_supports( 'default-gradient-presets' ); $theme_support_data['settings']['typography']['defaultFontSizes'] = ! isset( $theme_support_data['settings']['typography']['fontSizes'] ) || current_theme_supports( 'default-font-sizes' ); $theme_support_data['settings']['spacing']['defaultSpacingSizes'] = ! isset( $theme_support_data['settings']['spacing']['spacingSizes'] ) || current_theme_supports( 'default-spacing-sizes' ); /* * Shadow presets are explicitly disabled for classic themes until a * decision is made for whether the default presets should match the * other presets or if they should be disabled by default in classic * themes. See https://github.com/WordPress/gutenberg/issues/59989. */ $theme_support_data['settings']['shadow']['defaultPresets'] = false; // Allow themes to enable link color setting via theme_support. if ( current_theme_supports( 'link-color' ) ) { $theme_support_data['settings']['color']['link'] = true; } // Allow themes to enable all border settings via theme_support. if ( current_theme_supports( 'border' ) ) { $theme_support_data['settings']['border']['color'] = true; $theme_support_data['settings']['border']['radius'] = true; $theme_support_data['settings']['border']['style'] = true; $theme_support_data['settings']['border']['width'] = true; } // Allow themes to enable appearance tools via theme_support. if ( current_theme_supports( 'appearance-tools' ) ) { $theme_support_data['settings']['appearanceTools'] = true; } } $with_theme_supports = new WP_Theme_JSON( $theme_support_data ); $with_theme_supports->merge( static::$theme ); return $with_theme_supports; } /** * Gets the styles for blocks from the block.json file. * * @since 6.1.0 * * @return WP_Theme_JSON */ public static function get_block_data() { $registry = WP_Block_Type_Registry::get_instance(); $blocks = $registry->get_all_registered(); if ( null !== static::$blocks && static::has_same_registered_blocks( 'blocks' ) ) { return static::$blocks; } $config = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ); foreach ( $blocks as $block_name => $block_type ) { if ( isset( $block_type->supports['__experimentalStyle'] ) ) { $config['styles']['blocks'][ $block_name ] = static::remove_json_comments( $block_type->supports['__experimentalStyle'] ); } if ( isset( $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ) && ! isset( $config['styles']['blocks'][ $block_name ]['spacing']['blockGap'] ) ) { /* * Ensure an empty placeholder value exists for the block, if it provides a default blockGap value. * The real blockGap value to be used will be determined when the styles are rendered for output. */ $config['styles']['blocks'][ $block_name ]['spacing']['blockGap'] = null; } } /** * Filters the data provided by the blocks for global styles & settings. * * @since 6.1.0 * * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. */ $theme_json = apply_filters( 'wp_theme_json_data_blocks', new WP_Theme_JSON_Data( $config, 'blocks' ) ); /* * Backward compatibility for extenders returning a WP_Theme_JSON_Data * compatible class that is not a WP_Theme_JSON_Data object. */ if ( $theme_json instanceof WP_Theme_JSON_Data ) { static::$blocks = $theme_json->get_theme_json(); } else { $config = $theme_json->get_data(); static::$blocks = new WP_Theme_JSON( $config, 'blocks' ); } return static::$blocks; } /** * When given an array, this will remove any keys with the name `//`. * * @since 6.1.0 * * @param array $input_array The array to filter. * @return array The filtered array. */ private static function remove_json_comments( $input_array ) { unset( $input_array['//'] ); foreach ( $input_array as $k => $v ) { if ( is_array( $v ) ) { $input_array[ $k ] = static::remove_json_comments( $v ); } } return $input_array; } /** * Returns the custom post type that contains the user's origin config * for the active theme or an empty array if none are found. * * This can also create and return a new draft custom post type. * * @since 5.9.0 * * @param WP_Theme $theme The theme object. If empty, it * defaults to the active theme. * @param bool $create_post Optional. Whether a new custom post * type should be created if none are * found. Default false. * @param array $post_status_filter Optional. Filter custom post type by * post status. Default `array( 'publish' )`, * so it only fetches published posts. * @return array Custom Post Type for the user's origin config. */ public static function get_user_data_from_wp_global_styles( $theme, $create_post = false, $post_status_filter = array( 'publish' ) ) { if ( ! $theme instanceof WP_Theme ) { $theme = wp_get_theme(); } /* * Bail early if the theme does not support a theme.json. * * Since wp_theme_has_theme_json() only supports the active * theme, the extra condition for whether $theme is the active theme is * present here. */ if ( $theme->get_stylesheet() === get_stylesheet() && ! wp_theme_has_theme_json() ) { return array(); } $user_cpt = array(); $post_type_filter = 'wp_global_styles'; $stylesheet = $theme->get_stylesheet(); $args = array( 'posts_per_page' => 1, 'orderby' => 'date', 'order' => 'desc', 'post_type' => $post_type_filter, 'post_status' => $post_status_filter, 'ignore_sticky_posts' => true, 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'tax_query' => array( array( 'taxonomy' => 'wp_theme', 'field' => 'name', 'terms' => $stylesheet, ), ), ); $global_style_query = new WP_Query(); $recent_posts = $global_style_query->query( $args ); if ( count( $recent_posts ) === 1 ) { $user_cpt = get_object_vars( $recent_posts[0] ); } elseif ( $create_post ) { $cpt_post_id = wp_insert_post( array( 'post_content' => '{"version": ' . WP_Theme_JSON::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }', 'post_status' => 'publish', 'post_title' => 'Custom Styles', // Do not make string translatable, see https://core.trac.wordpress.org/ticket/54518. 'post_type' => $post_type_filter, 'post_name' => sprintf( 'wp-global-styles-%s', urlencode( $stylesheet ) ), 'tax_input' => array( 'wp_theme' => array( $stylesheet ), ), ), true ); if ( ! is_wp_error( $cpt_post_id ) ) { $user_cpt = get_object_vars( get_post( $cpt_post_id ) ); } } return $user_cpt; } /** * Returns the user's origin config. * * @since 5.9.0 * @since 6.6.0 The 'isGlobalStylesUserThemeJSON' flag is left on the user data. * Register the block style variations coming from the user data. * * @return WP_Theme_JSON Entity that holds styles for user data. */ public static function get_user_data() { if ( null !== static::$user && static::has_same_registered_blocks( 'user' ) ) { return static::$user; } $config = array(); $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme() ); if ( array_key_exists( 'post_content', $user_cpt ) ) { $decoded_data = json_decode( $user_cpt['post_content'], true ); $json_decoding_error = json_last_error(); if ( JSON_ERROR_NONE !== $json_decoding_error ) { wp_trigger_error( __METHOD__, 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() ); /** * Filters the data provided by the user for global styles & settings. * * @since 6.1.0 * * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. */ $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) ); /* * Backward compatibility for extenders returning a WP_Theme_JSON_Data * compatible class that is not a WP_Theme_JSON_Data object. */ if ( $theme_json instanceof WP_Theme_JSON_Data ) { return $theme_json->get_theme_json(); } else { $config = $theme_json->get_data(); return new WP_Theme_JSON( $config, 'custom' ); } } /* * Very important to verify that the flag isGlobalStylesUserThemeJSON is true. * If it's not true then the content was not escaped and is not safe. */ if ( is_array( $decoded_data ) && isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && $decoded_data['isGlobalStylesUserThemeJSON'] ) { unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); $config = $decoded_data; } } /** This filter is documented in wp-includes/class-wp-theme-json-resolver.php */ $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) ); /* * Backward compatibility for extenders returning a WP_Theme_JSON_Data * compatible class that is not a WP_Theme_JSON_Data object. */ if ( $theme_json instanceof WP_Theme_JSON_Data ) { static::$user = $theme_json->get_theme_json(); } else { $config = $theme_json->get_data(); static::$user = new WP_Theme_JSON( $config, 'custom' ); } return static::$user; } /** * Returns the data merged from multiple origins. * * There are four sources of data (origins) for a site: * * - default => WordPress * - blocks => each one of the blocks provides data for itself * - theme => the active theme * - custom => data provided by the user * * The custom's has higher priority than the theme's, the theme's higher than blocks', * and block's higher than default's. * * Unlike the getters * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_core_data/ get_core_data}, * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_theme_data/ get_theme_data}, * and {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_user_data/ get_user_data}, * this method returns data after it has been merged with the previous origins. * This means that if the same piece of data is declared in different origins * (default, blocks, theme, custom), the last origin overrides the previous. * * For example, if the user has set a background color * for the paragraph block, and the theme has done it as well, * the user preference wins. * * @since 5.8.0 * @since 5.9.0 Added user data, removed the `$settings` parameter, * added the `$origin` parameter. * @since 6.1.0 Added block data and generation of spacingSizes array. * @since 6.2.0 Changed ' $origin' parameter values to 'default', 'blocks', 'theme' or 'custom'. * * @param string $origin Optional. To what level should we merge data: 'default', 'blocks', 'theme' or 'custom'. * 'custom' is used as default value as well as fallback value if the origin is unknown. * @return WP_Theme_JSON */ public static function get_merged_data( $origin = 'custom' ) { if ( is_array( $origin ) ) { _deprecated_argument( __FUNCTION__, '5.9.0' ); } $result = new WP_Theme_JSON(); $result->merge( static::get_core_data() ); if ( 'default' === $origin ) { return $result; } $result->merge( static::get_block_data() ); if ( 'blocks' === $origin ) { return $result; } $result->merge( static::get_theme_data() ); if ( 'theme' === $origin ) { return $result; } $result->merge( static::get_user_data() ); return $result; } /** * Returns the ID of the custom post type * that stores user data. * * @since 5.9.0 * * @return integer|null */ public static function get_user_global_styles_post_id() { if ( null !== static::$user_custom_post_type_id ) { return static::$user_custom_post_type_id; } $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme(), true ); if ( array_key_exists( 'ID', $user_cpt ) ) { static::$user_custom_post_type_id = $user_cpt['ID']; } return static::$user_custom_post_type_id; } /** * Determines whether the active theme has a theme.json file. * * @since 5.8.0 * @since 5.9.0 Added a check in the parent theme. * @deprecated 6.2.0 Use wp_theme_has_theme_json() instead. * * @return bool */ public static function theme_has_support() { _deprecated_function( __METHOD__, '6.2.0', 'wp_theme_has_theme_json()' ); return wp_theme_has_theme_json(); } /** * Builds the path to the given file and checks that it is readable. * * If it isn't, returns an empty string, otherwise returns the whole file path. * * @since 5.8.0 * @since 5.9.0 Adapted to work with child themes, added the `$template` argument. * * @param string $file_name Name of the file. * @param bool $template Optional. Use template theme directory. Default false. * @return string The whole file path or empty if the file doesn't exist. */ protected static function get_file_path_from_theme( $file_name, $template = false ) { $path = $template ? get_template_directory() : get_stylesheet_directory(); $candidate = $path . '/' . $file_name; return is_readable( $candidate ) ? $candidate : ''; } /** * Cleans the cached data so it can be recalculated. * * @since 5.8.0 * @since 5.9.0 Added the `$user`, `$user_custom_post_type_id`, * and `$i18n_schema` variables to reset. * @since 6.1.0 Added the `$blocks` and `$blocks_cache` variables * to reset. */ public static function clean_cached_data() { static::$core = null; static::$blocks = null; static::$blocks_cache = array( 'core' => array(), 'blocks' => array(), 'theme' => array(), 'user' => array(), ); static::$theme = null; static::$user = null; static::$user_custom_post_type_id = null; static::$i18n_schema = null; } /** * Returns an array of all nested JSON files within a given directory. * * @since 6.2.0 * * @param string $dir The directory to recursively iterate and list files of. * @return array The merged array. */ private static function recursively_iterate_json( $dir ) { $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir ) ); $nested_json_files = iterator_to_array( new RegexIterator( $nested_files, '/^.+\.json$/i', RecursiveRegexIterator::GET_MATCH ) ); return $nested_json_files; } /** * Determines if a supplied style variation matches the provided scope. * * For backwards compatibility, if a variation does not define any scope * related property, e.g. `blockTypes`, it is assumed to be a theme style * variation. * * @since 6.6.0 * * @param array $variation Theme.json shaped style variation object. * @param string $scope Scope to check e.g. theme, block etc. * @return boolean */ private static function style_variation_has_scope( $variation, $scope ) { if ( 'block' === $scope ) { return isset( $variation['blockTypes'] ); } if ( 'theme' === $scope ) { return ! isset( $variation['blockTypes'] ); } return false; } /** * Returns the style variations defined by the theme. * * @since 6.0.0 * @since 6.2.0 Returns parent theme variations if theme is a child. * @since 6.6.0 Added configurable scope parameter to allow filtering * theme.json partial files by the scope to which they * can be applied e.g. theme vs block etc. * Added basic caching for read theme.json partial files. * * @param string $scope The scope or type of style variation to retrieve e.g. theme, block etc. * @return array */ public static function get_style_variations( $scope = 'theme' ) { $variation_files = array(); $variations = array(); $base_directory = get_stylesheet_directory() . '/styles'; $template_directory = get_template_directory() . '/styles'; if ( is_dir( $base_directory ) ) { $variation_files = static::recursively_iterate_json( $base_directory ); } if ( is_dir( $template_directory ) && $template_directory !== $base_directory ) { $variation_files_parent = static::recursively_iterate_json( $template_directory ); // If the child and parent variation file basename are the same, only include the child theme's. foreach ( $variation_files_parent as $parent_path => $parent ) { foreach ( $variation_files as $child_path => $child ) { if ( basename( $parent_path ) === basename( $child_path ) ) { unset( $variation_files_parent[ $parent_path ] ); } } } $variation_files = array_merge( $variation_files, $variation_files_parent ); } ksort( $variation_files ); foreach ( $variation_files as $path => $file ) { $decoded_file = self::read_json_file( $path ); if ( is_array( $decoded_file ) && static::style_variation_has_scope( $decoded_file, $scope ) ) { $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); $variation = ( new WP_Theme_JSON( $translated ) )->get_raw_data(); if ( empty( $variation['title'] ) ) { $variation['title'] = basename( $path, '.json' ); } $variations[] = $variation; } } return $variations; } /** * Resolves relative paths in theme.json styles to theme absolute paths * and returns them in an array that can be embedded * as the value of `_link` object in REST API responses. * * @since 6.6.0 * @since 6.7.0 Resolve relative paths in block styles. * * @param WP_Theme_JSON $theme_json A theme json instance. * @return array An array of resolved paths. */ public static function get_resolved_theme_uris( $theme_json ) { $resolved_theme_uris = array(); if ( ! $theme_json instanceof WP_Theme_JSON ) { return $resolved_theme_uris; } $theme_json_data = $theme_json->get_raw_data(); /* * The same file convention when registering web fonts. * See: WP_Font_Face_Resolver::to_theme_file_uri. */ $placeholder = 'file:./'; // Top level styles. $background_image_url = $theme_json_data['styles']['background']['backgroundImage']['url'] ?? null; if ( isset( $background_image_url ) && is_string( $background_image_url ) && // Skip if the src doesn't start with the placeholder, as there's nothing to replace. str_starts_with( $background_image_url, $placeholder ) ) { $file_type = wp_check_filetype( $background_image_url ); $src_url = str_replace( $placeholder, '', $background_image_url ); $resolved_theme_uri = array( 'name' => $background_image_url, 'href' => sanitize_url( get_theme_file_uri( $src_url ) ), 'target' => 'styles.background.backgroundImage.url', ); if ( isset( $file_type['type'] ) ) { $resolved_theme_uri['type'] = $file_type['type']; } $resolved_theme_uris[] = $resolved_theme_uri; } // Block styles. if ( ! empty( $theme_json_data['styles']['blocks'] ) ) { foreach ( $theme_json_data['styles']['blocks'] as $block_name => $block_styles ) { if ( ! isset( $block_styles['background']['backgroundImage']['url'] ) ) { continue; } $background_image_url = $block_styles['background']['backgroundImage']['url']; if ( is_string( $background_image_url ) && // Skip if the src doesn't start with the placeholder, as there's nothing to replace. str_starts_with( $background_image_url, $placeholder ) ) { $file_type = wp_check_filetype( $background_image_url ); $src_url = str_replace( $placeholder, '', $background_image_url ); $resolved_theme_uri = array( 'name' => $background_image_url, 'href' => sanitize_url( get_theme_file_uri( $src_url ) ), 'target' => "styles.blocks.{$block_name}.background.backgroundImage.url", ); if ( isset( $file_type['type'] ) ) { $resolved_theme_uri['type'] = $file_type['type']; } $resolved_theme_uris[] = $resolved_theme_uri; } } } return $resolved_theme_uris; } /** * Resolves relative paths in theme.json styles to theme absolute paths * and merges them with incoming theme JSON. * * @since 6.6.0 * * @param WP_Theme_JSON $theme_json A theme json instance. * @return WP_Theme_JSON Theme merged with resolved paths, if any found. */ public static function resolve_theme_file_uris( $theme_json ) { $resolved_urls = static::get_resolved_theme_uris( $theme_json ); if ( empty( $resolved_urls ) ) { return $theme_json; } $resolved_theme_json_data = $theme_json->get_raw_data(); foreach ( $resolved_urls as $resolved_url ) { $path = explode( '.', $resolved_url['target'] ); _wp_array_set( $resolved_theme_json_data, $path, $resolved_url['href'] ); } return new WP_Theme_JSON( $resolved_theme_json_data ); } /** * Adds variations sourced from block style variations files to the supplied theme.json data. * * @since 6.6.0 * * @param array $data Array following the theme.json specification. * @param array $variations Shared block style variations. * @return array Theme json data including shared block style variation definitions. */ private static function inject_variations_from_block_style_variation_files( $data, $variations ) { if ( empty( $variations ) ) { return $data; } foreach ( $variations as $variation ) { if ( empty( $variation['styles'] ) || empty( $variation['blockTypes'] ) ) { continue; } $variation_name = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ); foreach ( $variation['blockTypes'] as $block_type ) { // First, override partial styles with any top-level styles. $top_level_data = $data['styles']['variations'][ $variation_name ] ?? array(); if ( ! empty( $top_level_data ) ) { $variation['styles'] = array_replace_recursive( $variation['styles'], $top_level_data ); } // Then, override styles so far with any block-level styles. $block_level_data = $data['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); if ( ! empty( $block_level_data ) ) { $variation['styles'] = array_replace_recursive( $variation['styles'], $block_level_data ); } $path = array( 'styles', 'blocks', $block_type, 'variations', $variation_name ); _wp_array_set( $data, $path, $variation['styles'] ); } } return $data; } /** * Adds variations sourced from the block styles registry to the supplied theme.json data. * * @since 6.6.0 * * @param array $data Array following the theme.json specification. * @return array Theme json data including shared block style variation definitions. */ private static function inject_variations_from_block_styles_registry( $data ) { $registry = WP_Block_Styles_Registry::get_instance(); $styles = $registry->get_all_registered(); foreach ( $styles as $block_type => $variations ) { foreach ( $variations as $variation_name => $variation ) { if ( empty( $variation['style_data'] ) ) { continue; } // First, override registry styles with any top-level styles. $top_level_data = $data['styles']['variations'][ $variation_name ] ?? array(); if ( ! empty( $top_level_data ) ) { $variation['style_data'] = array_replace_recursive( $variation['style_data'], $top_level_data ); } // Then, override styles so far with any block-level styles. $block_level_data = $data['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); if ( ! empty( $block_level_data ) ) { $variation['style_data'] = array_replace_recursive( $variation['style_data'], $block_level_data ); } $path = array( 'styles', 'blocks', $block_type, 'variations', $variation_name ); _wp_array_set( $data, $path, $variation['style_data'] ); } } return $data; } } https-migration-20260605225728.php 0000666 00000011205 15226762357 0012024 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260605174541.php 0000666 00000013341 15226762357 0012010 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } speculative-loading.php 0000666 00000020630 15226762357 0011240 0 ustar 00 <?php /** * Speculative loading functions. * * @package WordPress * @subpackage Speculative Loading * @since 6.8.0 */ /** * Returns the speculation rules configuration. * * @since 6.8.0 * * @return array<string, string>|null Associative array with 'mode' and 'eagerness' keys, or null if speculative * loading is disabled. */ function wp_get_speculation_rules_configuration(): ?array { // By default, speculative loading is only enabled for sites with pretty permalinks when no user is logged in. if ( ! is_user_logged_in() && get_option( 'permalink_structure' ) ) { $config = array( 'mode' => 'auto', 'eagerness' => 'auto', ); } else { $config = null; } /** * Filters the way that speculation rules are configured. * * The Speculation Rules API is a web API that allows to automatically prefetch or prerender certain URLs on the * page, which can lead to near-instant page load times. This is also referred to as speculative loading. * * There are two aspects to the configuration: * * The "mode" (whether to "prefetch" or "prerender" URLs). * * The "eagerness" (whether to speculatively load URLs in an "eager", "moderate", or "conservative" way). * * By default, the speculation rules configuration is decided by WordPress Core ("auto"). This filter can be used * to force a certain configuration, which could for instance load URLs more or less eagerly. * * For logged-in users or for sites that are not configured to use pretty permalinks, the default value is `null`, * indicating that speculative loading is entirely disabled. * * @since 6.8.0 * @see https://developer.chrome.com/docs/web-platform/prerender-pages * * @param array<string, string>|null $config Associative array with 'mode' and 'eagerness' keys, or `null`. The * default value for both of the keys is 'auto'. Other possible values * for 'mode' are 'prefetch' and 'prerender'. Other possible values for * 'eagerness' are 'eager', 'moderate', and 'conservative'. The value * `null` is used to disable speculative loading entirely. */ $config = apply_filters( 'wp_speculation_rules_configuration', $config ); // Allow the value `null` to indicate that speculative loading is disabled. if ( null === $config ) { return null; } // Sanitize the configuration and replace 'auto' with current defaults. $default_mode = 'prefetch'; $default_eagerness = 'conservative'; if ( ! is_array( $config ) ) { return array( 'mode' => $default_mode, 'eagerness' => $default_eagerness, ); } if ( ! isset( $config['mode'] ) || 'auto' === $config['mode'] || ! WP_Speculation_Rules::is_valid_mode( $config['mode'] ) ) { $config['mode'] = $default_mode; } if ( ! isset( $config['eagerness'] ) || 'auto' === $config['eagerness'] || ! WP_Speculation_Rules::is_valid_eagerness( $config['eagerness'] ) || // 'immediate' is a valid eagerness, but for safety WordPress does not allow it for document-level rules. 'immediate' === $config['eagerness'] ) { $config['eagerness'] = $default_eagerness; } return array( 'mode' => $config['mode'], 'eagerness' => $config['eagerness'], ); } /** * Returns the full speculation rules data based on the configuration. * * Plugins with features that rely on frontend URLs to exclude from prefetching or prerendering should use the * {@see 'wp_speculation_rules_href_exclude_paths'} filter to ensure those URL patterns are excluded. * * Additional speculation rules other than the default rule from WordPress Core can be provided by using the * {@see 'wp_load_speculation_rules'} action and amending the passed WP_Speculation_Rules object. * * @since 6.8.0 * @access private * * @return WP_Speculation_Rules|null Object representing the speculation rules to use, or null if speculative loading * is disabled in the current context. */ function wp_get_speculation_rules(): ?WP_Speculation_Rules { $configuration = wp_get_speculation_rules_configuration(); if ( null === $configuration ) { return null; } $mode = $configuration['mode']; $eagerness = $configuration['eagerness']; $prefixer = new WP_URL_Pattern_Prefixer(); $base_href_exclude_paths = array( $prefixer->prefix_path_pattern( '/wp-*.php', 'site' ), $prefixer->prefix_path_pattern( '/wp-admin/*', 'site' ), $prefixer->prefix_path_pattern( '/*', 'uploads' ), $prefixer->prefix_path_pattern( '/*', 'content' ), $prefixer->prefix_path_pattern( '/*', 'plugins' ), $prefixer->prefix_path_pattern( '/*', 'template' ), $prefixer->prefix_path_pattern( '/*', 'stylesheet' ), ); /* * If pretty permalinks are enabled, exclude any URLs with query parameters. * Otherwise, exclude specifically the URLs with a `_wpnonce` query parameter or any other query parameter * containing the word `nonce`. */ if ( get_option( 'permalink_structure' ) ) { $base_href_exclude_paths[] = $prefixer->prefix_path_pattern( '/*\\?(.+)', 'home' ); } else { $base_href_exclude_paths[] = $prefixer->prefix_path_pattern( '/*\\?*(^|&)*nonce*=*', 'home' ); } /** * Filters the paths for which speculative loading should be disabled. * * All paths should start in a forward slash, relative to the root document. The `*` can be used as a wildcard. * If the WordPress site is in a subdirectory, the exclude paths will automatically be prefixed as necessary. * * Note that WordPress always excludes certain path patterns such as `/wp-login.php` and `/wp-admin/*`, and those * cannot be modified using the filter. * * @since 6.8.0 * * @param string[] $href_exclude_paths Additional path patterns to disable speculative loading for. * @param string $mode Mode used to apply speculative loading. Either 'prefetch' or 'prerender'. */ $href_exclude_paths = (array) apply_filters( 'wp_speculation_rules_href_exclude_paths', array(), $mode ); // Ensure that: // 1. There are no duplicates. // 2. The base paths cannot be removed. // 3. The array has sequential keys (i.e. array_is_list()). $href_exclude_paths = array_values( array_unique( array_merge( $base_href_exclude_paths, array_map( static function ( string $href_exclude_path ) use ( $prefixer ): string { return $prefixer->prefix_path_pattern( $href_exclude_path ); }, $href_exclude_paths ) ) ) ); $speculation_rules = new WP_Speculation_Rules(); $main_rule_conditions = array( // Include any URLs within the same site. array( 'href_matches' => $prefixer->prefix_path_pattern( '/*' ), ), // Except for excluded paths. array( 'not' => array( 'href_matches' => $href_exclude_paths, ), ), // Also exclude rel=nofollow links, as certain plugins use that on their links that perform an action. array( 'not' => array( 'selector_matches' => 'a[rel~="nofollow"]', ), ), // Also exclude links that are explicitly marked to opt out, either directly or via a parent element. array( 'not' => array( 'selector_matches' => ".no-{$mode}, .no-{$mode} a", ), ), ); // If using 'prerender', also exclude links that opt out of 'prefetch' because it's part of 'prerender'. if ( 'prerender' === $mode ) { $main_rule_conditions[] = array( 'not' => array( 'selector_matches' => '.no-prefetch, .no-prefetch a', ), ); } $speculation_rules->add_rule( $mode, 'main', array( 'source' => 'document', 'where' => array( 'and' => $main_rule_conditions, ), 'eagerness' => $eagerness, ) ); /** * Fires when speculation rules data is loaded, allowing to amend the rules. * * @since 6.8.0 * * @param WP_Speculation_Rules $speculation_rules Object representing the speculation rules to use. */ do_action( 'wp_load_speculation_rules', $speculation_rules ); return $speculation_rules; } /** * Prints the speculation rules. * * For browsers that do not support speculation rules yet, the `script[type="speculationrules"]` tag will be ignored. * * @since 6.8.0 * @access private */ function wp_print_speculation_rules(): void { $speculation_rules = wp_get_speculation_rules(); if ( null === $speculation_rules ) { return; } wp_print_inline_script_tag( (string) wp_json_encode( $speculation_rules, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ), array( 'type' => 'speculationrules' ) ); } https-migration.php 0000666 00000011205 15226762357 0010430 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260622024947-20260626151929.php 0000666 00000011205 15226762357 0013423 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260608061205.php 0000666 00000011205 15226762357 0012013 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } wp-diff-20260605042839.php 0000666 00000001437 15226762357 0010235 0 ustar 00 <?php /** * WordPress Diff bastard child of old MediaWiki Diff Formatter. * * Basically all that remains is the table structure and some method names. * * @package WordPress * @subpackage Diff */ // Don't load directly. if ( ! defined( 'ABSPATH' ) ) { die( '-1' ); } if ( ! class_exists( 'Text_Diff', false ) ) { /** Text_Diff class */ require ABSPATH . WPINC . '/Text/Diff.php'; /** Text_Diff_Renderer class */ require ABSPATH . WPINC . '/Text/Diff/Renderer.php'; /** Text_Diff_Renderer_inline class */ require ABSPATH . WPINC . '/Text/Diff/Renderer/inline.php'; /** Text_Exception class */ require ABSPATH . WPINC . '/Text/Exception.php'; } require ABSPATH . WPINC . '/class-wp-text-diff-renderer-table.php'; require ABSPATH . WPINC . '/class-wp-text-diff-renderer-inline.php'; https-detection-20260622015131.php 0000666 00000013341 15226762357 0011774 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-hook.php 0000666 00000040442 15226762357 0007773 0 ustar 00 <?php /** * Plugin API: WP_Hook class * * @package WordPress * @subpackage Plugin * @since 4.7.0 */ /** * Core class used to implement action and filter hook functionality. * * @since 4.7.0 * * @see Iterator * @see ArrayAccess */ #[AllowDynamicProperties] final class WP_Hook implements Iterator, ArrayAccess { /** * Hook callbacks. * * @since 4.7.0 * @var array */ public $callbacks = array(); /** * Priorities list. * * @since 6.4.0 * @var array */ protected $priorities = array(); /** * The priority keys of actively running iterations of a hook. * * @since 4.7.0 * @var array */ private $iterations = array(); /** * The current priority of actively running iterations of a hook. * * @since 4.7.0 * @var array */ private $current_priority = array(); /** * Number of levels this hook can be recursively called. * * @since 4.7.0 * @var int */ private $nesting_level = 0; /** * Flag for if we're currently doing an action, rather than a filter. * * @since 4.7.0 * @var bool */ private $doing_action = false; /** * Adds a callback function to a filter hook. * * @since 4.7.0 * * @param string $hook_name The name of the filter to add the callback to. * @param callable $callback The callback to be run when the filter is applied. * @param int $priority The order in which the functions associated with a particular filter * are executed. Lower numbers correspond with earlier execution, * and functions with the same priority are executed in the order * in which they were added to the filter. * @param int $accepted_args The number of arguments the function accepts. */ public function add_filter( $hook_name, $callback, $priority, $accepted_args ) { if ( null === $priority ) { $priority = 0; } $idx = _wp_filter_build_unique_id( $hook_name, $callback, $priority ); $priority_existed = isset( $this->callbacks[ $priority ] ); $this->callbacks[ $priority ][ $idx ] = array( 'function' => $callback, 'accepted_args' => (int) $accepted_args, ); // If we're adding a new priority to the list, put them back in sorted order. if ( ! $priority_existed && count( $this->callbacks ) > 1 ) { ksort( $this->callbacks, SORT_NUMERIC ); } $this->priorities = array_keys( $this->callbacks ); if ( $this->nesting_level > 0 ) { $this->resort_active_iterations( $priority, $priority_existed ); } } /** * Handles resetting callback priority keys mid-iteration. * * @since 4.7.0 * * @param false|int $new_priority Optional. The priority of the new filter being added. Default false, * for no priority being added. * @param bool $priority_existed Optional. Flag for whether the priority already existed before the new * filter was added. Default false. */ private function resort_active_iterations( $new_priority = false, $priority_existed = false ) { $new_priorities = $this->priorities; // If there are no remaining hooks, clear out all running iterations. if ( ! $new_priorities ) { foreach ( $this->iterations as $index => $iteration ) { $this->iterations[ $index ] = $new_priorities; } return; } $min = min( $new_priorities ); foreach ( $this->iterations as $index => &$iteration ) { $current = current( $iteration ); // If we're already at the end of this iteration, just leave the array pointer where it is. if ( false === $current ) { continue; } $iteration = $new_priorities; if ( $current < $min ) { array_unshift( $iteration, $current ); continue; } while ( current( $iteration ) < $current ) { if ( false === next( $iteration ) ) { break; } } // If we have a new priority that didn't exist, but ::apply_filters() or ::do_action() thinks it's the current priority... if ( $new_priority === $this->current_priority[ $index ] && ! $priority_existed ) { /* * ...and the new priority is the same as what $this->iterations thinks is the previous * priority, we need to move back to it. */ if ( false === current( $iteration ) ) { // If we've already moved off the end of the array, go back to the last element. $prev = end( $iteration ); } else { // Otherwise, just go back to the previous element. $prev = prev( $iteration ); } if ( false === $prev ) { // Start of the array. Reset, and go about our day. reset( $iteration ); } elseif ( $new_priority !== $prev ) { // Previous wasn't the same. Move forward again. next( $iteration ); } } } unset( $iteration ); } /** * Removes a callback function from a filter hook. * * @since 4.7.0 * * @param string $hook_name The filter hook to which the function to be removed is hooked. * @param callable|string|array $callback The callback to be removed from running when the filter is applied. * This method can be called unconditionally to speculatively remove * a callback that may or may not exist. * @param int $priority The exact priority used when adding the original filter callback. * @return bool Whether the callback existed before it was removed. */ public function remove_filter( $hook_name, $callback, $priority ) { if ( null === $priority ) { $priority = 0; } $function_key = _wp_filter_build_unique_id( $hook_name, $callback, $priority ); $exists = isset( $function_key, $this->callbacks[ $priority ][ $function_key ] ); if ( $exists ) { unset( $this->callbacks[ $priority ][ $function_key ] ); if ( ! $this->callbacks[ $priority ] ) { unset( $this->callbacks[ $priority ] ); $this->priorities = array_keys( $this->callbacks ); if ( $this->nesting_level > 0 ) { $this->resort_active_iterations(); } } } return $exists; } /** * Checks if a specific callback has been registered for this hook. * * When using the `$callback` argument, this function may return a non-boolean value * that evaluates to false (e.g. 0), so use the `===` operator for testing the return value. * * @since 4.7.0 * @since 6.9.0 Added the `$priority` parameter. * * @param string $hook_name Optional. The name of the filter hook. Default empty. * @param callable|string|array|false $callback Optional. The callback to check for. * This method can be called unconditionally to speculatively check * a callback that may or may not exist. Default false. * @param int|false $priority Optional. The specific priority at which to check for the callback. * Default false. * @return bool|int If `$callback` is omitted, returns boolean for whether the hook has * anything registered. When checking a specific function, the priority * of that hook is returned, or false if the function is not attached. * If `$callback` and `$priority` are both provided, a boolean is returned * for whether the specific function is registered at that priority. */ public function has_filter( $hook_name = '', $callback = false, $priority = false ) { if ( false === $callback ) { return $this->has_filters(); } $function_key = _wp_filter_build_unique_id( $hook_name, $callback, false ); if ( ! $function_key ) { return false; } if ( is_int( $priority ) ) { return isset( $this->callbacks[ $priority ][ $function_key ] ); } foreach ( $this->callbacks as $callback_priority => $callbacks ) { if ( isset( $callbacks[ $function_key ] ) ) { return $callback_priority; } } return false; } /** * Checks if any callbacks have been registered for this hook. * * @since 4.7.0 * * @return bool True if callbacks have been registered for the current hook, otherwise false. */ public function has_filters() { foreach ( $this->callbacks as $callbacks ) { if ( $callbacks ) { return true; } } return false; } /** * Removes all callbacks from the current filter. * * @since 4.7.0 * * @param int|false $priority Optional. The priority number to remove. Default false. */ public function remove_all_filters( $priority = false ) { if ( ! $this->callbacks ) { return; } if ( false === $priority ) { $this->callbacks = array(); $this->priorities = array(); } elseif ( isset( $this->callbacks[ $priority ] ) ) { unset( $this->callbacks[ $priority ] ); $this->priorities = array_keys( $this->callbacks ); } if ( $this->nesting_level > 0 ) { $this->resort_active_iterations(); } } /** * Calls the callback functions that have been added to a filter hook. * * @since 4.7.0 * * @param mixed $value The value to filter. * @param array $args Additional parameters to pass to the callback functions. * This array is expected to include $value at index 0. * @return mixed The filtered value after all hooked functions are applied to it. */ public function apply_filters( $value, $args ) { if ( ! $this->callbacks ) { return $value; } $nesting_level = $this->nesting_level++; $this->iterations[ $nesting_level ] = $this->priorities; $num_args = count( $args ); do { $this->current_priority[ $nesting_level ] = current( $this->iterations[ $nesting_level ] ); $priority = $this->current_priority[ $nesting_level ]; foreach ( $this->callbacks[ $priority ] as $the_ ) { if ( ! $this->doing_action ) { $args[0] = $value; } // Avoid the array_slice() if possible. if ( 0 === $the_['accepted_args'] ) { $value = call_user_func( $the_['function'] ); } elseif ( $the_['accepted_args'] >= $num_args ) { $value = call_user_func_array( $the_['function'], $args ); } else { $value = call_user_func_array( $the_['function'], array_slice( $args, 0, $the_['accepted_args'] ) ); } } } while ( false !== next( $this->iterations[ $nesting_level ] ) ); unset( $this->iterations[ $nesting_level ] ); unset( $this->current_priority[ $nesting_level ] ); --$this->nesting_level; return $value; } /** * Calls the callback functions that have been added to an action hook. * * @since 4.7.0 * * @param array $args Parameters to pass to the callback functions. */ public function do_action( $args ) { $this->doing_action = true; $this->apply_filters( '', $args ); // If there are recursive calls to the current action, we haven't finished it until we get to the last one. if ( ! $this->nesting_level ) { $this->doing_action = false; } } /** * Processes the functions hooked into the 'all' hook. * * @since 4.7.0 * * @param array $args Arguments to pass to the hook callbacks. Passed by reference. */ public function do_all_hook( &$args ) { $nesting_level = $this->nesting_level++; $this->iterations[ $nesting_level ] = $this->priorities; do { $priority = current( $this->iterations[ $nesting_level ] ); foreach ( $this->callbacks[ $priority ] as $the_ ) { call_user_func_array( $the_['function'], $args ); } } while ( false !== next( $this->iterations[ $nesting_level ] ) ); unset( $this->iterations[ $nesting_level ] ); --$this->nesting_level; } /** * Return the current priority level of the currently running iteration of the hook. * * @since 4.7.0 * * @return int|false If the hook is running, return the current priority level. * If it isn't running, return false. */ public function current_priority() { if ( false === current( $this->iterations ) ) { return false; } return current( current( $this->iterations ) ); } /** * Normalizes filters set up before WordPress has initialized to WP_Hook objects. * * The `$filters` parameter should be an array keyed by hook name, with values * containing either: * * - A `WP_Hook` instance * - An array of callbacks keyed by their priorities * * Examples: * * $filters = array( * 'wp_fatal_error_handler_enabled' => array( * 10 => array( * array( * 'accepted_args' => 0, * 'function' => function() { * return false; * }, * ), * ), * ), * ); * * @since 4.7.0 * * @param array $filters Filters to normalize. See documentation above for details. * @return WP_Hook[] Array of normalized filters. */ public static function build_preinitialized_hooks( $filters ) { /** @var WP_Hook[] $normalized */ $normalized = array(); foreach ( $filters as $hook_name => $callback_groups ) { if ( $callback_groups instanceof WP_Hook ) { $normalized[ $hook_name ] = $callback_groups; continue; } $hook = new WP_Hook(); // Loop through callback groups. foreach ( $callback_groups as $priority => $callbacks ) { // Loop through callbacks. foreach ( $callbacks as $cb ) { $hook->add_filter( $hook_name, $cb['function'], $priority, $cb['accepted_args'] ); } } $normalized[ $hook_name ] = $hook; } return $normalized; } /** * Determines whether an offset value exists. * * @since 4.7.0 * * @link https://www.php.net/manual/en/arrayaccess.offsetexists.php * * @param mixed $offset An offset to check for. * @return bool True if the offset exists, false otherwise. */ #[ReturnTypeWillChange] public function offsetExists( $offset ) { return isset( $this->callbacks[ $offset ] ); } /** * Retrieves a value at a specified offset. * * @since 4.7.0 * * @link https://www.php.net/manual/en/arrayaccess.offsetget.php * * @param mixed $offset The offset to retrieve. * @return mixed If set, the value at the specified offset, null otherwise. */ #[ReturnTypeWillChange] public function offsetGet( $offset ) { return isset( $this->callbacks[ $offset ] ) ? $this->callbacks[ $offset ] : null; } /** * Sets a value at a specified offset. * * @since 4.7.0 * * @link https://www.php.net/manual/en/arrayaccess.offsetset.php * * @param mixed $offset The offset to assign the value to. * @param mixed $value The value to set. */ #[ReturnTypeWillChange] public function offsetSet( $offset, $value ) { if ( is_null( $offset ) ) { $this->callbacks[] = $value; } else { $this->callbacks[ $offset ] = $value; } $this->priorities = array_keys( $this->callbacks ); } /** * Unsets a specified offset. * * @since 4.7.0 * * @link https://www.php.net/manual/en/arrayaccess.offsetunset.php * * @param mixed $offset The offset to unset. */ #[ReturnTypeWillChange] public function offsetUnset( $offset ) { unset( $this->callbacks[ $offset ] ); $this->priorities = array_keys( $this->callbacks ); } /** * Returns the current element. * * @since 4.7.0 * * @link https://www.php.net/manual/en/iterator.current.php * * @return array Of callbacks at current priority. */ #[ReturnTypeWillChange] public function current() { return current( $this->callbacks ); } /** * Moves forward to the next element. * * @since 4.7.0 * * @link https://www.php.net/manual/en/iterator.next.php * * @return array Of callbacks at next priority. */ #[ReturnTypeWillChange] public function next() { return next( $this->callbacks ); } /** * Returns the key of the current element. * * @since 4.7.0 * * @link https://www.php.net/manual/en/iterator.key.php * * @return mixed Returns current priority on success, or NULL on failure */ #[ReturnTypeWillChange] public function key() { return key( $this->callbacks ); } /** * Checks if current position is valid. * * @since 4.7.0 * * @link https://www.php.net/manual/en/iterator.valid.php * * @return bool Whether the current position is valid. */ #[ReturnTypeWillChange] public function valid() { return key( $this->callbacks ) !== null; } /** * Rewinds the Iterator to the first element. * * @since 4.7.0 * * @link https://www.php.net/manual/en/iterator.rewind.php */ #[ReturnTypeWillChange] public function rewind() { reset( $this->callbacks ); } } string.php 0000666 00000020233 15226762357 0006606 0 ustar 00 <?php /** * Parses unified or context diffs output from eg. the diff utility. * * Example: * <code> * $patch = file_get_contents('example.patch'); * $diff = new Text_Diff('string', array($patch)); * $renderer = new Text_Diff_Renderer_inline(); * echo $renderer->render($diff); * </code> * * Copyright 2005 Örjan Persson <o@42mm.org> * Copyright 2005-2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did * not receive this file, see https://opensource.org/license/lgpl-2-1/. * * @author Örjan Persson <o@42mm.org> * @package Text_Diff * @since 0.2.0 */ class Text_Diff_Engine_string { /** * Parses a unified or context diff. * * First param contains the whole diff and the second can be used to force * a specific diff type. If the second parameter is 'autodetect', the * diff will be examined to find out which type of diff this is. * * @param string $diff The diff content. * @param string $mode The diff mode of the content in $diff. One of * 'context', 'unified', or 'autodetect'. * * @return array List of all diff operations. */ function diff($diff, $mode = 'autodetect') { // Detect line breaks. $lnbr = "\n"; if (strpos($diff, "\r\n") !== false) { $lnbr = "\r\n"; } elseif (strpos($diff, "\r") !== false) { $lnbr = "\r"; } // Make sure we have a line break at the EOF. if (substr($diff, -strlen($lnbr)) != $lnbr) { $diff .= $lnbr; } if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { return PEAR::raiseError('Type of diff is unsupported'); } if ($mode == 'autodetect') { $context = strpos($diff, '***'); $unified = strpos($diff, '---'); if ($context === $unified) { return PEAR::raiseError('Type of diff could not be detected'); } elseif ($context === false || $unified === false) { $mode = $context !== false ? 'context' : 'unified'; } else { $mode = $context < $unified ? 'context' : 'unified'; } } // Split by new line and remove the diff header, if there is one. $diff = explode($lnbr, $diff); if (($mode == 'context' && strpos($diff[0], '***') === 0) || ($mode == 'unified' && strpos($diff[0], '---') === 0)) { array_shift($diff); array_shift($diff); } if ($mode == 'context') { return $this->parseContextDiff($diff); } else { return $this->parseUnifiedDiff($diff); } } /** * Parses an array containing the unified diff. * * @param array $diff Array of lines. * * @return array List of all diff operations. */ function parseUnifiedDiff($diff) { $edits = array(); $end = count($diff) - 1; for ($i = 0; $i < $end;) { $diff1 = array(); switch (substr($diff[$i], 0, 1)) { case ' ': do { $diff1[] = substr($diff[$i], 1); } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); $edits[] = new Text_Diff_Op_copy($diff1); break; case '+': // get all new lines do { $diff1[] = substr($diff[$i], 1); } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); $edits[] = new Text_Diff_Op_add($diff1); break; case '-': // get changed or removed lines $diff2 = array(); do { $diff1[] = substr($diff[$i], 1); } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); while ($i < $end && substr($diff[$i], 0, 1) == '+') { $diff2[] = substr($diff[$i++], 1); } if (count($diff2) == 0) { $edits[] = new Text_Diff_Op_delete($diff1); } else { $edits[] = new Text_Diff_Op_change($diff1, $diff2); } break; default: $i++; break; } } return $edits; } /** * Parses an array containing the context diff. * * @param array $diff Array of lines. * * @return array List of all diff operations. */ function parseContextDiff(&$diff) { $edits = array(); $i = $max_i = $j = $max_j = 0; $end = count($diff) - 1; while ($i < $end && $j < $end) { while ($i >= $max_i && $j >= $max_j) { // Find the boundaries of the diff output of the two files for ($i = $j; $i < $end && substr($diff[$i], 0, 3) == '***'; $i++); for ($max_i = $i; $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; $max_i++); for ($j = $max_i; $j < $end && substr($diff[$j], 0, 3) == '---'; $j++); for ($max_j = $j; $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; $max_j++); } // find what hasn't been changed $array = array(); while ($i < $max_i && $j < $max_j && strcmp($diff[$i], $diff[$j]) == 0) { $array[] = substr($diff[$i], 2); $i++; $j++; } while ($i < $max_i && ($max_j-$j) <= 1) { if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { break; } $array[] = substr($diff[$i++], 2); } while ($j < $max_j && ($max_i-$i) <= 1) { if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { break; } $array[] = substr($diff[$j++], 2); } if (count($array) > 0) { $edits[] = new Text_Diff_Op_copy($array); } if ($i < $max_i) { $diff1 = array(); switch (substr($diff[$i], 0, 1)) { case '!': $diff2 = array(); do { $diff1[] = substr($diff[$i], 2); if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { $diff2[] = substr($diff[$j++], 2); } } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); $edits[] = new Text_Diff_Op_change($diff1, $diff2); break; case '+': do { $diff1[] = substr($diff[$i], 2); } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); $edits[] = new Text_Diff_Op_add($diff1); break; case '-': do { $diff1[] = substr($diff[$i], 2); } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); $edits[] = new Text_Diff_Op_delete($diff1); break; } } if ($j < $max_j) { $diff2 = array(); switch (substr($diff[$j], 0, 1)) { case '+': do { $diff2[] = substr($diff[$j++], 2); } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); $edits[] = new Text_Diff_Op_add($diff2); break; case '-': do { $diff2[] = substr($diff[$j++], 2); } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); $edits[] = new Text_Diff_Op_delete($diff2); break; } } } return $edits; } } class-wp-customize-nav-menu-location-control.php 0000666 00000004373 15226762357 0016070 0 ustar 00 <?php /** * Customize API: WP_Customize_Nav_Menu_Location_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Menu Location Control Class. * * This custom control is only needed for JS. * * @since 4.3.0 * * @see WP_Customize_Control */ class WP_Customize_Nav_Menu_Location_Control extends WP_Customize_Control { /** * Control type. * * @since 4.3.0 * @var string */ public $type = 'nav_menu_location'; /** * Location ID. * * @since 4.3.0 * @var string */ public $location_id = ''; /** * Refresh the parameters passed to JavaScript via JSON. * * @since 4.3.0 * * @see WP_Customize_Control::to_json() */ public function to_json() { parent::to_json(); $this->json['locationId'] = $this->location_id; } /** * Render content just like a normal select control. * * @since 4.3.0 * @since 4.9.0 Added a button to create menus. */ public function render_content() { if ( empty( $this->choices ) ) { return; } $value_hidden_class = ''; $no_value_hidden_class = ''; if ( $this->value() ) { $value_hidden_class = ' hidden'; } else { $no_value_hidden_class = ' hidden'; } ?> <label> <?php if ( ! empty( $this->label ) ) : ?> <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span> <?php endif; ?> <?php if ( ! empty( $this->description ) ) : ?> <span class="description customize-control-description"><?php echo $this->description; ?></span> <?php endif; ?> <select <?php $this->link(); ?>> <?php foreach ( $this->choices as $value => $label ) : echo '<option value="' . esc_attr( $value ) . '"' . selected( $this->value(), $value, false ) . '>' . esc_html( $label ) . '</option>'; endforeach; ?> </select> </label> <button type="button" class="button-link create-menu<?php echo $value_hidden_class; ?>" data-location-id="<?php echo esc_attr( $this->location_id ); ?>" aria-label="<?php esc_attr_e( 'Create a menu for this location' ); ?>"><?php _e( '+ Create New Menu' ); ?></button> <button type="button" class="button-link edit-menu<?php echo $no_value_hidden_class; ?>" aria-label="<?php esc_attr_e( 'Edit selected menu' ); ?>"><?php _e( 'Edit Menu' ); ?></button> <?php } } https-detection-20260605133803.php 0000666 00000013341 15226762357 0012004 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260605104610.php 0000666 00000011205 15226762357 0012006 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-customize-image-control.php 0000666 00000002273 15226762357 0013613 0 ustar 00 <?php /** * Customize API: WP_Customize_Image_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Image Control class. * * @since 3.4.0 * * @see WP_Customize_Upload_Control */ class WP_Customize_Image_Control extends WP_Customize_Upload_Control { /** * Control type. * * @since 3.4.0 * @var string */ public $type = 'image'; /** * Media control mime type. * * @since 4.1.0 * @var string */ public $mime_type = 'image'; /** * @since 3.4.2 * @deprecated 4.1.0 */ public function prepare_control() {} /** * @since 3.4.0 * @deprecated 4.1.0 * * @param string $id * @param string $label * @param mixed $callback */ public function add_tab( $id, $label, $callback ) { _deprecated_function( __METHOD__, '4.1.0' ); } /** * @since 3.4.0 * @deprecated 4.1.0 * * @param string $id */ public function remove_tab( $id ) { _deprecated_function( __METHOD__, '4.1.0' ); } /** * @since 3.4.0 * @deprecated 4.1.0 * * @param string $url * @param string $thumbnail_url */ public function print_tab_image( $url, $thumbnail_url = null ) { _deprecated_function( __METHOD__, '4.1.0' ); } } class-wp-sidebar-block-editor-control.php 0000666 00000001256 15226762357 0014476 0 ustar 00 <?php /** * Customize API: WP_Sidebar_Block_Editor_Control class. * * @package WordPress * @subpackage Customize * @since 5.8.0 */ /** * Core class used to implement the widgets block editor control in the * customizer. * * @since 5.8.0 * * @see WP_Customize_Control */ class WP_Sidebar_Block_Editor_Control extends WP_Customize_Control { /** * The control type. * * @since 5.8.0 * * @var string */ public $type = 'sidebar_block_editor'; /** * Render the widgets block editor container. * * @since 5.8.0 */ public function render_content() { // Render an empty control. The JavaScript in // @wordpress/customize-widgets will do the rest. } } https-detection-20260605171209.php 0000666 00000013341 15226762357 0012006 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-widget-form-customize-control.php 0000666 00000005126 15226762357 0014755 0 ustar 00 <?php /** * Customize API: WP_Widget_Form_Customize_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Widget Form Customize Control class. * * @since 3.9.0 * * @see WP_Customize_Control */ class WP_Widget_Form_Customize_Control extends WP_Customize_Control { /** * Customize control type. * * @since 3.9.0 * @var string */ public $type = 'widget_form'; /** * Widget ID. * * @since 3.9.0 * @var string */ public $widget_id; /** * Widget ID base. * * @since 3.9.0 * @var string */ public $widget_id_base; /** * Sidebar ID. * * @since 3.9.0 * @var string */ public $sidebar_id; /** * Widget status. * * @since 3.9.0 * @var bool True if new, false otherwise. Default false. */ public $is_new = false; /** * Widget width. * * @since 3.9.0 * @var int */ public $width; /** * Widget height. * * @since 3.9.0 * @var int */ public $height; /** * Widget mode. * * @since 3.9.0 * @var bool True if wide, false otherwise. Default false. */ public $is_wide = false; /** * Gather control params for exporting to JavaScript. * * @since 3.9.0 * * @global array $wp_registered_widgets */ public function to_json() { global $wp_registered_widgets; parent::to_json(); $exported_properties = array( 'widget_id', 'widget_id_base', 'sidebar_id', 'width', 'height', 'is_wide' ); foreach ( $exported_properties as $key ) { $this->json[ $key ] = $this->$key; } // Get the widget_control and widget_content. require_once ABSPATH . 'wp-admin/includes/widgets.php'; $widget = $wp_registered_widgets[ $this->widget_id ]; if ( ! isset( $widget['params'][0] ) ) { $widget['params'][0] = array(); } $args = array( 'widget_id' => $widget['id'], 'widget_name' => $widget['name'], ); $args = wp_list_widget_controls_dynamic_sidebar( array( 0 => $args, 1 => $widget['params'][0], ) ); $widget_control_parts = $this->manager->widgets->get_widget_control_parts( $args ); $this->json['widget_control'] = $widget_control_parts['control']; $this->json['widget_content'] = $widget_control_parts['content']; } /** * Override render_content to be no-op since content is exported via to_json for deferred embedding. * * @since 3.9.0 */ public function render_content() {} /** * Whether the current widget is rendered on the page. * * @since 4.0.0 * * @return bool Whether the widget is rendered. */ public function active_callback() { return $this->manager->widgets->is_widget_rendered( $this->widget_id ); } } https-migration-20260605101402.php 0000666 00000011205 15226762357 0012002 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260606072822.php 0000666 00000013341 15226762357 0012010 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260605083511.php 0000666 00000013341 15226762357 0012004 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-error-20260621210742.php 0000666 00000016516 15226762357 0011551 0 ustar 00 <?php /** * WordPress Error API. * * @package WordPress */ /** * WordPress Error class. * * Container for checking for WordPress errors and error messages. Return * WP_Error and use is_wp_error() to check if this class is returned. Many * core WordPress functions pass this class in the event of an error and * if not handled properly will result in code errors. * * @since 2.1.0 */ #[AllowDynamicProperties] class WP_Error { /** * Stores the list of errors. * * @since 2.1.0 * @var array */ public $errors = array(); /** * Stores the most recently added data for each error code. * * @since 2.1.0 * @var array */ public $error_data = array(); /** * Stores previously added data added for error codes, oldest-to-newest by code. * * @since 5.6.0 * @var array[] */ protected $additional_data = array(); /** * Initializes the error. * * If `$code` is empty, the other parameters will be ignored. * When `$code` is not empty, `$message` will be used even if * it is empty. The `$data` parameter will be used only if it * is not empty. * * Though the class is constructed with a single error code and * message, multiple codes can be added using the `add()` method. * * @since 2.1.0 * * @param string|int $code Error code. * @param string $message Error message. * @param mixed $data Optional. Error data. Default empty string. */ public function __construct( $code = '', $message = '', $data = '' ) { if ( empty( $code ) ) { return; } $this->add( $code, $message, $data ); } /** * Retrieves all error codes. * * @since 2.1.0 * * @return array List of error codes, if available. */ public function get_error_codes() { if ( ! $this->has_errors() ) { return array(); } return array_keys( $this->errors ); } /** * Retrieves the first error code available. * * @since 2.1.0 * * @return string|int Empty string, if no error codes. */ public function get_error_code() { $codes = $this->get_error_codes(); if ( empty( $codes ) ) { return ''; } return $codes[0]; } /** * Retrieves all error messages, or the error messages for the given error code. * * @since 2.1.0 * * @param string|int $code Optional. Error code to retrieve the messages for. * Default empty string. * @return string[] Error strings on success, or empty array if there are none. */ public function get_error_messages( $code = '' ) { // Return all messages if no code specified. if ( empty( $code ) ) { $all_messages = array(); foreach ( (array) $this->errors as $code => $messages ) { $all_messages = array_merge( $all_messages, $messages ); } return $all_messages; } if ( isset( $this->errors[ $code ] ) ) { return $this->errors[ $code ]; } else { return array(); } } /** * Gets a single error message. * * This will get the first message available for the code. If no code is * given then the first code available will be used. * * @since 2.1.0 * * @param string|int $code Optional. Error code to retrieve the message for. * Default empty string. * @return string The error message. */ public function get_error_message( $code = '' ) { if ( empty( $code ) ) { $code = $this->get_error_code(); } $messages = $this->get_error_messages( $code ); if ( empty( $messages ) ) { return ''; } return $messages[0]; } /** * Retrieves the most recently added error data for an error code. * * @since 2.1.0 * * @param string|int $code Optional. Error code. Default empty string. * @return mixed Error data, if it exists. */ public function get_error_data( $code = '' ) { if ( empty( $code ) ) { $code = $this->get_error_code(); } if ( isset( $this->error_data[ $code ] ) ) { return $this->error_data[ $code ]; } } /** * Verifies if the instance contains errors. * * @since 5.1.0 * * @return bool If the instance contains errors. */ public function has_errors() { if ( ! empty( $this->errors ) ) { return true; } return false; } /** * Adds an error or appends an additional message to an existing error. * * @since 2.1.0 * * @param string|int $code Error code. * @param string $message Error message. * @param mixed $data Optional. Error data. Default empty string. */ public function add( $code, $message, $data = '' ) { $this->errors[ $code ][] = $message; if ( ! empty( $data ) ) { $this->add_data( $data, $code ); } /** * Fires when an error is added to a WP_Error object. * * @since 5.6.0 * * @param string|int $code Error code. * @param string $message Error message. * @param mixed $data Error data. Might be empty. * @param WP_Error $wp_error The WP_Error object. */ do_action( 'wp_error_added', $code, $message, $data, $this ); } /** * Adds data to an error with the given code. * * @since 2.1.0 * @since 5.6.0 Errors can now contain more than one item of error data. {@see WP_Error::$additional_data}. * * @param mixed $data Error data. * @param string|int $code Error code. */ public function add_data( $data, $code = '' ) { if ( empty( $code ) ) { $code = $this->get_error_code(); } if ( isset( $this->error_data[ $code ] ) ) { $this->additional_data[ $code ][] = $this->error_data[ $code ]; } $this->error_data[ $code ] = $data; } /** * Retrieves all error data for an error code in the order in which the data was added. * * @since 5.6.0 * * @param string|int $code Error code. * @return mixed[] Array of error data, if it exists. */ public function get_all_error_data( $code = '' ) { if ( empty( $code ) ) { $code = $this->get_error_code(); } $data = array(); if ( isset( $this->additional_data[ $code ] ) ) { $data = $this->additional_data[ $code ]; } if ( isset( $this->error_data[ $code ] ) ) { $data[] = $this->error_data[ $code ]; } return $data; } /** * Removes the specified error. * * This function removes all error messages associated with the specified * error code, along with any error data for that code. * * @since 4.1.0 * * @param string|int $code Error code. */ public function remove( $code ) { unset( $this->errors[ $code ] ); unset( $this->error_data[ $code ] ); unset( $this->additional_data[ $code ] ); } /** * Merges the errors in the given error object into this one. * * @since 5.6.0 * * @param WP_Error $error Error object to merge. */ public function merge_from( WP_Error $error ) { static::copy_errors( $error, $this ); } /** * Exports the errors in this object into the given one. * * @since 5.6.0 * * @param WP_Error $error Error object to export into. */ public function export_to( WP_Error $error ) { static::copy_errors( $this, $error ); } /** * Copies errors from one WP_Error instance to another. * * @since 5.6.0 * * @param WP_Error $from The WP_Error to copy from. * @param WP_Error $to The WP_Error to copy to. */ protected static function copy_errors( WP_Error $from, WP_Error $to ) { foreach ( $from->get_error_codes() as $code ) { foreach ( $from->get_error_messages( $code ) as $error_message ) { $to->add( $code, $error_message ); } foreach ( $from->get_all_error_data( $code ) as $data ) { $to->add_data( $data, $code ); } } } } https-migration-20260606062412.php 0000666 00000011205 15226762357 0012012 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260605120245.php 0000666 00000013341 15226762357 0012000 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-scripts.php 0000666 00000104060 15226762357 0010517 0 ustar 00 <?php /** * Dependencies API: WP_Scripts class * * @since 2.6.0 * * @package WordPress * @subpackage Dependencies */ /** * Core class used to register scripts. * * @since 2.1.0 * * @see WP_Dependencies */ class WP_Scripts extends WP_Dependencies { /** * Base URL for scripts. * * Full URL with trailing slash. * * @since 2.6.0 * @var string */ public $base_url; /** * URL of the content directory. * * @since 2.8.0 * @var string */ public $content_url; /** * Default version string for scripts. * * @since 2.6.0 * @var string */ public $default_version; /** * Holds handles of scripts which are enqueued in footer. * * @since 2.8.0 * @var array */ public $in_footer = array(); /** * Holds a list of script handles which will be concatenated. * * @since 2.8.0 * @var string */ public $concat = ''; /** * Holds a string which contains script handles and their version. * * @since 2.8.0 * @deprecated 3.4.0 * @var string */ public $concat_version = ''; /** * Whether to perform concatenation. * * @since 2.8.0 * @var bool */ public $do_concat = false; /** * Holds HTML markup of scripts and additional data if concatenation * is enabled. * * @since 2.8.0 * @var string */ public $print_html = ''; /** * Holds inline code if concatenation is enabled. * * @since 2.8.0 * @var string */ public $print_code = ''; /** * Holds a list of script handles which are not in the default directory * if concatenation is enabled. * * Unused in core. * * @since 2.8.0 * @var string */ public $ext_handles = ''; /** * Holds a string which contains handles and versions of scripts which * are not in the default directory if concatenation is enabled. * * Unused in core. * * @since 2.8.0 * @var string */ public $ext_version = ''; /** * List of default directories. * * @since 2.8.0 * @var array */ public $default_dirs; /** * Holds a mapping of dependents (as handles) for a given script handle. * Used to optimize recursive dependency tree checks. * * @since 6.3.0 * @var array<string, string[]> */ private $dependents_map = array(); /** * Holds a reference to the delayed (non-blocking) script loading strategies. * Used by methods that validate loading strategies. * * @since 6.3.0 * @var string[] */ private $delayed_strategies = array( 'defer', 'async' ); /** * Constructor. * * @since 2.6.0 */ public function __construct() { $this->init(); add_action( 'init', array( $this, 'init' ), 0 ); } /** * Initialize the class. * * @since 3.4.0 */ public function init() { /** * Fires when the WP_Scripts instance is initialized. * * @since 2.6.0 * * @param WP_Scripts $wp_scripts WP_Scripts instance (passed by reference). */ do_action_ref_array( 'wp_default_scripts', array( &$this ) ); } /** * Prints scripts. * * Prints the scripts passed to it or the print queue. Also prints all necessary dependencies. * * @since 2.1.0 * @since 2.8.0 Added the `$group` parameter. * * @param string|string[]|false $handles Optional. Scripts to be printed: queue (false), * single script (string), or multiple scripts (array of strings). * Default false. * @param int|false $group Optional. Group level: level (int), no groups (false). * Default false. * @return string[] Handles of scripts that have been printed. */ public function print_scripts( $handles = false, $group = false ) { return $this->do_items( $handles, $group ); } /** * Prints extra scripts of a registered script. * * @since 2.1.0 * @since 2.8.0 Added the `$display` parameter. * @deprecated 3.3.0 * * @see print_extra_script() * * @param string $handle The script's registered handle. * @param bool $display Optional. Whether to print the extra script * instead of just returning it. Default true. * @return bool|string|void Void if no data exists, extra scripts if `$display` is true, * true otherwise. */ public function print_scripts_l10n( $handle, $display = true ) { _deprecated_function( __FUNCTION__, '3.3.0', 'WP_Scripts::print_extra_script()' ); return $this->print_extra_script( $handle, $display ); } /** * Prints extra scripts of a registered script. * * @since 3.3.0 * * @param string $handle The script's registered handle. * @param bool $display Optional. Whether to print the extra script * instead of just returning it. Default true. * @return bool|string|void Void if no data exists, extra scripts if `$display` is true, * true otherwise. */ public function print_extra_script( $handle, $display = true ) { $output = $this->get_data( $handle, 'data' ); if ( ! $output ) { return; } /* * Do not print a sourceURL comment if concatenation is enabled. * * Extra scripts may be concatenated into a single script. * The line-based sourceURL comments may break concatenated scripts * and do not make sense when multiple scripts are joined together. */ if ( ! $this->do_concat ) { $output .= sprintf( "\n//# sourceURL=%s", rawurlencode( "{$handle}-js-extra" ) ); } if ( ! $display ) { return $output; } wp_print_inline_script_tag( $output, array( 'id' => "{$handle}-js-extra" ) ); return true; } /** * Checks whether all dependents of a given handle are in the footer. * * If there are no dependents, this is considered the same as if all dependents were in the footer. * * @since 6.4.0 * * @param string $handle Script handle. * @return bool Whether all dependents are in the footer. */ private function are_all_dependents_in_footer( $handle ) { foreach ( $this->get_dependents( $handle ) as $dep ) { if ( isset( $this->groups[ $dep ] ) && 0 === $this->groups[ $dep ] ) { return false; } } return true; } /** * Processes a script dependency. * * @since 2.6.0 * @since 2.8.0 Added the `$group` parameter. * * @see WP_Dependencies::do_item() * * @param string $handle The script's registered handle. * @param int|false $group Optional. Group level: level (int), no groups (false). * Default false. * @return bool True on success, false on failure. */ public function do_item( $handle, $group = false ) { if ( ! parent::do_item( $handle ) ) { return false; } if ( 0 === $group && $this->groups[ $handle ] > 0 ) { $this->in_footer[] = $handle; return false; } if ( false === $group && in_array( $handle, $this->in_footer, true ) ) { $this->in_footer = array_diff( $this->in_footer, (array) $handle ); } $obj = $this->registered[ $handle ]; if ( $obj->extra['conditional'] ?? false ) { return false; } if ( null === $obj->ver ) { $ver = ''; } else { $ver = $obj->ver ? $obj->ver : $this->default_version; } if ( isset( $this->args[ $handle ] ) ) { $ver = $ver ? $ver . '&' . $this->args[ $handle ] : $this->args[ $handle ]; } $src = $obj->src; $strategy = $this->get_eligible_loading_strategy( $handle ); $intended_strategy = (string) $this->get_data( $handle, 'strategy' ); if ( ! $this->is_delayed_strategy( $intended_strategy ) ) { $intended_strategy = ''; } /* * Move this script to the footer if: * 1. The script is in the header group. * 2. The current output is the header. * 3. The intended strategy is delayed. * 4. The actual strategy is not delayed. * 5. All dependent scripts are in the footer. */ if ( 0 === $group && 0 === $this->groups[ $handle ] && $intended_strategy && ! $this->is_delayed_strategy( $strategy ) && $this->are_all_dependents_in_footer( $handle ) ) { $this->in_footer[] = $handle; return false; } $before_script = $this->get_inline_script_tag( $handle, 'before' ); $after_script = $this->get_inline_script_tag( $handle, 'after' ); if ( $before_script || $after_script ) { $inline_script_tag = $before_script . $after_script; } else { $inline_script_tag = ''; } /* * Prevent concatenation of scripts if the text domain is defined * to ensure the dependency order is respected. */ $translations_stop_concat = ! empty( $obj->textdomain ); $translations = $this->print_translations( $handle, false ); if ( $translations ) { /* * The sourceURL comment is not included by WP_Scripts::print_translations() * when `$display` is `false` to prevent issues where the script tag contents are used * by extenders for other purposes, for example concatenated with other script content. * * Include the sourceURL comment here as it would be when printed directly. */ $source_url = rawurlencode( "{$handle}-js-translations" ); $translations .= "\n//# sourceURL={$source_url}"; $translations = wp_get_inline_script_tag( $translations, array( 'id' => "{$handle}-js-translations" ) ); } if ( $this->do_concat ) { /** * Filters the script loader source. * * @since 2.2.0 * * @param string $src Script loader source path. * @param string $handle Script handle. */ $filtered_src = apply_filters( 'script_loader_src', $src, $handle ); if ( $this->in_default_dir( $filtered_src ) && ( $before_script || $after_script || $translations_stop_concat || $this->is_delayed_strategy( $strategy ) ) ) { $this->do_concat = false; // Have to print the so-far concatenated scripts right away to maintain the right order. _print_scripts(); $this->reset(); } elseif ( $this->in_default_dir( $filtered_src ) ) { $this->print_code .= $this->print_extra_script( $handle, false ); $this->concat .= "$handle,"; $this->concat_version .= "$handle$ver"; return true; } else { $this->ext_handles .= "$handle,"; $this->ext_version .= "$handle$ver"; } } $this->print_extra_script( $handle ); // A single item may alias a set of items, by having dependencies, but no source. if ( ! $src ) { if ( $inline_script_tag ) { if ( $this->do_concat ) { $this->print_html .= $inline_script_tag; } else { echo $inline_script_tag; } } return true; } if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $this->content_url && str_starts_with( $src, $this->content_url ) ) ) { $src = $this->base_url . $src; } if ( ! empty( $ver ) ) { $src = add_query_arg( 'ver', $ver, $src ); } /** This filter is documented in wp-includes/class-wp-scripts.php */ $src = esc_url_raw( apply_filters( 'script_loader_src', $src, $handle ) ); if ( ! $src ) { return true; } $attr = array( 'src' => $src, 'id' => "{$handle}-js", ); if ( $strategy ) { $attr[ $strategy ] = true; } if ( $intended_strategy ) { $attr['data-wp-strategy'] = $intended_strategy; } // Determine fetchpriority. $original_fetchpriority = isset( $obj->extra['fetchpriority'] ) ? $obj->extra['fetchpriority'] : null; if ( null === $original_fetchpriority || ! $this->is_valid_fetchpriority( $original_fetchpriority ) ) { $original_fetchpriority = 'auto'; } $actual_fetchpriority = $this->get_highest_fetchpriority_with_dependents( $handle ); if ( null === $actual_fetchpriority ) { // If null, it's likely this script was not explicitly enqueued, so in this case use the original priority. $actual_fetchpriority = $original_fetchpriority; } if ( is_string( $actual_fetchpriority ) && 'auto' !== $actual_fetchpriority ) { $attr['fetchpriority'] = $actual_fetchpriority; } if ( $original_fetchpriority !== $actual_fetchpriority ) { $attr['data-wp-fetchpriority'] = $original_fetchpriority; } $tag = $translations . $before_script; $tag .= wp_get_script_tag( $attr ); $tag .= $after_script; /** * Filters the HTML script tag of an enqueued script. * * @since 4.1.0 * * @param string $tag The `<script>` tag for the enqueued script. * @param string $handle The script's registered handle. * @param string $src The script's source URL. */ $tag = apply_filters( 'script_loader_tag', $tag, $handle, $src ); if ( $this->do_concat ) { $this->print_html .= $tag; } else { echo $tag; } return true; } /** * Adds extra code to a registered script. * * @since 4.5.0 * * @param string $handle Name of the script to add the inline script to. * Must be lowercase. * @param string $data String containing the JavaScript to be added. * @param string $position Optional. Whether to add the inline script * before the handle or after. Default 'after'. * @return bool True on success, false on failure. */ public function add_inline_script( $handle, $data, $position = 'after' ) { if ( ! $data ) { return false; } if ( 'after' !== $position ) { $position = 'before'; } $script = (array) $this->get_data( $handle, $position ); $script[] = $data; return $this->add_data( $handle, $position, $script ); } /** * Prints inline scripts registered for a specific handle. * * @since 4.5.0 * @deprecated 6.3.0 Use methods get_inline_script_tag() or get_inline_script_data() instead. * * @param string $handle Name of the script to print inline scripts for. * Must be lowercase. * @param string $position Optional. Whether to add the inline script * before the handle or after. Default 'after'. * @param bool $display Optional. Whether to print the script tag * instead of just returning the script data. Default true. * @return string|false Script data on success, false otherwise. */ public function print_inline_script( $handle, $position = 'after', $display = true ) { _deprecated_function( __METHOD__, '6.3.0', 'WP_Scripts::get_inline_script_data() or WP_Scripts::get_inline_script_tag()' ); $output = $this->get_inline_script_data( $handle, $position ); if ( empty( $output ) ) { return false; } if ( $display ) { echo $this->get_inline_script_tag( $handle, $position ); } return $output; } /** * Gets data for inline scripts registered for a specific handle. * * @since 6.3.0 * * @param string $handle Name of the script to get data for. * Must be lowercase. * @param string $position Optional. Whether to add the inline script * before the handle or after. Default 'after'. * @return string Inline script, which may be empty string. */ public function get_inline_script_data( $handle, $position = 'after' ) { $data = $this->get_data( $handle, $position ); if ( empty( $data ) || ! is_array( $data ) ) { return ''; } /* * Print sourceURL comment regardless of concatenation. * * Inline scripts prevent scripts from being concatenated, so * sourceURL comments are safe to print for inline scripts. */ $data[] = sprintf( '//# sourceURL=%s', rawurlencode( "{$handle}-js-{$position}" ) ); return trim( implode( "\n", $data ), "\n" ); } /** * Gets tags for inline scripts registered for a specific handle. * * @since 6.3.0 * * @param string $handle Name of the script to get associated inline script tag for. * Must be lowercase. * @param string $position Optional. Whether to get tag for inline * scripts in the before or after position. Default 'after'. * @return string Inline script, which may be empty string. */ public function get_inline_script_tag( $handle, $position = 'after' ) { $js = $this->get_inline_script_data( $handle, $position ); if ( empty( $js ) ) { return ''; } $id = "{$handle}-js-{$position}"; return wp_get_inline_script_tag( $js, compact( 'id' ) ); } /** * Localizes a script, only if the script has already been added. * * @since 2.1.0 * * @param string $handle Name of the script to attach data to. * @param string $object_name Name of the variable that will contain the data. * @param array $l10n Array of data to localize. * @return bool True on success, false on failure. */ public function localize( $handle, $object_name, $l10n ) { if ( 'jquery' === $handle ) { $handle = 'jquery-core'; } if ( is_array( $l10n ) && isset( $l10n['l10n_print_after'] ) ) { // back compat, preserve the code in 'l10n_print_after' if present. $after = $l10n['l10n_print_after']; unset( $l10n['l10n_print_after'] ); } if ( ! is_array( $l10n ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: 1: $l10n, 2: wp_add_inline_script() */ __( 'The %1$s parameter must be an array. To pass arbitrary data to scripts, use the %2$s function instead.' ), '<code>$l10n</code>', '<code>wp_add_inline_script()</code>' ), '5.7.0' ); if ( false === $l10n ) { // This should really not be needed, but is necessary for backward compatibility. $l10n = array( $l10n ); } } if ( is_string( $l10n ) ) { $l10n = html_entity_decode( $l10n, ENT_QUOTES, 'UTF-8' ); } elseif ( is_array( $l10n ) ) { foreach ( $l10n as $key => $value ) { if ( ! is_scalar( $value ) ) { continue; } $l10n[ $key ] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8' ); } } $script = "var $object_name = " . wp_json_encode( $l10n, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';'; if ( ! empty( $after ) ) { $script .= "\n$after;"; } $data = $this->get_data( $handle, 'data' ); if ( ! empty( $data ) ) { $script = "$data\n$script"; } return $this->add_data( $handle, 'data', $script ); } /** * Sets handle group. * * @since 2.8.0 * * @see WP_Dependencies::set_group() * * @param string $handle Name of the item. Should be unique. * @param bool $recursion Internal flag that calling function was called recursively. * @param int|false $group Optional. Group level: level (int), no groups (false). * Default false. * @return bool Not already in the group or a lower group. */ public function set_group( $handle, $recursion, $group = false ) { if ( isset( $this->registered[ $handle ]->args ) && 1 === $this->registered[ $handle ]->args ) { $calculated_group = 1; } else { $calculated_group = (int) $this->get_data( $handle, 'group' ); } if ( false !== $group && $calculated_group > $group ) { $calculated_group = $group; } return parent::set_group( $handle, $recursion, $calculated_group ); } /** * Sets a translation textdomain. * * @since 5.0.0 * @since 5.1.0 The `$domain` parameter was made optional. * * @param string $handle Name of the script to register a translation domain to. * @param string $domain Optional. Text domain. Default 'default'. * @param string $path Optional. The full file path to the directory containing translation files. * @return bool True if the text domain was registered, false if not. */ public function set_translations( $handle, $domain = 'default', $path = '' ) { if ( ! isset( $this->registered[ $handle ] ) ) { return false; } /** @var \_WP_Dependency $obj */ $obj = $this->registered[ $handle ]; if ( ! in_array( 'wp-i18n', $obj->deps, true ) ) { $obj->deps[] = 'wp-i18n'; } return $obj->set_translations( $domain, $path ); } /** * Prints translations set for a specific handle. * * @since 5.0.0 * * @param string $handle Name of the script to add the inline script to. * Must be lowercase. * @param bool $display Optional. Whether to print the script * instead of just returning it. Default true. * @return string|false Script on success, false otherwise. */ public function print_translations( $handle, $display = true ) { if ( ! isset( $this->registered[ $handle ] ) || empty( $this->registered[ $handle ]->textdomain ) ) { return false; } $domain = $this->registered[ $handle ]->textdomain; $path = ''; if ( isset( $this->registered[ $handle ]->translations_path ) ) { $path = $this->registered[ $handle ]->translations_path; } $json_translations = load_script_textdomain( $handle, $domain, $path ); if ( ! $json_translations ) { return false; } $output = <<<JS ( function( domain, translations ) { var localeData = translations.locale_data[ domain ] || translations.locale_data.messages; localeData[""].domain = domain; wp.i18n.setLocaleData( localeData, domain ); } )( "{$domain}", {$json_translations} ); JS; if ( $display ) { $source_url = rawurlencode( "{$handle}-js-translations" ); $output .= "\n//# sourceURL={$source_url}"; wp_print_inline_script_tag( $output, array( 'id' => "{$handle}-js-translations" ) ); } return $output; } /** * Determines script dependencies. * * @since 2.1.0 * * @see WP_Dependencies::all_deps() * * @param string|string[] $handles Item handle (string) or item handles (array of strings). * @param bool $recursion Optional. Internal flag that function is calling itself. * Default false. * @param int|false $group Optional. Group level: level (int), no groups (false). * Default false. * @return bool True on success, false on failure. */ public function all_deps( $handles, $recursion = false, $group = false ) { $result = parent::all_deps( $handles, $recursion, $group ); if ( ! $recursion ) { /** * Filters the list of script dependencies left to print. * * @since 2.3.0 * * @param string[] $to_do An array of script dependency handles. */ $this->to_do = apply_filters( 'print_scripts_array', $this->to_do ); } return $result; } /** * Processes items and dependencies for the head group. * * @since 2.8.0 * * @see WP_Dependencies::do_items() * * @return string[] Handles of items that have been processed. */ public function do_head_items() { $this->do_items( false, 0 ); return $this->done; } /** * Processes items and dependencies for the footer group. * * @since 2.8.0 * * @see WP_Dependencies::do_items() * * @return string[] Handles of items that have been processed. */ public function do_footer_items() { $this->do_items( false, 1 ); return $this->done; } /** * Whether a handle's source is in a default directory. * * @since 2.8.0 * * @param string $src The source of the enqueued script. * @return bool True if found, false if not. */ public function in_default_dir( $src ) { if ( ! $this->default_dirs ) { return true; } if ( str_starts_with( $src, '/' . WPINC . '/js/l10n' ) ) { return false; } foreach ( (array) $this->default_dirs as $test ) { if ( str_starts_with( $src, $test ) ) { return true; } } return false; } /** * This overrides the add_data method from WP_Dependencies, to support normalizing of $args. * * @since 6.3.0 * * @param string $handle Name of the item. Should be unique. * @param string $key The data key. * @param mixed $value The data value. * @return bool True on success, false on failure. */ public function add_data( $handle, $key, $value ) { if ( ! isset( $this->registered[ $handle ] ) ) { return false; } if ( 'conditional' === $key ) { // If a dependency is declared by a conditional script, remove it. $this->registered[ $handle ]->deps = array(); } if ( 'strategy' === $key ) { if ( ! empty( $value ) && ! $this->is_delayed_strategy( $value ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: 1: $strategy, 2: $handle */ __( 'Invalid strategy `%1$s` defined for `%2$s` during script registration.' ), $value, $handle ), '6.3.0' ); return false; } elseif ( ! $this->registered[ $handle ]->src && $this->is_delayed_strategy( $value ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: 1: $strategy, 2: $handle */ __( 'Cannot supply a strategy `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ), $value, $handle ), '6.3.0' ); return false; } } elseif ( 'fetchpriority' === $key ) { if ( empty( $value ) ) { $value = 'auto'; } if ( ! $this->is_valid_fetchpriority( $value ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: 1: $fetchpriority, 2: $handle */ __( 'Invalid fetchpriority `%1$s` defined for `%2$s` during script registration.' ), is_string( $value ) ? $value : gettype( $value ), $handle ), '6.9.0' ); return false; } elseif ( ! $this->registered[ $handle ]->src ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: 1: $fetchpriority, 2: $handle */ __( 'Cannot supply a fetchpriority `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ), is_string( $value ) ? $value : gettype( $value ), $handle ), '6.9.0' ); return false; } } return parent::add_data( $handle, $key, $value ); } /** * Gets all dependents of a script. * * This is not recursive. * * @since 6.3.0 * * @param string $handle The script handle. * @return string[] Script handles. */ private function get_dependents( $handle ) { // Check if dependents map for the handle in question is present. If so, use it. if ( isset( $this->dependents_map[ $handle ] ) ) { return $this->dependents_map[ $handle ]; } $dependents = array(); // Iterate over all registered scripts, finding dependents of the script passed to this method. foreach ( $this->registered as $registered_handle => $args ) { if ( in_array( $handle, $args->deps, true ) ) { $dependents[] = $registered_handle; } } // Add the handles dependents to the map to ease future lookups. $this->dependents_map[ $handle ] = $dependents; return $dependents; } /** * Checks if the strategy passed is a valid delayed (non-blocking) strategy. * * @since 6.3.0 * * @param string|mixed $strategy The strategy to check. * @return bool True if $strategy is one of the delayed strategies, otherwise false. */ private function is_delayed_strategy( $strategy ): bool { return in_array( $strategy, $this->delayed_strategies, true ); } /** * Checks if the provided fetchpriority is valid. * * @since 6.9.0 * * @param string|mixed $priority Fetch priority. * @return bool Whether valid fetchpriority. */ private function is_valid_fetchpriority( $priority ): bool { return in_array( $priority, array( 'auto', 'low', 'high' ), true ); } /** * Gets the best eligible loading strategy for a script. * * @since 6.3.0 * * @param string $handle The script handle. * @return string The best eligible loading strategy. */ private function get_eligible_loading_strategy( $handle ) { $intended_strategy = (string) $this->get_data( $handle, 'strategy' ); // Bail early if there is no intended strategy. if ( ! $intended_strategy ) { return ''; } /* * If the intended strategy is 'defer', limit the initial list of eligible * strategies, since 'async' can fallback to 'defer', but not vice-versa. */ $initial_strategy = ( 'defer' === $intended_strategy ) ? array( 'defer' ) : null; $eligible_strategies = $this->filter_eligible_strategies( $handle, $initial_strategy ); // Return early once we know the eligible strategy is blocking. if ( empty( $eligible_strategies ) ) { return ''; } return in_array( 'async', $eligible_strategies, true ) ? 'async' : 'defer'; } /** * Filter the list of eligible loading strategies for a script. * * @since 6.3.0 * * @param string $handle The script handle. * @param string[]|null $eligible_strategies Optional. The list of strategies to filter. Default null. * @param array<string, true> $checked Optional. An array of already checked script handles, used to avoid recursive loops. * @param array<string, string[]> $stored_results Optional. An array of already computed eligible loading strategies by handle, used to increase performance in large dependency lists. * @return string[] A list of eligible loading strategies that could be used. */ private function filter_eligible_strategies( $handle, $eligible_strategies = null, $checked = array(), array &$stored_results = array() ) { if ( isset( $stored_results[ $handle ] ) ) { return $stored_results[ $handle ]; } // If no strategies are being passed, all strategies are eligible. if ( null === $eligible_strategies ) { $eligible_strategies = $this->delayed_strategies; } // If this handle was already checked, return early. if ( isset( $checked[ $handle ] ) ) { return $eligible_strategies; } // Mark this handle as checked. $checked[ $handle ] = true; // If this handle isn't registered, don't filter anything and return. if ( ! isset( $this->registered[ $handle ] ) ) { return $eligible_strategies; } // If the handle is not enqueued, don't filter anything and return. if ( ! $this->query( $handle, 'enqueued' ) ) { return $eligible_strategies; } $is_alias = (bool) ! $this->registered[ $handle ]->src; $intended_strategy = $this->get_data( $handle, 'strategy' ); // For non-alias handles, an empty intended strategy filters all strategies. if ( ! $is_alias && empty( $intended_strategy ) ) { return array(); } // Handles with inline scripts attached in the 'after' position cannot be delayed. if ( $this->has_inline_script( $handle, 'after' ) ) { return array(); } // If the intended strategy is 'defer', filter out 'async'. if ( 'defer' === $intended_strategy ) { $eligible_strategies = array( 'defer' ); } $dependents = $this->get_dependents( $handle ); // Recursively filter eligible strategies for dependents. foreach ( $dependents as $dependent ) { // Bail early once we know the eligible strategy is blocking. if ( empty( $eligible_strategies ) ) { return array(); } $eligible_strategies = $this->filter_eligible_strategies( $dependent, $eligible_strategies, $checked, $stored_results ); } $stored_results[ $handle ] = $eligible_strategies; return $eligible_strategies; } /** * Gets the highest fetch priority for a given script and all of its dependent scripts. * * @since 6.9.0 * @see self::filter_eligible_strategies() * @see WP_Script_Modules::get_highest_fetchpriority() * * @param string $handle Script module ID. * @param array<string, true> $checked Optional. An array of already checked script handles, used to avoid recursive loops. * @param array<string, string> $stored_results Optional. An array of already computed max priority by handle, used to increase performance in large dependency lists. * @return string|null Highest fetch priority for the script and its dependents. */ private function get_highest_fetchpriority_with_dependents( string $handle, array $checked = array(), array &$stored_results = array() ): ?string { if ( isset( $stored_results[ $handle ] ) ) { return $stored_results[ $handle ]; } // If there is a recursive dependency, return early. if ( isset( $checked[ $handle ] ) ) { return null; } // Mark this handle as checked to guard against infinite recursion. $checked[ $handle ] = true; // Abort if the script is not enqueued or a dependency of an enqueued script. if ( ! $this->query( $handle, 'enqueued' ) ) { return null; } $fetchpriority = $this->get_data( $handle, 'fetchpriority' ); if ( ! $this->is_valid_fetchpriority( $fetchpriority ) ) { $fetchpriority = 'auto'; } static $priorities = array( 'low', 'auto', 'high', ); $high_priority_index = count( $priorities ) - 1; $highest_priority_index = (int) array_search( $fetchpriority, $priorities, true ); if ( $highest_priority_index !== $high_priority_index ) { foreach ( $this->get_dependents( $handle ) as $dependent_handle ) { $dependent_priority = $this->get_highest_fetchpriority_with_dependents( $dependent_handle, $checked, $stored_results ); if ( is_string( $dependent_priority ) ) { $highest_priority_index = max( $highest_priority_index, (int) array_search( $dependent_priority, $priorities, true ) ); if ( $highest_priority_index === $high_priority_index ) { break; } } } } $stored_results[ $handle ] = $priorities[ $highest_priority_index ]; // @phpstan-ignore parameterByRef.type (We know the index is valid and that this will be a string.) return $priorities[ $highest_priority_index ]; } /** * Gets data for inline scripts registered for a specific handle. * * @since 6.3.0 * * @param string $handle Name of the script to get data for. Must be lowercase. * @param string $position The position of the inline script. * @return bool Whether the handle has an inline script (either before or after). */ private function has_inline_script( $handle, $position = null ) { if ( $position && in_array( $position, array( 'before', 'after' ), true ) ) { return (bool) $this->get_data( $handle, $position ); } return (bool) ( $this->get_data( $handle, 'before' ) || $this->get_data( $handle, 'after' ) ); } /** * Resets class properties. * * @since 2.8.0 */ public function reset() { $this->do_concat = false; $this->print_code = ''; $this->concat = ''; $this->concat_version = ''; $this->print_html = ''; $this->ext_version = ''; $this->ext_handles = ''; } /** * Gets a script-specific dependency warning message. * * @since 6.9.1 * * @param string $handle Script handle with missing dependencies. * @param string[] $missing_dependency_handles Missing dependency handles. * @return string Formatted, localized warning message. */ protected function get_dependency_warning_message( $handle, $missing_dependency_handles ) { return sprintf( /* translators: 1: Script handle, 2: List of missing dependency handles. */ __( 'The script with the handle "%1$s" was enqueued with dependencies that are not registered: %2$s.' ), $handle, implode( wp_get_list_item_separator(), $missing_dependency_handles ) ); } } https-migration-20260605081349.php 0000666 00000011205 15226762357 0012023 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-customize-sidebar-section.php 0000666 00000002043 15226762357 0014121 0 ustar 00 <?php /** * Customize API: WP_Customize_Sidebar_Section class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customizer section representing widget area (sidebar). * * @since 4.1.0 * * @see WP_Customize_Section */ class WP_Customize_Sidebar_Section extends WP_Customize_Section { /** * Type of this section. * * @since 4.1.0 * @var string */ public $type = 'sidebar'; /** * Unique identifier. * * @since 4.1.0 * @var string */ public $sidebar_id; /** * Gather the parameters passed to client JavaScript via JSON. * * @since 4.1.0 * * @return array The array to be exported to the client as JSON. */ public function json() { $json = parent::json(); $json['sidebarId'] = $this->sidebar_id; return $json; } /** * Whether the current sidebar is rendered on the page. * * @since 4.1.0 * * @return bool Whether sidebar is rendered. */ public function active_callback() { return $this->manager->widgets->is_sidebar_rendered( $this->sidebar_id ); } } class-wp-customize-site-icon-control.php 0000666 00000012053 15226762357 0014420 0 ustar 00 <?php /** * Customize API: WP_Customize_Site_Icon_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Site Icon control class. * * Used only for custom functionality in JavaScript. * * @since 4.3.0 * * @see WP_Customize_Cropped_Image_Control */ class WP_Customize_Site_Icon_Control extends WP_Customize_Cropped_Image_Control { /** * Control type. * * @since 4.3.0 * @var string */ public $type = 'site_icon'; /** * Constructor. * * @since 4.3.0 * * @see WP_Customize_Control::__construct() * * @param WP_Customize_Manager $manager Customizer bootstrap instance. * @param string $id Control ID. * @param array $args Optional. Arguments to override class property defaults. * See WP_Customize_Control::__construct() for information * on accepted arguments. Default empty array. */ public function __construct( $manager, $id, $args = array() ) { parent::__construct( $manager, $id, $args ); add_action( 'customize_controls_print_styles', 'wp_site_icon', 99 ); } /** * Renders a JS template for the content of the site icon control. * * @since 4.5.0 */ public function content_template() { ?> <# if ( data.label ) { #> <span class="customize-control-title">{{ data.label }}</span> <# } #> <# if ( data.attachment && data.attachment.id ) { #> <div class="attachment-media-view"> <# if ( data.attachment.sizes ) { #> <style> :root{ --site-icon-url: url( '{{ data.attachment.sizes.full ? data.attachment.sizes.full.url : data.attachment.url }}' ); } </style> <div class="site-icon-preview customizer"> <div class="direction-wrap"> <img src="{{ data.attachment.sizes.full ? data.attachment.sizes.full.url : data.attachment.url }}" class="app-icon-preview" alt="{{ data.attachment.alt ? wp.i18n.sprintf( <?php /* translators: %s: The selected image alt text. */ echo wp_json_encode( __( 'App icon preview: Current image: %s' ) ) ?> , data.attachment.alt ) : wp.i18n.sprintf( <?php /* translators: %s: The selected image filename. */ echo wp_json_encode( __( 'App icon preview: The current image has no alternative text. The file name is: %s' ) ); ?> , data.attachment.filename ) }}" /> <div class="site-icon-preview-browser"> <svg role="img" aria-hidden="true" fill="none" xmlns="http://www.w3.org/2000/svg" class="browser-buttons"><path fill-rule="evenodd" clip-rule="evenodd" d="M0 20a6 6 0 1 1 12 0 6 6 0 0 1-12 0Zm18 0a6 6 0 1 1 12 0 6 6 0 0 1-12 0Zm24-6a6 6 0 1 0 0 12 6 6 0 0 0 0-12Z" /></svg> <div class="site-icon-preview-tab"> <img src="{{ data.attachment.sizes.full ? data.attachment.sizes.full.url : data.attachment.url }}" class="browser-icon-preview" alt="{{ data.attachment.alt ? wp.i18n.sprintf( <?php /* translators: %s: The selected image alt text. */ echo wp_json_encode( __( 'Browser icon preview: Current image: %s' ) ); ?> , data.attachment.alt ) : wp.i18n.sprintf( <?php /* translators: %s: The selected image filename. */ echo wp_json_encode( __( 'Browser icon preview: The current image has no alternative text. The file name is: %s' ) ); ?> , data.attachment.filename ) }}" /> <div class="site-icon-preview-site-title" aria-hidden="true"><# print( '<?php echo esc_js( get_bloginfo( 'name' ) ); ?>' ) #></div> <svg role="img" aria-hidden="true" fill="none" xmlns="http://www.w3.org/2000/svg" class="close-button"> <path d="M12 13.0607L15.7123 16.773L16.773 15.7123L13.0607 12L16.773 8.28772L15.7123 7.22706L12 10.9394L8.28771 7.22705L7.22705 8.28771L10.9394 12L7.22706 15.7123L8.28772 16.773L12 13.0607Z" /> </svg> </div> </div> </div> </div> </div> <# } #> <div class="actions"> <# if ( data.canUpload ) { #> <button type="button" class="button remove-button"><?php echo $this->button_labels['remove']; ?></button> <button type="button" class="button upload-button"><?php echo $this->button_labels['change']; ?></button> <# } #> </div> </div> <# } else { #> <div class="attachment-media-view"> <# if ( data.canUpload ) { #> <button type="button" class="upload-button button"><?php echo $this->button_labels['site_icon']; ?></button> <# } #> <div class="actions"> <# if ( data.defaultAttachment ) { #> <button type="button" class="button default-button"><?php echo $this->button_labels['default']; ?></button> <# } #> </div> </div> <# } #> <# if ( data.description ) { #> <span class="description customize-control-description">{{{ data.description }}}</span> <# } #> <?php } } https-detection-20260605080438.php 0000666 00000013341 15226762357 0012011 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260613232255.php 0000666 00000013341 15226762357 0012004 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260605034026.php 0000666 00000013341 15226762357 0012001 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260605101210.php 0000666 00000011205 15226762357 0011777 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260605112630.php 0000666 00000013341 15226762357 0011777 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-comment.php 0000666 00000022335 15226762357 0010476 0 ustar 00 <?php /** * Comment API: WP_Comment class * * @package WordPress * @subpackage Comments * @since 4.4.0 */ /** * Core class used to organize comments as instantiated objects with defined members. * * @since 4.4.0 */ #[AllowDynamicProperties] final class WP_Comment { /** * Comment ID. * * A numeric string, for compatibility reasons. * * @since 4.4.0 * @var string */ public $comment_ID; /** * ID of the post the comment is associated with. * * A numeric string, for compatibility reasons. * * @since 4.4.0 * @var string */ public $comment_post_ID = '0'; /** * Comment author name. * * @since 4.4.0 * @var string */ public $comment_author = ''; /** * Comment author email address. * * @since 4.4.0 * @var string */ public $comment_author_email = ''; /** * Comment author URL. * * @since 4.4.0 * @var string */ public $comment_author_url = ''; /** * Comment author IP address (IPv4 format). * * @since 4.4.0 * @var string */ public $comment_author_IP = ''; /** * Comment date in YYYY-MM-DD HH:MM:SS format. * * @since 4.4.0 * @var string */ public $comment_date = '0000-00-00 00:00:00'; /** * Comment GMT date in YYYY-MM-DD HH::MM:SS format. * * @since 4.4.0 * @var string */ public $comment_date_gmt = '0000-00-00 00:00:00'; /** * Comment content. * * @since 4.4.0 * @var string */ public $comment_content; /** * Comment karma count. * * A numeric string, for compatibility reasons. * * @since 4.4.0 * @var string */ public $comment_karma = '0'; /** * Comment approval status. * * @since 4.4.0 * @var string */ public $comment_approved = '1'; /** * Comment author HTTP user agent. * * @since 4.4.0 * @var string */ public $comment_agent = ''; /** * Comment type. * * @since 4.4.0 * @since 5.5.0 Default value changed to `comment`. * @var string */ public $comment_type = 'comment'; /** * Parent comment ID. * * A numeric string, for compatibility reasons. * * @since 4.4.0 * @var string */ public $comment_parent = '0'; /** * Comment author ID. * * A numeric string, for compatibility reasons. * * @since 4.4.0 * @var string */ public $user_id = '0'; /** * Comment children. * * @since 4.4.0 * @var array */ protected $children; /** * Whether children have been populated for this comment object. * * @since 4.4.0 * @var bool */ protected $populated_children = false; /** * Post fields. * * @since 4.4.0 * @var array */ protected $post_fields = array( 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_excerpt', 'post_status', 'comment_status', 'ping_status', 'post_name', 'to_ping', 'pinged', 'post_modified', 'post_modified_gmt', 'post_content_filtered', 'post_parent', 'guid', 'menu_order', 'post_type', 'post_mime_type', 'comment_count' ); /** * Retrieves a WP_Comment instance. * * @since 4.4.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $id Comment ID. * @return WP_Comment|false Comment object, otherwise false. */ public static function get_instance( $id ) { global $wpdb; $comment_id = (int) $id; if ( ! $comment_id ) { return false; } $_comment = wp_cache_get( $comment_id, 'comment' ); if ( ! $_comment ) { $_comment = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->comments WHERE comment_ID = %d LIMIT 1", $comment_id ) ); if ( ! $_comment ) { return false; } wp_cache_add( $_comment->comment_ID, $_comment, 'comment' ); } return new WP_Comment( $_comment ); } /** * Constructor. * * Populates properties with object vars. * * @since 4.4.0 * * @param WP_Comment $comment Comment object. */ public function __construct( $comment ) { foreach ( get_object_vars( $comment ) as $key => $value ) { $this->$key = $value; } } /** * Converts object to array. * * @since 4.4.0 * * @return array Object as array. */ public function to_array() { return get_object_vars( $this ); } /** * Gets the children of a comment. * * @since 4.4.0 * * @param array $args { * Array of arguments used to pass to get_comments() and determine format. * * @type string $format Return value format. 'tree' for a hierarchical tree, 'flat' for a flattened array. * Default 'tree'. * @type string $status Comment status to limit results by. Accepts 'hold' (`comment_status=0`), * 'approve' (`comment_status=1`), 'all', or a custom comment status. * Default 'all'. * @type string $hierarchical Whether to include comment descendants in the results. * 'threaded' returns a tree, with each comment's children * stored in a `children` property on the `WP_Comment` object. * 'flat' returns a flat array of found comments plus their children. * Pass `false` to leave out descendants. * The parameter is ignored (forced to `false`) when `$fields` is 'ids' or 'counts'. * Accepts 'threaded', 'flat', or false. Default: 'threaded'. * @type string|array $orderby Comment status or array of statuses. To use 'meta_value' * or 'meta_value_num', `$meta_key` must also be defined. * To sort by a specific `$meta_query` clause, use that * clause's array key. Accepts 'comment_agent', * 'comment_approved', 'comment_author', * 'comment_author_email', 'comment_author_IP', * 'comment_author_url', 'comment_content', 'comment_date', * 'comment_date_gmt', 'comment_ID', 'comment_karma', * 'comment_parent', 'comment_post_ID', 'comment_type', * 'user_id', 'comment__in', 'meta_value', 'meta_value_num', * the value of $meta_key, and the array keys of * `$meta_query`. Also accepts false, an empty array, or * 'none' to disable `ORDER BY` clause. * } * @return WP_Comment[] Array of `WP_Comment` objects. */ public function get_children( $args = array() ) { $defaults = array( 'format' => 'tree', 'status' => 'all', 'hierarchical' => 'threaded', 'orderby' => '', ); $_args = wp_parse_args( $args, $defaults ); $_args['parent'] = $this->comment_ID; if ( is_null( $this->children ) ) { if ( $this->populated_children ) { $this->children = array(); } else { $this->children = get_comments( $_args ); } } if ( 'flat' === $_args['format'] ) { $children = array(); foreach ( $this->children as $child ) { $child_args = $_args; $child_args['format'] = 'flat'; // get_children() resets this value automatically. unset( $child_args['parent'] ); $children = array_merge( $children, array( $child ), $child->get_children( $child_args ) ); } } else { $children = $this->children; } return $children; } /** * Adds a child to the comment. * * Used by `WP_Comment_Query` when bulk-filling descendants. * * @since 4.4.0 * * @param WP_Comment $child Child comment. */ public function add_child( WP_Comment $child ) { $this->children[ $child->comment_ID ] = $child; } /** * Gets a child comment by ID. * * @since 4.4.0 * * @param int $child_id ID of the child. * @return WP_Comment|false Returns the comment object if found, otherwise false. */ public function get_child( $child_id ) { if ( isset( $this->children[ $child_id ] ) ) { return $this->children[ $child_id ]; } return false; } /** * Sets the 'populated_children' flag. * * This flag is important for ensuring that calling `get_children()` on a childless comment will not trigger * unneeded database queries. * * @since 4.4.0 * * @param bool $set Whether the comment's children have already been populated. */ public function populated_children( $set ) { $this->populated_children = (bool) $set; } /** * Determines whether a non-public property is set. * * If `$name` matches a post field, the comment post will be loaded and the post's value checked. * * @since 4.4.0 * * @param string $name Property to check if set. * @return bool Whether the property is set. */ public function __isset( $name ) { if ( in_array( $name, $this->post_fields, true ) && 0 !== (int) $this->comment_post_ID ) { $post = get_post( $this->comment_post_ID ); return property_exists( $post, $name ); } return false; } /** * Magic getter. * * If `$name` matches a post field, the comment post will be loaded and the post's value returned. * * @since 4.4.0 * * @param string $name Property name. * @return mixed */ public function __get( $name ) { if ( in_array( $name, $this->post_fields, true ) ) { $post = get_post( $this->comment_post_ID ); return $post->$name; } } } https-migration-20260605080458.php 0000666 00000011205 15226762357 0012023 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-customize-nav-menu-setting.php 0000666 00000044770 15226762357 0014264 0 ustar 00 <?php /** * Customize API: WP_Customize_Nav_Menu_Setting class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Setting to represent a nav_menu. * * Subclass of WP_Customize_Setting to represent a nav_menu taxonomy term, and * the IDs for the nav_menu_items associated with the nav menu. * * @since 4.3.0 * * @see wp_get_nav_menu_object() * @see WP_Customize_Setting */ class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting { const ID_PATTERN = '/^nav_menu\[(?P<id>-?\d+)\]$/'; const TAXONOMY = 'nav_menu'; const TYPE = 'nav_menu'; /** * Setting type. * * @since 4.3.0 * @var string */ public $type = self::TYPE; /** * Default setting value. * * @since 4.3.0 * @var array * * @see wp_get_nav_menu_object() */ public $default = array( 'name' => '', 'description' => '', 'parent' => 0, 'auto_add' => false, ); /** * Default transport. * * @since 4.3.0 * @var string */ public $transport = 'postMessage'; /** * The term ID represented by this setting instance. * * A negative value represents a placeholder ID for a new menu not yet saved. * * @since 4.3.0 * @var int */ public $term_id; /** * Previous (placeholder) term ID used before creating a new menu. * * This value will be exported to JS via the {@see 'customize_save_response'} filter * so that JavaScript can update the settings to refer to the newly-assigned * term ID. This value is always negative to indicate it does not refer to * a real term. * * @since 4.3.0 * @var int * * @see WP_Customize_Nav_Menu_Setting::update() * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() */ public $previous_term_id; /** * Whether or not update() was called. * * @since 4.3.0 * @var bool */ protected $is_updated = false; /** * Status for calling the update method, used in customize_save_response filter. * * See {@see 'customize_save_response'}. * * When status is inserted, the placeholder term ID is stored in `$previous_term_id`. * When status is error, the error is stored in `$update_error`. * * @since 4.3.0 * @var string updated|inserted|deleted|error * * @see WP_Customize_Nav_Menu_Setting::update() * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() */ public $update_status; /** * Any error object returned by wp_update_nav_menu_object() when setting is updated. * * @since 4.3.0 * @var WP_Error * * @see WP_Customize_Nav_Menu_Setting::update() * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() */ public $update_error; /** * Constructor. * * Any supplied $args override class property defaults. * * @since 4.3.0 * * @throws Exception If $id is not valid for this setting type. * * @param WP_Customize_Manager $manager Customizer bootstrap instance. * @param string $id A specific ID of the setting. * Can be a theme mod or option name. * @param array $args Optional. Setting arguments. */ public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) { if ( empty( $manager->nav_menus ) ) { throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' ); } if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) { throw new Exception( "Illegal widget setting ID: $id" ); } $this->term_id = (int) $matches['id']; parent::__construct( $manager, $id, $args ); } /** * Get the instance data for a given widget setting. * * @since 4.3.0 * * @see wp_get_nav_menu_object() * * @return array Instance data. */ public function value() { if ( $this->is_previewed && get_current_blog_id() === $this->_previewed_blog_id ) { $undefined = new stdClass(); // Symbol. $post_value = $this->post_value( $undefined ); if ( $undefined === $post_value ) { $value = $this->_original_value; } else { $value = $post_value; } } else { $value = false; // Note that a term_id of less than one indicates a nav_menu not yet inserted. if ( $this->term_id > 0 ) { $term = wp_get_nav_menu_object( $this->term_id ); if ( $term ) { $value = wp_array_slice_assoc( (array) $term, array_keys( $this->default ) ); $nav_menu_options = (array) get_option( 'nav_menu_options', array() ); $value['auto_add'] = false; if ( isset( $nav_menu_options['auto_add'] ) && is_array( $nav_menu_options['auto_add'] ) ) { $value['auto_add'] = in_array( $term->term_id, $nav_menu_options['auto_add'], true ); } } } if ( ! is_array( $value ) ) { $value = $this->default; } } return $value; } /** * Handle previewing the setting. * * @since 4.3.0 * @since 4.4.0 Added boolean return value * * @see WP_Customize_Manager::post_value() * * @return bool False if method short-circuited due to no-op. */ public function preview() { if ( $this->is_previewed ) { return false; } $undefined = new stdClass(); $is_placeholder = ( $this->term_id < 0 ); $is_dirty = ( $undefined !== $this->post_value( $undefined ) ); if ( ! $is_placeholder && ! $is_dirty ) { return false; } $this->is_previewed = true; $this->_original_value = $this->value(); $this->_previewed_blog_id = get_current_blog_id(); add_filter( 'wp_get_nav_menus', array( $this, 'filter_wp_get_nav_menus' ), 10, 2 ); add_filter( 'wp_get_nav_menu_object', array( $this, 'filter_wp_get_nav_menu_object' ), 10, 2 ); add_filter( 'default_option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) ); add_filter( 'option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) ); return true; } /** * Filters the wp_get_nav_menus() result to ensure the inserted menu object is included, and the deleted one is removed. * * @since 4.3.0 * * @see wp_get_nav_menus() * * @param WP_Term[] $menus An array of menu objects. * @param array $args An array of arguments used to retrieve menu objects. * @return WP_Term[] Array of menu objects. */ public function filter_wp_get_nav_menus( $menus, $args ) { if ( get_current_blog_id() !== $this->_previewed_blog_id ) { return $menus; } $setting_value = $this->value(); $is_delete = ( false === $setting_value ); $index = -1; // Find the existing menu item's position in the list. foreach ( $menus as $i => $menu ) { if ( (int) $this->term_id === (int) $menu->term_id || (int) $this->previous_term_id === (int) $menu->term_id ) { $index = $i; break; } } if ( $is_delete ) { // Handle deleted menu by removing it from the list. if ( -1 !== $index ) { array_splice( $menus, $index, 1 ); } } else { // Handle menus being updated or inserted. $menu_obj = (object) array_merge( array( 'term_id' => $this->term_id, 'term_taxonomy_id' => $this->term_id, 'slug' => sanitize_title( $setting_value['name'] ), 'count' => 0, 'term_group' => 0, 'taxonomy' => self::TAXONOMY, 'filter' => 'raw', ), $setting_value ); array_splice( $menus, $index, ( -1 === $index ? 0 : 1 ), array( $menu_obj ) ); } // Make sure the menu objects get re-sorted after an update/insert. if ( ! $is_delete && ! empty( $args['orderby'] ) ) { $menus = wp_list_sort( $menus, array( $args['orderby'] => 'ASC', ) ); } // @todo Add support for $args['hide_empty'] === true. return $menus; } /** * Temporary non-closure passing of orderby value to function. * * @since 4.3.0 * @var string * * @see WP_Customize_Nav_Menu_Setting::filter_wp_get_nav_menus() * @see WP_Customize_Nav_Menu_Setting::_sort_menus_by_orderby() */ protected $_current_menus_sort_orderby; /** * Sort menu objects by the class-supplied orderby property. * * This is a workaround for a lack of closures. * * @since 4.3.0 * @deprecated 4.7.0 Use wp_list_sort() * * @param object $menu1 * @param object $menu2 * @return int * * @see WP_Customize_Nav_Menu_Setting::filter_wp_get_nav_menus() */ protected function _sort_menus_by_orderby( $menu1, $menu2 ) { _deprecated_function( __METHOD__, '4.7.0', 'wp_list_sort' ); $key = $this->_current_menus_sort_orderby; return strcmp( $menu1->$key, $menu2->$key ); } /** * Filters the wp_get_nav_menu_object() result to supply the previewed menu object. * * Requesting a nav_menu object by anything but ID is not supported. * * @since 4.3.0 * * @see wp_get_nav_menu_object() * * @param object|null $menu_obj Object returned by wp_get_nav_menu_object(). * @param string $menu_id ID of the nav_menu term. Requests by slug or name will be ignored. * @return object|null */ public function filter_wp_get_nav_menu_object( $menu_obj, $menu_id ) { $ok = ( get_current_blog_id() === $this->_previewed_blog_id && is_int( $menu_id ) && $menu_id === $this->term_id ); if ( ! $ok ) { return $menu_obj; } $setting_value = $this->value(); // Handle deleted menus. if ( false === $setting_value ) { return false; } // Handle sanitization failure by preventing short-circuiting. if ( null === $setting_value ) { return $menu_obj; } $menu_obj = (object) array_merge( array( 'term_id' => $this->term_id, 'term_taxonomy_id' => $this->term_id, 'slug' => sanitize_title( $setting_value['name'] ), 'count' => 0, 'term_group' => 0, 'taxonomy' => self::TAXONOMY, 'filter' => 'raw', ), $setting_value ); return $menu_obj; } /** * Filters the nav_menu_options option to include this menu's auto_add preference. * * @since 4.3.0 * * @param array $nav_menu_options Nav menu options including auto_add. * @return array (Maybe) modified nav menu options. */ public function filter_nav_menu_options( $nav_menu_options ) { if ( get_current_blog_id() !== $this->_previewed_blog_id ) { return $nav_menu_options; } $menu = $this->value(); $nav_menu_options = $this->filter_nav_menu_options_value( $nav_menu_options, $this->term_id, false === $menu ? false : $menu['auto_add'] ); return $nav_menu_options; } /** * Sanitize an input. * * Note that parent::sanitize() erroneously does wp_unslash() on $value, but * we remove that in this override. * * @since 4.3.0 * * @param array $value The menu value to sanitize. * @return array|false|null Null if an input isn't valid. False if it is marked for deletion. * Otherwise the sanitized value. */ public function sanitize( $value ) { // Menu is marked for deletion. if ( false === $value ) { return $value; } // Invalid. if ( ! is_array( $value ) ) { return null; } $default = array( 'name' => '', 'description' => '', 'parent' => 0, 'auto_add' => false, ); $value = array_merge( $default, $value ); $value = wp_array_slice_assoc( $value, array_keys( $default ) ); $value['name'] = trim( esc_html( $value['name'] ) ); // This sanitization code is used in wp-admin/nav-menus.php. $value['description'] = sanitize_text_field( $value['description'] ); $value['parent'] = max( 0, (int) $value['parent'] ); $value['auto_add'] = ! empty( $value['auto_add'] ); if ( '' === $value['name'] ) { $value['name'] = _x( '(unnamed)', 'Missing menu name.' ); } /** This filter is documented in wp-includes/class-wp-customize-setting.php */ return apply_filters( "customize_sanitize_{$this->id}", $value, $this ); } /** * Storage for data to be sent back to client in customize_save_response filter. * * See {@see 'customize_save_response'}. * * @since 4.3.0 * @var array * * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() */ protected $_widget_nav_menu_updates = array(); /** * Create/update the nav_menu term for this setting. * * Any created menus will have their assigned term IDs exported to the client * via the {@see 'customize_save_response'} filter. Likewise, any errors will be exported * to the client via the customize_save_response() filter. * * To delete a menu, the client can send false as the value. * * @since 4.3.0 * * @see wp_update_nav_menu_object() * * @param array|false $value { * The value to update. Note that slug cannot be updated via wp_update_nav_menu_object(). * If false, then the menu will be deleted entirely. * * @type string $name The name of the menu to save. * @type string $description The term description. Default empty string. * @type int $parent The id of the parent term. Default 0. * @type bool $auto_add Whether pages will auto_add to this menu. Default false. * } * @return null|void */ protected function update( $value ) { if ( $this->is_updated ) { return; } $this->is_updated = true; $is_placeholder = ( $this->term_id < 0 ); $is_delete = ( false === $value ); add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) ); $auto_add = null; if ( $is_delete ) { // If the current setting term is a placeholder, a delete request is a no-op. if ( $is_placeholder ) { $this->update_status = 'deleted'; } else { $r = wp_delete_nav_menu( $this->term_id ); if ( is_wp_error( $r ) ) { $this->update_status = 'error'; $this->update_error = $r; } else { $this->update_status = 'deleted'; $auto_add = false; } } } else { // Insert or update menu. $menu_data = wp_array_slice_assoc( $value, array( 'description', 'parent' ) ); $menu_data['menu-name'] = $value['name']; $menu_id = $is_placeholder ? 0 : $this->term_id; $r = wp_update_nav_menu_object( $menu_id, wp_slash( $menu_data ) ); $original_name = $menu_data['menu-name']; $name_conflict_suffix = 1; while ( is_wp_error( $r ) && 'menu_exists' === $r->get_error_code() ) { $name_conflict_suffix += 1; /* translators: 1: Original menu name, 2: Duplicate count. */ $menu_data['menu-name'] = sprintf( __( '%1$s (%2$d)' ), $original_name, $name_conflict_suffix ); $r = wp_update_nav_menu_object( $menu_id, wp_slash( $menu_data ) ); } if ( is_wp_error( $r ) ) { $this->update_status = 'error'; $this->update_error = $r; } else { if ( $is_placeholder ) { $this->previous_term_id = $this->term_id; $this->term_id = $r; $this->update_status = 'inserted'; } else { $this->update_status = 'updated'; } $auto_add = $value['auto_add']; } } if ( null !== $auto_add ) { $nav_menu_options = $this->filter_nav_menu_options_value( (array) get_option( 'nav_menu_options', array() ), $this->term_id, $auto_add ); update_option( 'nav_menu_options', $nav_menu_options ); } if ( 'inserted' === $this->update_status ) { // Make sure that new menus assigned to nav menu locations use their new IDs. foreach ( $this->manager->settings() as $setting ) { if ( ! preg_match( '/^nav_menu_locations\[/', $setting->id ) ) { continue; } $post_value = $setting->post_value( null ); if ( ! is_null( $post_value ) && (int) $post_value === $this->previous_term_id ) { $this->manager->set_post_value( $setting->id, $this->term_id ); $setting->save(); } } // Make sure that any nav_menu widgets referencing the placeholder nav menu get updated and sent back to client. foreach ( array_keys( $this->manager->unsanitized_post_values() ) as $setting_id ) { $nav_menu_widget_setting = $this->manager->get_setting( $setting_id ); if ( ! $nav_menu_widget_setting || ! preg_match( '/^widget_nav_menu\[/', $nav_menu_widget_setting->id ) ) { continue; } $widget_instance = $nav_menu_widget_setting->post_value(); // Note that this calls WP_Customize_Widgets::sanitize_widget_instance(). if ( empty( $widget_instance['nav_menu'] ) || (int) $widget_instance['nav_menu'] !== $this->previous_term_id ) { continue; } $widget_instance['nav_menu'] = $this->term_id; $updated_widget_instance = $this->manager->widgets->sanitize_widget_js_instance( $widget_instance ); $this->manager->set_post_value( $nav_menu_widget_setting->id, $updated_widget_instance ); $nav_menu_widget_setting->save(); $this->_widget_nav_menu_updates[ $nav_menu_widget_setting->id ] = $updated_widget_instance; } } } /** * Updates a nav_menu_options array. * * @since 4.3.0 * * @see WP_Customize_Nav_Menu_Setting::filter_nav_menu_options() * @see WP_Customize_Nav_Menu_Setting::update() * * @param array $nav_menu_options Array as returned by get_option( 'nav_menu_options' ). * @param int $menu_id The term ID for the given menu. * @param bool $auto_add Whether to auto-add or not. * @return array (Maybe) modified nav_menu_options array. */ protected function filter_nav_menu_options_value( $nav_menu_options, $menu_id, $auto_add ) { $nav_menu_options = (array) $nav_menu_options; if ( ! isset( $nav_menu_options['auto_add'] ) ) { $nav_menu_options['auto_add'] = array(); } $i = array_search( $menu_id, $nav_menu_options['auto_add'], true ); if ( $auto_add && false === $i ) { array_push( $nav_menu_options['auto_add'], $this->term_id ); } elseif ( ! $auto_add && false !== $i ) { array_splice( $nav_menu_options['auto_add'], $i, 1 ); } return $nav_menu_options; } /** * Export data for the JS client. * * @since 4.3.0 * * @see WP_Customize_Nav_Menu_Setting::update() * * @param array $data Additional information passed back to the 'saved' event on `wp.customize`. * @return array Export data. */ public function amend_customize_save_response( $data ) { if ( ! isset( $data['nav_menu_updates'] ) ) { $data['nav_menu_updates'] = array(); } if ( ! isset( $data['widget_nav_menu_updates'] ) ) { $data['widget_nav_menu_updates'] = array(); } $data['nav_menu_updates'][] = array( 'term_id' => $this->term_id, 'previous_term_id' => $this->previous_term_id, 'error' => $this->update_error ? $this->update_error->get_error_code() : null, 'status' => $this->update_status, 'saved_value' => 'deleted' === $this->update_status ? null : $this->value(), ); $data['widget_nav_menu_updates'] = array_merge( $data['widget_nav_menu_updates'], $this->_widget_nav_menu_updates ); $this->_widget_nav_menu_updates = array(); return $data; } } class-wp-customize-nav-menu-item-control.php 0000666 00000017735 15226762357 0015224 0 ustar 00 <?php /** * Customize API: WP_Customize_Nav_Menu_Item_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize control to represent the name field for a given menu. * * @since 4.3.0 * * @see WP_Customize_Control */ class WP_Customize_Nav_Menu_Item_Control extends WP_Customize_Control { /** * Control type. * * @since 4.3.0 * @var string */ public $type = 'nav_menu_item'; /** * The nav menu item setting. * * @since 4.3.0 * @var WP_Customize_Nav_Menu_Item_Setting */ public $setting; /** * Constructor. * * @since 4.3.0 * * @see WP_Customize_Control::__construct() * * @param WP_Customize_Manager $manager Customizer bootstrap instance. * @param string $id The control ID. * @param array $args Optional. Arguments to override class property defaults. * See WP_Customize_Control::__construct() for information * on accepted arguments. Default empty array. */ public function __construct( $manager, $id, $args = array() ) { parent::__construct( $manager, $id, $args ); } /** * Don't render the control's content - it's rendered with a JS template. * * @since 4.3.0 */ public function render_content() {} /** * JS/Underscore template for the control UI. * * @since 4.3.0 */ public function content_template() { ?> <div class="menu-item-bar"> <div class="menu-item-handle"> <span class="item-type" aria-hidden="true">{{ data.item_type_label }}</span> <span class="item-title" aria-hidden="true"> <span class="spinner"></span> <span class="menu-item-title<# if ( ! data.title && ! data.original_title ) { #> no-title<# } #>">{{ data.title || data.original_title || wp.customize.Menus.data.l10n.untitled }}</span> <# if ( 0 === data.depth ) { #> <span class="is-submenu" style="display: none;"><?php _e( 'sub item' ); ?></span> <# } else { #> <span class="is-submenu"><?php _e( 'sub item' ); ?></span> <# } #> </span> <span class="item-controls"> <button type="button" class="button-link item-edit" aria-expanded="false"><span class="screen-reader-text"> <# if ( 0 === data.depth ) { #> <?php /* translators: 1: Title of a menu item, 2: Type of a menu item. 3: Item index, 4: Total items. */ printf( __( 'Edit %1$s (%2$s, %3$d of %4$d)' ), '{{ data.title || data.original_title || wp.customize.Menus.data.l10n.untitled }}', '{{ data.item_type_label }}', '', '' ); ?> <# } else if ( 1 === data.depth ) { #> <?php /* translators: 1: Title of a menu item, 2: Type of a menu item, 3, Item index, 4, Total items, 5: Item parent. */ printf( __( 'Edit %1$s (%2$s, sub-item %3$d of %4$d under %5$s)' ), '{{ data.title || data.original_title || wp.customize.Menus.data.l10n.untitled }}', '{{ data.item_type_label }}', '', '', '' ); ?> <# } else { #> <?php /* translators: 1: Title of a menu item, 2: Type of a menu item, 3, Item index, 4, Total items, 5: Item parent, 6: Item depth. */ printf( __( 'Edit %1$s (%2$s, sub-item %3$d of %4$d under %5$s, level %6$s)' ), '{{ data.title || data.original_title || wp.customize.Menus.data.l10n.untitled }}', '{{ data.item_type_label }}', '', '', '', '{{data.depth}}' ); ?> <# } #> </span><span class="toggle-indicator" aria-hidden="true"></span></button> <button type="button" class="button-link item-delete submitdelete deletion"><span class="screen-reader-text"> <?php /* translators: 1: Title of a menu item, 2: Type of a menu item. */ printf( __( 'Remove Menu Item: %1$s (%2$s)' ), '{{ data.title || data.original_title || wp.customize.Menus.data.l10n.untitled }}', '{{ data.item_type_label }}' ); ?> </span></button> </span> </div> </div> <div class="menu-item-settings" id="menu-item-settings-{{ data.menu_item_id }}"> <# if ( 'custom' === data.item_type ) { #> <p class="field-url description description-thin"> <label for="edit-menu-item-url-{{ data.menu_item_id }}"> <?php _e( 'URL' ); ?><br /> <input class="widefat code edit-menu-item-url" type="text" id="edit-menu-item-url-{{ data.menu_item_id }}" name="menu-item-url" /> </label> </p> <# } #> <p class="description description-thin"> <label for="edit-menu-item-title-{{ data.menu_item_id }}"> <?php _e( 'Navigation Label' ); ?><br /> <input type="text" id="edit-menu-item-title-{{ data.menu_item_id }}" placeholder="{{ data.original_title }}" class="widefat edit-menu-item-title" name="menu-item-title" /> </label> </p> <p class="field-link-target description description-thin"> <label for="edit-menu-item-target-{{ data.menu_item_id }}"> <input type="checkbox" id="edit-menu-item-target-{{ data.menu_item_id }}" class="edit-menu-item-target" value="_blank" name="menu-item-target" /> <?php _e( 'Open link in a new tab' ); ?> </label> </p> <p class="field-title-attribute field-attr-title description description-thin"> <label for="edit-menu-item-attr-title-{{ data.menu_item_id }}"> <?php _e( 'Title Attribute' ); ?><br /> <input type="text" id="edit-menu-item-attr-title-{{ data.menu_item_id }}" class="widefat edit-menu-item-attr-title" name="menu-item-attr-title" /> </label> </p> <p class="field-css-classes description description-thin"> <label for="edit-menu-item-classes-{{ data.menu_item_id }}"> <?php _e( 'CSS Classes' ); ?><br /> <input type="text" id="edit-menu-item-classes-{{ data.menu_item_id }}" class="widefat code edit-menu-item-classes" name="menu-item-classes" /> </label> </p> <p class="field-xfn description description-thin"> <label for="edit-menu-item-xfn-{{ data.menu_item_id }}"> <?php _e( 'Link Relationship (XFN)' ); ?><br /> <input type="text" id="edit-menu-item-xfn-{{ data.menu_item_id }}" class="widefat code edit-menu-item-xfn" name="menu-item-xfn" /> </label> </p> <p class="field-description description description-thin"> <label for="edit-menu-item-description-{{ data.menu_item_id }}"> <?php _e( 'Description' ); ?><br /> <textarea id="edit-menu-item-description-{{ data.menu_item_id }}" class="widefat edit-menu-item-description" rows="3" cols="20" name="menu-item-description">{{ data.description }}</textarea> <span class="description"><?php _e( 'The description will be displayed in the menu if the active theme supports it.' ); ?></span> </label> </p> <?php /** * Fires at the end of the form field template for nav menu items in the customizer. * * Additional fields can be rendered here and managed in JavaScript. * * @since 5.4.0 */ do_action( 'wp_nav_menu_item_custom_fields_customize_template' ); ?> <div class="menu-item-actions description-thin submitbox"> <# if ( ( 'post_type' === data.item_type || 'taxonomy' === data.item_type ) && '' !== data.original_title ) { #> <p class="link-to-original"> <?php /* translators: Nav menu item original title. %s: Original title. */ printf( __( 'Original: %s' ), '<a class="original-link" href="{{ data.url }}">{{ data.original_title }}</a>' ); ?> </p> <# } #> <button type="button" class="button-link button-link-delete item-delete submitdelete deletion"><?php _e( 'Remove' ); ?></button> <span class="spinner"></span> </div> <input type="hidden" name="menu-item-db-id[{{ data.menu_item_id }}]" class="menu-item-data-db-id" value="{{ data.menu_item_id }}" /> <input type="hidden" name="menu-item-parent-id[{{ data.menu_item_id }}]" class="menu-item-data-parent-id" value="{{ data.parent }}" /> </div><!-- .menu-item-settings--> <ul class="menu-item-transport"></ul> <?php } /** * Return parameters for this control. * * @since 4.3.0 * * @return array Exported parameters. */ public function json() { $exported = parent::json(); $exported['menu_item_id'] = $this->setting->post_id; return $exported; } } class-wp-customize-nav-menu-control.php 0000666 00000004120 15226762357 0014250 0 ustar 00 <?php /** * Customize API: WP_Customize_Nav_Menu_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Nav Menu Control Class. * * @since 4.3.0 * * @see WP_Customize_Control */ class WP_Customize_Nav_Menu_Control extends WP_Customize_Control { /** * Control type. * * @since 4.3.0 * @var string */ public $type = 'nav_menu'; /** * Don't render the control's content - it uses a JS template instead. * * @since 4.3.0 */ public function render_content() {} /** * JS/Underscore template for the control UI. * * @since 4.3.0 */ public function content_template() { $add_items = __( 'Add Items' ); ?> <p class="new-menu-item-invitation"> <?php printf( /* translators: %s: "Add Items" button text. */ __( 'Time to add some links! Click “%s” to start putting pages, categories, and custom links in your menu. Add as many things as you would like.' ), $add_items ); ?> </p> <div class="customize-control-nav_menu-buttons"> <button type="button" class="button add-new-menu-item" aria-label="<?php esc_attr_e( 'Add or remove menu items' ); ?>" aria-expanded="false" aria-controls="available-menu-items"> <?php echo $add_items; ?> </button> <button type="button" class="button-link reorder-toggle" aria-label="<?php esc_attr_e( 'Reorder menu items' ); ?>" aria-describedby="reorder-items-desc-{{ data.menu_id }}"> <span class="reorder"><?php _e( 'Reorder' ); ?></span> <span class="reorder-done"><?php _e( 'Done' ); ?></span> </button> </div> <p class="screen-reader-text" id="reorder-items-desc-{{ data.menu_id }}"> <?php /* translators: Hidden accessibility text. */ _e( 'When in reorder mode, additional controls to reorder menu items will be available in the items list above.' ); ?> </p> <?php } /** * Return parameters for this control. * * @since 4.3.0 * * @return array Exported parameters. */ public function json() { $exported = parent::json(); $exported['menu_id'] = $this->setting->term_id; return $exported; } } class-wp-customize-nav-menu-name-control.php 0000666 00000002153 15226762357 0015172 0 ustar 00 <?php /** * Customize API: WP_Customize_Nav_Menu_Name_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize control to represent the name field for a given menu. * * @since 4.3.0 * * @see WP_Customize_Control */ class WP_Customize_Nav_Menu_Name_Control extends WP_Customize_Control { /** * Type of control, used by JS. * * @since 4.3.0 * @var string */ public $type = 'nav_menu_name'; /** * No-op since we're using JS template. * * @since 4.3.0 */ protected function render_content() {} /** * Render the Underscore template for this control. * * @since 4.3.0 */ protected function content_template() { ?> <label> <# if ( data.label ) { #> <span class="customize-control-title">{{ data.label }}</span> <# } #> <input type="text" class="menu-name-field live-update-section-title" <# if ( data.description ) { #> aria-describedby="{{ data.section }}-description" <# } #> /> </label> <# if ( data.description ) { #> <p id="{{ data.section }}-description">{{ data.description }}</p> <# } #> <?php } } class-wp-customize-nav-menu-item-setting.php 0000666 00000066335 15226762357 0015221 0 ustar 00 <?php /** * Customize API: WP_Customize_Nav_Menu_Item_Setting class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Setting to represent a nav_menu. * * Subclass of WP_Customize_Setting to represent a nav_menu taxonomy term, and * the IDs for the nav_menu_items associated with the nav menu. * * @since 4.3.0 * * @see WP_Customize_Setting */ class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting { const ID_PATTERN = '/^nav_menu_item\[(?P<id>-?\d+)\]$/'; const POST_TYPE = 'nav_menu_item'; const TYPE = 'nav_menu_item'; /** * Setting type. * * @since 4.3.0 * @var string */ public $type = self::TYPE; /** * Default setting value. * * @since 4.3.0 * @var array * * @see wp_setup_nav_menu_item() */ public $default = array( // The $menu_item_data for wp_update_nav_menu_item(). 'object_id' => 0, 'object' => '', // Taxonomy name. 'menu_item_parent' => 0, // A.K.A. menu-item-parent-id; note that post_parent is different, and not included. 'position' => 0, // A.K.A. menu_order. 'type' => 'custom', // Note that type_label is not included here. 'title' => '', 'url' => '', 'target' => '', 'attr_title' => '', 'description' => '', 'classes' => '', 'xfn' => '', 'status' => 'publish', 'nav_menu_term_id' => 0, // This will be supplied as the $menu_id arg for wp_update_nav_menu_item(). '_invalid' => false, ); /** * Default transport. * * @since 4.3.0 * @since 4.5.0 Default changed to 'refresh' * @var string */ public $transport = 'refresh'; /** * The post ID represented by this setting instance. This is the db_id. * * A negative value represents a placeholder ID for a new menu not yet saved. * * @since 4.3.0 * @var int */ public $post_id; /** * Storage of pre-setup menu item to prevent wasted calls to wp_setup_nav_menu_item(). * * @since 4.3.0 * @var array|null */ protected $value; /** * Previous (placeholder) post ID used before creating a new menu item. * * This value will be exported to JS via the customize_save_response filter * so that JavaScript can update the settings to refer to the newly-assigned * post ID. This value is always negative to indicate it does not refer to * a real post. * * @since 4.3.0 * @var int * * @see WP_Customize_Nav_Menu_Item_Setting::update() * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() */ public $previous_post_id; /** * When previewing or updating a menu item, this stores the previous nav_menu_term_id * which ensures that we can apply the proper filters. * * @since 4.3.0 * @var int */ public $original_nav_menu_term_id; /** * Whether or not update() was called. * * @since 4.3.0 * @var bool */ protected $is_updated = false; /** * Status for calling the update method, used in customize_save_response filter. * * See {@see 'customize_save_response'}. * * When status is inserted, the placeholder post ID is stored in $previous_post_id. * When status is error, the error is stored in $update_error. * * @since 4.3.0 * @var string updated|inserted|deleted|error * * @see WP_Customize_Nav_Menu_Item_Setting::update() * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() */ public $update_status; /** * Any error object returned by wp_update_nav_menu_item() when setting is updated. * * @since 4.3.0 * @var WP_Error * * @see WP_Customize_Nav_Menu_Item_Setting::update() * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() */ public $update_error; /** * Constructor. * * Any supplied $args override class property defaults. * * @since 4.3.0 * * @throws Exception If $id is not valid for this setting type. * * @param WP_Customize_Manager $manager Customizer bootstrap instance. * @param string $id A specific ID of the setting. * Can be a theme mod or option name. * @param array $args Optional. Setting arguments. */ public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) { if ( empty( $manager->nav_menus ) ) { throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' ); } if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) { throw new Exception( "Illegal widget setting ID: $id" ); } $this->post_id = (int) $matches['id']; add_action( 'wp_update_nav_menu_item', array( $this, 'flush_cached_value' ), 10, 2 ); parent::__construct( $manager, $id, $args ); // Ensure that an initially-supplied value is valid. if ( isset( $this->value ) ) { $this->populate_value(); foreach ( array_diff( array_keys( $this->default ), array_keys( $this->value ) ) as $missing ) { throw new Exception( "Supplied nav_menu_item value missing property: $missing" ); } } } /** * Clear the cached value when this nav menu item is updated. * * @since 4.3.0 * * @param int $menu_id The term ID for the menu. * @param int $menu_item_id The post ID for the menu item. */ public function flush_cached_value( $menu_id, $menu_item_id ) { unset( $menu_id ); if ( $menu_item_id === $this->post_id ) { $this->value = null; } } /** * Get the instance data for a given nav_menu_item setting. * * @since 4.3.0 * * @see wp_setup_nav_menu_item() * * @return array|false Instance data array, or false if the item is marked for deletion. */ public function value() { $type_label = null; if ( $this->is_previewed && get_current_blog_id() === $this->_previewed_blog_id ) { $undefined = new stdClass(); // Symbol. $post_value = $this->post_value( $undefined ); if ( $undefined === $post_value ) { $value = $this->_original_value; } else { $value = $post_value; } } elseif ( isset( $this->value ) ) { $value = $this->value; } else { $value = false; // Note that an ID of less than one indicates a nav_menu not yet inserted. if ( $this->post_id > 0 ) { $post = get_post( $this->post_id ); if ( $post && self::POST_TYPE === $post->post_type ) { $is_title_empty = empty( $post->post_title ); $value = (array) wp_setup_nav_menu_item( $post ); if ( isset( $value['type_label'] ) ) { $type_label = $value['type_label']; } if ( $is_title_empty ) { $value['title'] = ''; } } } if ( ! is_array( $value ) ) { $value = $this->default; } // Cache the value for future calls to avoid having to re-call wp_setup_nav_menu_item(). $this->value = $value; $this->populate_value(); $value = $this->value; } // These properties are read-only and are part of the setting for use in the Customizer UI. if ( is_array( $value ) ) { $value_obj = (object) $value; $value['type_label'] = isset( $type_label ) ? $type_label : $this->get_type_label( $value_obj ); $value['original_title'] = $this->get_original_title( $value_obj ); } return $value; } /** * Prepares the value for editing on the client. * * @since 6.8.3 * * @return array|false Value prepared for the client. */ public function js_value() { $value = parent::js_value(); if ( is_array( $value ) && isset( $value['original_title'] ) ) { // Decode entities for the sake of displaying the original title as a placeholder. $value['original_title'] = html_entity_decode( $value['original_title'], ENT_QUOTES, get_bloginfo( 'charset' ) ); } return $value; } /** * Get original title. * * @since 4.7.0 * * @param object $item Nav menu item. * @return string The original title, without entity decoding. */ protected function get_original_title( $item ) { $original_title = ''; if ( 'post_type' === $item->type && ! empty( $item->object_id ) ) { $original_object = get_post( $item->object_id ); if ( $original_object ) { /** This filter is documented in wp-includes/post-template.php */ $original_title = apply_filters( 'the_title', $original_object->post_title, $original_object->ID ); if ( '' === $original_title ) { /* translators: %d: ID of a post. */ $original_title = sprintf( __( '#%d (no title)' ), $original_object->ID ); } } } elseif ( 'taxonomy' === $item->type && ! empty( $item->object_id ) ) { $original_term_title = get_term_field( 'name', $item->object_id, $item->object, 'raw' ); if ( ! is_wp_error( $original_term_title ) ) { $original_title = $original_term_title; } } elseif ( 'post_type_archive' === $item->type ) { $original_object = get_post_type_object( $item->object ); if ( $original_object ) { $original_title = $original_object->labels->archives; } } return $original_title; } /** * Get type label. * * @since 4.7.0 * * @param object $item Nav menu item. * @return string The type label. */ protected function get_type_label( $item ) { if ( 'post_type' === $item->type ) { $object = get_post_type_object( $item->object ); if ( $object ) { $type_label = $object->labels->singular_name; } else { $type_label = $item->object; } } elseif ( 'taxonomy' === $item->type ) { $object = get_taxonomy( $item->object ); if ( $object ) { $type_label = $object->labels->singular_name; } else { $type_label = $item->object; } } elseif ( 'post_type_archive' === $item->type ) { $type_label = __( 'Post Type Archive' ); } else { $type_label = __( 'Custom Link' ); } return $type_label; } /** * Ensure that the value is fully populated with the necessary properties. * * Translates some properties added by wp_setup_nav_menu_item() and removes others. * * @since 4.3.0 * * @see WP_Customize_Nav_Menu_Item_Setting::value() */ protected function populate_value() { if ( ! is_array( $this->value ) ) { return; } if ( isset( $this->value['menu_order'] ) ) { $this->value['position'] = $this->value['menu_order']; unset( $this->value['menu_order'] ); } if ( isset( $this->value['post_status'] ) ) { $this->value['status'] = $this->value['post_status']; unset( $this->value['post_status'] ); } if ( ! isset( $this->value['nav_menu_term_id'] ) && $this->post_id > 0 ) { $menus = wp_get_post_terms( $this->post_id, WP_Customize_Nav_Menu_Setting::TAXONOMY, array( 'fields' => 'ids', ) ); if ( ! empty( $menus ) ) { $this->value['nav_menu_term_id'] = array_shift( $menus ); } else { $this->value['nav_menu_term_id'] = 0; } } foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) { if ( ! is_int( $this->value[ $key ] ) ) { $this->value[ $key ] = (int) $this->value[ $key ]; } } foreach ( array( 'classes', 'xfn' ) as $key ) { if ( is_array( $this->value[ $key ] ) ) { $this->value[ $key ] = implode( ' ', $this->value[ $key ] ); } } if ( ! isset( $this->value['title'] ) ) { $this->value['title'] = ''; } if ( ! isset( $this->value['_invalid'] ) ) { $this->value['_invalid'] = false; $is_known_invalid = ( ( ( 'post_type' === $this->value['type'] || 'post_type_archive' === $this->value['type'] ) && ! post_type_exists( $this->value['object'] ) ) || ( 'taxonomy' === $this->value['type'] && ! taxonomy_exists( $this->value['object'] ) ) ); if ( $is_known_invalid ) { $this->value['_invalid'] = true; } } // Remove remaining properties available on a setup nav_menu_item post object which aren't relevant to the setting value. $irrelevant_properties = array( 'ID', 'comment_count', 'comment_status', 'db_id', 'filter', 'guid', 'ping_status', 'pinged', 'post_author', 'post_content', 'post_content_filtered', 'post_date', 'post_date_gmt', 'post_excerpt', 'post_mime_type', 'post_modified', 'post_modified_gmt', 'post_name', 'post_parent', 'post_password', 'post_title', 'post_type', 'to_ping', ); foreach ( $irrelevant_properties as $property ) { unset( $this->value[ $property ] ); } } /** * Handle previewing the setting. * * @since 4.3.0 * @since 4.4.0 Added boolean return value. * * @see WP_Customize_Manager::post_value() * * @return bool False if method short-circuited due to no-op. */ public function preview() { if ( $this->is_previewed ) { return false; } $undefined = new stdClass(); $is_placeholder = ( $this->post_id < 0 ); $is_dirty = ( $undefined !== $this->post_value( $undefined ) ); if ( ! $is_placeholder && ! $is_dirty ) { return false; } $this->is_previewed = true; $this->_original_value = $this->value(); $this->original_nav_menu_term_id = $this->_original_value['nav_menu_term_id']; $this->_previewed_blog_id = get_current_blog_id(); add_filter( 'wp_get_nav_menu_items', array( $this, 'filter_wp_get_nav_menu_items' ), 10, 3 ); $sort_callback = array( __CLASS__, 'sort_wp_get_nav_menu_items' ); if ( ! has_filter( 'wp_get_nav_menu_items', $sort_callback ) ) { add_filter( 'wp_get_nav_menu_items', array( __CLASS__, 'sort_wp_get_nav_menu_items' ), 1000, 3 ); } // @todo Add get_post_metadata filters for plugins to add their data. return true; } /** * Filters the wp_get_nav_menu_items() result to supply the previewed menu items. * * @since 4.3.0 * * @see wp_get_nav_menu_items() * * @param WP_Post[] $items An array of menu item post objects. * @param WP_Term $menu The menu object. * @param array $args An array of arguments used to retrieve menu item objects. * @return WP_Post[] Array of menu item objects. */ public function filter_wp_get_nav_menu_items( $items, $menu, $args ) { $this_item = $this->value(); $current_nav_menu_term_id = null; if ( isset( $this_item['nav_menu_term_id'] ) ) { $current_nav_menu_term_id = $this_item['nav_menu_term_id']; unset( $this_item['nav_menu_term_id'] ); } $should_filter = ( $menu->term_id === $this->original_nav_menu_term_id || $menu->term_id === $current_nav_menu_term_id ); if ( ! $should_filter ) { return $items; } // Handle deleted menu item, or menu item moved to another menu. $should_remove = ( false === $this_item || ( isset( $this_item['_invalid'] ) && true === $this_item['_invalid'] ) || ( $this->original_nav_menu_term_id === $menu->term_id && $current_nav_menu_term_id !== $this->original_nav_menu_term_id ) ); if ( $should_remove ) { $filtered_items = array(); foreach ( $items as $item ) { if ( $item->db_id !== $this->post_id ) { $filtered_items[] = $item; } } return $filtered_items; } $mutated = false; $should_update = ( is_array( $this_item ) && $current_nav_menu_term_id === $menu->term_id ); if ( $should_update ) { foreach ( $items as $item ) { if ( $item->db_id === $this->post_id ) { foreach ( get_object_vars( $this->value_as_wp_post_nav_menu_item() ) as $key => $value ) { $item->$key = $value; } $mutated = true; } } // Not found so we have to append it.. if ( ! $mutated ) { $items[] = $this->value_as_wp_post_nav_menu_item(); } } return $items; } /** * Re-apply the tail logic also applied on $items by wp_get_nav_menu_items(). * * @since 4.3.0 * * @see wp_get_nav_menu_items() * * @param WP_Post[] $items An array of menu item post objects. * @param WP_Term $menu The menu object. * @param array $args An array of arguments used to retrieve menu item objects. * @return WP_Post[] Array of menu item objects. */ public static function sort_wp_get_nav_menu_items( $items, $menu, $args ) { // @todo We should probably re-apply some constraints imposed by $args. unset( $args['include'] ); // Remove invalid items only in front end. if ( ! is_admin() ) { $items = array_filter( $items, '_is_valid_nav_menu_item' ); } if ( ARRAY_A === $args['output'] ) { $items = wp_list_sort( $items, array( $args['output_key'] => 'ASC', ) ); $i = 1; foreach ( $items as $k => $item ) { $items[ $k ]->{$args['output_key']} = $i++; } } return $items; } /** * Get the value emulated into a WP_Post and set up as a nav_menu_item. * * @since 4.3.0 * * @return WP_Post With wp_setup_nav_menu_item() applied. */ public function value_as_wp_post_nav_menu_item() { $item = (object) $this->value(); unset( $item->nav_menu_term_id ); $item->post_status = $item->status; unset( $item->status ); $item->post_type = 'nav_menu_item'; $item->menu_order = $item->position; unset( $item->position ); if ( empty( $item->title ) && ! empty( $item->original_title ) ) { $item->title = $item->original_title; // This is NOT entity-decoded. It comes from self::get_original_title(). } if ( $item->title ) { $item->post_title = $item->title; } // 'classes' should be an array, as in wp_setup_nav_menu_item(). if ( isset( $item->classes ) && is_scalar( $item->classes ) ) { $item->classes = explode( ' ', $item->classes ); } $item->ID = $this->post_id; $item->db_id = $this->post_id; $post = new WP_Post( (object) $item ); if ( empty( $post->post_author ) ) { $post->post_author = get_current_user_id(); } if ( ! isset( $post->type_label ) ) { $post->type_label = $this->get_type_label( $post ); } // Ensure nav menu item URL is set according to linked object. if ( 'post_type' === $post->type && ! empty( $post->object_id ) ) { $post->url = get_permalink( $post->object_id ); } elseif ( 'taxonomy' === $post->type && ! empty( $post->object ) && ! empty( $post->object_id ) ) { $post->url = get_term_link( (int) $post->object_id, $post->object ); } elseif ( 'post_type_archive' === $post->type && ! empty( $post->object ) ) { $post->url = get_post_type_archive_link( $post->object ); } if ( is_wp_error( $post->url ) ) { $post->url = ''; } /** This filter is documented in wp-includes/nav-menu.php */ $post->attr_title = apply_filters( 'nav_menu_attr_title', $post->attr_title ); /** This filter is documented in wp-includes/nav-menu.php */ $post->description = apply_filters( 'nav_menu_description', wp_trim_words( $post->description, 200 ) ); /** This filter is documented in wp-includes/nav-menu.php */ $post = apply_filters( 'wp_setup_nav_menu_item', $post ); return $post; } /** * Sanitize an input. * * Note that parent::sanitize() erroneously does wp_unslash() on $value, but * we remove that in this override. * * @since 4.3.0 * @since 5.9.0 Renamed `$menu_item_value` to `$value` for PHP 8 named parameter support. * * @param array|false $value The menu item value to sanitize. * @return array|false|null|WP_Error Null or WP_Error if an input isn't valid. False if it is marked for deletion. * Otherwise the sanitized value. */ public function sanitize( $value ) { // Restores the more descriptive, specific name for use within this method. $menu_item_value = $value; // Menu is marked for deletion. if ( false === $menu_item_value ) { return $menu_item_value; } // Invalid. if ( ! is_array( $menu_item_value ) ) { return null; } $default = array( 'object_id' => 0, 'object' => '', 'menu_item_parent' => 0, 'position' => 0, 'type' => 'custom', 'title' => '', 'url' => '', 'target' => '', 'attr_title' => '', 'description' => '', 'classes' => '', 'xfn' => '', 'status' => 'publish', 'original_title' => '', 'nav_menu_term_id' => 0, '_invalid' => false, ); $menu_item_value = array_merge( $default, $menu_item_value ); $menu_item_value = wp_array_slice_assoc( $menu_item_value, array_keys( $default ) ); $menu_item_value['position'] = (int) $menu_item_value['position']; foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) { // Note we need to allow negative-integer IDs for previewed objects not inserted yet. $menu_item_value[ $key ] = (int) $menu_item_value[ $key ]; } foreach ( array( 'type', 'object', 'target' ) as $key ) { $menu_item_value[ $key ] = sanitize_key( $menu_item_value[ $key ] ); } foreach ( array( 'xfn', 'classes' ) as $key ) { $value = $menu_item_value[ $key ]; if ( ! is_array( $value ) ) { $value = explode( ' ', $value ); } $menu_item_value[ $key ] = implode( ' ', array_map( 'sanitize_html_class', $value ) ); } // Apply the same filters as when calling wp_insert_post(). /** This filter is documented in wp-includes/post.php */ $menu_item_value['title'] = wp_unslash( apply_filters( 'title_save_pre', wp_slash( $menu_item_value['title'] ) ) ); /** This filter is documented in wp-includes/post.php */ $menu_item_value['attr_title'] = wp_unslash( apply_filters( 'excerpt_save_pre', wp_slash( $menu_item_value['attr_title'] ) ) ); /** This filter is documented in wp-includes/post.php */ $menu_item_value['description'] = wp_unslash( apply_filters( 'content_save_pre', wp_slash( $menu_item_value['description'] ) ) ); if ( '' !== $menu_item_value['url'] ) { $menu_item_value['url'] = sanitize_url( $menu_item_value['url'] ); if ( '' === $menu_item_value['url'] ) { return new WP_Error( 'invalid_url', __( 'Invalid URL.' ) ); // Fail sanitization if URL is invalid. } } if ( 'publish' !== $menu_item_value['status'] ) { $menu_item_value['status'] = 'draft'; } $menu_item_value['_invalid'] = (bool) $menu_item_value['_invalid']; /** This filter is documented in wp-includes/class-wp-customize-setting.php */ return apply_filters( "customize_sanitize_{$this->id}", $menu_item_value, $this ); } /** * Creates/updates the nav_menu_item post for this setting. * * Any created menu items will have their assigned post IDs exported to the client * via the {@see 'customize_save_response'} filter. Likewise, any errors will be * exported to the client via the customize_save_response() filter. * * To delete a menu, the client can send false as the value. * * @since 4.3.0 * * @see wp_update_nav_menu_item() * * @param array|false $value The menu item array to update. If false, then the menu item will be deleted * entirely. See WP_Customize_Nav_Menu_Item_Setting::$default for what the value * should consist of. * @return null|void */ protected function update( $value ) { if ( $this->is_updated ) { return; } $this->is_updated = true; $is_placeholder = ( $this->post_id < 0 ); $is_delete = ( false === $value ); // Update the cached value. $this->value = $value; add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) ); if ( $is_delete ) { // If the current setting post is a placeholder, a delete request is a no-op. if ( $is_placeholder ) { $this->update_status = 'deleted'; } else { $r = wp_delete_post( $this->post_id, true ); if ( false === $r ) { $this->update_error = new WP_Error( 'delete_failure' ); $this->update_status = 'error'; } else { $this->update_status = 'deleted'; } // @todo send back the IDs for all associated nav menu items deleted, so these settings (and controls) can be removed from Customizer? } } else { // Handle saving menu items for menus that are being newly-created. if ( $value['nav_menu_term_id'] < 0 ) { $nav_menu_setting_id = sprintf( 'nav_menu[%s]', $value['nav_menu_term_id'] ); $nav_menu_setting = $this->manager->get_setting( $nav_menu_setting_id ); if ( ! $nav_menu_setting || ! ( $nav_menu_setting instanceof WP_Customize_Nav_Menu_Setting ) ) { $this->update_status = 'error'; $this->update_error = new WP_Error( 'unexpected_nav_menu_setting' ); return; } if ( false === $nav_menu_setting->save() ) { $this->update_status = 'error'; $this->update_error = new WP_Error( 'nav_menu_setting_failure' ); return; } if ( (int) $value['nav_menu_term_id'] !== $nav_menu_setting->previous_term_id ) { $this->update_status = 'error'; $this->update_error = new WP_Error( 'unexpected_previous_term_id' ); return; } $value['nav_menu_term_id'] = $nav_menu_setting->term_id; } // Handle saving a nav menu item that is a child of a nav menu item being newly-created. if ( $value['menu_item_parent'] < 0 ) { $parent_nav_menu_item_setting_id = sprintf( 'nav_menu_item[%s]', $value['menu_item_parent'] ); $parent_nav_menu_item_setting = $this->manager->get_setting( $parent_nav_menu_item_setting_id ); if ( ! $parent_nav_menu_item_setting || ! ( $parent_nav_menu_item_setting instanceof WP_Customize_Nav_Menu_Item_Setting ) ) { $this->update_status = 'error'; $this->update_error = new WP_Error( 'unexpected_nav_menu_item_setting' ); return; } if ( false === $parent_nav_menu_item_setting->save() ) { $this->update_status = 'error'; $this->update_error = new WP_Error( 'nav_menu_item_setting_failure' ); return; } if ( (int) $value['menu_item_parent'] !== $parent_nav_menu_item_setting->previous_post_id ) { $this->update_status = 'error'; $this->update_error = new WP_Error( 'unexpected_previous_post_id' ); return; } $value['menu_item_parent'] = $parent_nav_menu_item_setting->post_id; } // Insert or update menu. $menu_item_data = array( 'menu-item-object-id' => $value['object_id'], 'menu-item-object' => $value['object'], 'menu-item-parent-id' => $value['menu_item_parent'], 'menu-item-position' => $value['position'], 'menu-item-type' => $value['type'], 'menu-item-title' => $value['title'], 'menu-item-url' => $value['url'], 'menu-item-description' => $value['description'], 'menu-item-attr-title' => $value['attr_title'], 'menu-item-target' => $value['target'], 'menu-item-classes' => $value['classes'], 'menu-item-xfn' => $value['xfn'], 'menu-item-status' => $value['status'], ); $r = wp_update_nav_menu_item( $value['nav_menu_term_id'], $is_placeholder ? 0 : $this->post_id, wp_slash( $menu_item_data ) ); if ( is_wp_error( $r ) ) { $this->update_status = 'error'; $this->update_error = $r; } else { if ( $is_placeholder ) { $this->previous_post_id = $this->post_id; $this->post_id = $r; $this->update_status = 'inserted'; } else { $this->update_status = 'updated'; } } } } /** * Export data for the JS client. * * @since 4.3.0 * * @see WP_Customize_Nav_Menu_Item_Setting::update() * * @param array $data Additional information passed back to the 'saved' event on `wp.customize`. * @return array Save response data. */ public function amend_customize_save_response( $data ) { if ( ! isset( $data['nav_menu_item_updates'] ) ) { $data['nav_menu_item_updates'] = array(); } $data['nav_menu_item_updates'][] = array( 'post_id' => $this->post_id, 'previous_post_id' => $this->previous_post_id, 'error' => $this->update_error ? $this->update_error->get_error_code() : null, 'status' => $this->update_status, ); return $data; } } https-migration-20260605225728-20260613122638.php 0000666 00000011205 15226762357 0013413 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260605062553.php 0000666 00000011205 15226762357 0012017 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-plugin-dependencies-20260621194949.php 0000666 00000061343 15226762357 0014364 0 ustar 00 <?php /** * WordPress Plugin Administration API: WP_Plugin_Dependencies class * * @package WordPress * @subpackage Administration * @since 6.5.0 */ /** * Core class for installing plugin dependencies. * * It is designed to add plugin dependencies as designated in the * `Requires Plugins` header to a new view in the plugins install page. */ class WP_Plugin_Dependencies { /** * Holds 'get_plugins()'. * * @since 6.5.0 * * @var array */ protected static $plugins; /** * Holds plugin directory names to compare with cache. * * @since 6.5.0 * * @var array */ protected static $plugin_dirnames; /** * Holds sanitized plugin dependency slugs. * * Keyed on the dependent plugin's filepath, * relative to the plugins directory. * * @since 6.5.0 * * @var array */ protected static $dependencies; /** * Holds an array of sanitized plugin dependency slugs. * * @since 6.5.0 * * @var array */ protected static $dependency_slugs; /** * Holds an array of dependent plugin slugs. * * Keyed on the dependent plugin's filepath, * relative to the plugins directory. * * @since 6.5.0 * * @var array */ protected static $dependent_slugs; /** * Holds 'plugins_api()' data for plugin dependencies. * * @since 6.5.0 * * @var array */ protected static $dependency_api_data; /** * Holds plugin dependency filepaths, relative to the plugins directory. * * Keyed on the dependency's slug. * * @since 6.5.0 * * @var string[] */ protected static $dependency_filepaths; /** * An array of circular dependency pairings. * * @since 6.5.0 * * @var array[] */ protected static $circular_dependencies_pairs; /** * An array of circular dependency slugs. * * @since 6.5.0 * * @var string[] */ protected static $circular_dependencies_slugs; /** * Whether Plugin Dependencies have been initialized. * * @since 6.5.0 * * @var bool */ protected static $initialized = false; /** * Initializes by fetching plugin header and plugin API data. * * @since 6.5.0 */ public static function initialize() { if ( false === self::$initialized ) { self::read_dependencies_from_plugin_headers(); self::get_dependency_api_data(); self::$initialized = true; } } /** * Determines whether the plugin has plugins that depend on it. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return bool Whether the plugin has plugins that depend on it. */ public static function has_dependents( $plugin_file ) { return in_array( self::convert_to_slug( $plugin_file ), (array) self::$dependency_slugs, true ); } /** * Determines whether the plugin has plugin dependencies. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return bool Whether a plugin has plugin dependencies. */ public static function has_dependencies( $plugin_file ) { return isset( self::$dependencies[ $plugin_file ] ); } /** * Determines whether the plugin has active dependents. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return bool Whether the plugin has active dependents. */ public static function has_active_dependents( $plugin_file ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; $dependents = self::get_dependents( self::convert_to_slug( $plugin_file ) ); foreach ( $dependents as $dependent ) { if ( is_plugin_active( $dependent ) ) { return true; } } return false; } /** * Gets filepaths of plugins that require the dependency. * * @since 6.5.0 * * @param string $slug The dependency's slug. * @return array An array of dependent plugin filepaths, relative to the plugins directory. */ public static function get_dependents( $slug ) { $dependents = array(); foreach ( (array) self::$dependencies as $dependent => $dependencies ) { if ( in_array( $slug, $dependencies, true ) ) { $dependents[] = $dependent; } } return $dependents; } /** * Gets the slugs of plugins that the dependent requires. * * @since 6.5.0 * * @param string $plugin_file The dependent plugin's filepath, relative to the plugins directory. * @return array An array of dependency plugin slugs. */ public static function get_dependencies( $plugin_file ) { if ( isset( self::$dependencies[ $plugin_file ] ) ) { return self::$dependencies[ $plugin_file ]; } return array(); } /** * Gets a dependent plugin's filepath. * * @since 6.5.0 * * @param string $slug The dependent plugin's slug. * @return string|false The dependent plugin's filepath, relative to the plugins directory, * or false if the plugin has no dependencies. */ public static function get_dependent_filepath( $slug ) { $filepath = array_search( $slug, self::$dependent_slugs, true ); return $filepath ? $filepath : false; } /** * Determines whether the plugin has unmet dependencies. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return bool Whether the plugin has unmet dependencies. */ public static function has_unmet_dependencies( $plugin_file ) { if ( ! isset( self::$dependencies[ $plugin_file ] ) ) { return false; } require_once ABSPATH . 'wp-admin/includes/plugin.php'; foreach ( self::$dependencies[ $plugin_file ] as $dependency ) { $dependency_filepath = self::get_dependency_filepath( $dependency ); if ( false === $dependency_filepath || is_plugin_inactive( $dependency_filepath ) ) { return true; } } return false; } /** * Determines whether the plugin has a circular dependency. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return bool Whether the plugin has a circular dependency. */ public static function has_circular_dependency( $plugin_file ) { if ( ! is_array( self::$circular_dependencies_slugs ) ) { self::get_circular_dependencies(); } if ( ! empty( self::$circular_dependencies_slugs ) ) { $slug = self::convert_to_slug( $plugin_file ); if ( in_array( $slug, self::$circular_dependencies_slugs, true ) ) { return true; } } return false; } /** * Gets the names of plugins that require the plugin. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return array An array of dependent names. */ public static function get_dependent_names( $plugin_file ) { $dependent_names = array(); $plugins = self::get_plugins(); $slug = self::convert_to_slug( $plugin_file ); foreach ( self::get_dependents( $slug ) as $dependent ) { $dependent_names[ $dependent ] = $plugins[ $dependent ]['Name']; } sort( $dependent_names ); return $dependent_names; } /** * Gets the names of plugins required by the plugin. * * @since 6.5.0 * * @param string $plugin_file The dependent plugin's filepath, relative to the plugins directory. * @return array An array of dependency names. */ public static function get_dependency_names( $plugin_file ) { $dependency_api_data = self::get_dependency_api_data(); $dependencies = self::get_dependencies( $plugin_file ); $plugins = self::get_plugins(); $dependency_names = array(); foreach ( $dependencies as $dependency ) { // Use the name if it's available, otherwise fall back to the slug. if ( isset( $dependency_api_data[ $dependency ]['name'] ) ) { $name = $dependency_api_data[ $dependency ]['name']; } else { $dependency_filepath = self::get_dependency_filepath( $dependency ); if ( false !== $dependency_filepath ) { $name = $plugins[ $dependency_filepath ]['Name']; } else { $name = $dependency; } } $dependency_names[ $dependency ] = $name; } return $dependency_names; } /** * Gets the filepath for a dependency, relative to the plugin's directory. * * @since 6.5.0 * * @param string $slug The dependency's slug. * @return string|false If installed, the dependency's filepath relative to the plugins directory, otherwise false. */ public static function get_dependency_filepath( $slug ) { $dependency_filepaths = self::get_dependency_filepaths(); if ( ! isset( $dependency_filepaths[ $slug ] ) ) { return false; } return $dependency_filepaths[ $slug ]; } /** * Returns API data for the dependency. * * @since 6.5.0 * * @param string $slug The dependency's slug. * @return array|false The dependency's API data on success, otherwise false. */ public static function get_dependency_data( $slug ) { $dependency_api_data = self::get_dependency_api_data(); if ( isset( $dependency_api_data[ $slug ] ) ) { return $dependency_api_data[ $slug ]; } return false; } /** * Displays an admin notice if dependencies are not installed. * * @since 6.5.0 */ public static function display_admin_notice_for_unmet_dependencies() { if ( in_array( false, self::get_dependency_filepaths(), true ) ) { $error_message = __( 'Some required plugins are missing or inactive.' ); if ( is_multisite() ) { if ( current_user_can( 'manage_network_plugins' ) ) { $error_message .= ' ' . sprintf( /* translators: %s: Link to the network plugins page. */ __( '<a href="%s">Manage plugins</a>.' ), esc_url( network_admin_url( 'plugins.php' ) ) ); } else { $error_message .= ' ' . __( 'Please contact your network administrator.' ); } } elseif ( 'plugins' !== get_current_screen()->base ) { $error_message .= ' ' . sprintf( /* translators: %s: Link to the plugins page. */ __( '<a href="%s">Manage plugins</a>.' ), esc_url( admin_url( 'plugins.php' ) ) ); } wp_admin_notice( $error_message, array( 'type' => 'warning', ) ); } } /** * Displays an admin notice if circular dependencies are installed. * * @since 6.5.0 */ public static function display_admin_notice_for_circular_dependencies() { $circular_dependencies = self::get_circular_dependencies(); if ( ! empty( $circular_dependencies ) && count( $circular_dependencies ) > 1 ) { $circular_dependencies = array_unique( $circular_dependencies, SORT_REGULAR ); $plugins = self::get_plugins(); $plugin_dirnames = self::get_plugin_dirnames(); // Build output lines. $circular_dependency_lines = ''; foreach ( $circular_dependencies as $circular_dependency ) { $first_filepath = $plugin_dirnames[ $circular_dependency[0] ]; $second_filepath = $plugin_dirnames[ $circular_dependency[1] ]; $circular_dependency_lines .= sprintf( /* translators: 1: First plugin name, 2: Second plugin name. */ '<li>' . _x( '%1$s requires %2$s', 'The first plugin requires the second plugin.' ) . '</li>', '<strong>' . esc_html( $plugins[ $first_filepath ]['Name'] ) . '</strong>', '<strong>' . esc_html( $plugins[ $second_filepath ]['Name'] ) . '</strong>' ); } wp_admin_notice( sprintf( '<p>%1$s</p><ul>%2$s</ul><p>%3$s</p>', __( 'These plugins cannot be activated because their requirements are invalid.' ), $circular_dependency_lines, __( 'Please contact the plugin authors for more information.' ) ), array( 'type' => 'warning', 'paragraph_wrap' => false, ) ); } } /** * Checks plugin dependencies after a plugin is installed via AJAX. * * @since 6.5.0 */ public static function check_plugin_dependencies_during_ajax() { check_ajax_referer( 'updates' ); if ( empty( $_POST['slug'] ) ) { wp_send_json_error( array( 'slug' => '', 'pluginName' => '', 'errorCode' => 'no_plugin_specified', 'errorMessage' => __( 'No plugin specified.' ), ) ); } $slug = sanitize_key( wp_unslash( $_POST['slug'] ) ); $status = array( 'slug' => $slug ); self::get_plugins(); self::get_plugin_dirnames(); if ( ! isset( self::$plugin_dirnames[ $slug ] ) ) { $status['errorCode'] = 'plugin_not_installed'; $status['errorMessage'] = __( 'The plugin is not installed.' ); wp_send_json_error( $status ); } $plugin_file = self::$plugin_dirnames[ $slug ]; $status['pluginName'] = self::$plugins[ $plugin_file ]['Name']; $status['plugin'] = $plugin_file; if ( current_user_can( 'activate_plugin', $plugin_file ) && is_plugin_inactive( $plugin_file ) ) { $status['activateUrl'] = add_query_arg( array( '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $plugin_file ), 'action' => 'activate', 'plugin' => $plugin_file, ), is_multisite() ? network_admin_url( 'plugins.php' ) : admin_url( 'plugins.php' ) ); } if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) { $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] ); } self::initialize(); $dependencies = self::get_dependencies( $plugin_file ); if ( empty( $dependencies ) ) { $status['message'] = __( 'The plugin has no required plugins.' ); wp_send_json_success( $status ); } require_once ABSPATH . 'wp-admin/includes/plugin.php'; $inactive_dependencies = array(); foreach ( $dependencies as $dependency ) { if ( false === self::$plugin_dirnames[ $dependency ] || is_plugin_inactive( self::$plugin_dirnames[ $dependency ] ) ) { $inactive_dependencies[] = $dependency; } } if ( ! empty( $inactive_dependencies ) ) { $inactive_dependency_names = array_map( function ( $dependency ) { if ( isset( self::$dependency_api_data[ $dependency ]['Name'] ) ) { $inactive_dependency_name = self::$dependency_api_data[ $dependency ]['Name']; } else { $inactive_dependency_name = $dependency; } return $inactive_dependency_name; }, $inactive_dependencies ); $status['errorCode'] = 'inactive_dependencies'; $status['errorMessage'] = sprintf( /* translators: %s: A list of inactive dependency plugin names. */ __( 'The following plugins must be activated first: %s.' ), implode( ', ', $inactive_dependency_names ) ); $status['errorData'] = array_combine( $inactive_dependencies, $inactive_dependency_names ); wp_send_json_error( $status ); } $status['message'] = __( 'All required plugins are installed and activated.' ); wp_send_json_success( $status ); } /** * Gets data for installed plugins. * * @since 6.5.0 * * @return array An array of plugin data. */ protected static function get_plugins() { if ( is_array( self::$plugins ) ) { return self::$plugins; } require_once ABSPATH . 'wp-admin/includes/plugin.php'; self::$plugins = get_plugins(); return self::$plugins; } /** * Reads and stores dependency slugs from a plugin's 'Requires Plugins' header. * * @since 6.5.0 */ protected static function read_dependencies_from_plugin_headers() { self::$dependencies = array(); self::$dependency_slugs = array(); self::$dependent_slugs = array(); $plugins = self::get_plugins(); foreach ( $plugins as $plugin => $header ) { if ( '' === $header['RequiresPlugins'] ) { continue; } $dependency_slugs = self::sanitize_dependency_slugs( $header['RequiresPlugins'] ); self::$dependencies[ $plugin ] = $dependency_slugs; self::$dependency_slugs = array_merge( self::$dependency_slugs, $dependency_slugs ); $dependent_slug = self::convert_to_slug( $plugin ); self::$dependent_slugs[ $plugin ] = $dependent_slug; } self::$dependency_slugs = array_unique( self::$dependency_slugs ); } /** * Sanitizes slugs. * * @since 6.5.0 * * @param string $slugs A comma-separated string of plugin dependency slugs. * @return array An array of sanitized plugin dependency slugs. */ protected static function sanitize_dependency_slugs( $slugs ) { $sanitized_slugs = array(); $slugs = explode( ',', $slugs ); foreach ( $slugs as $slug ) { $slug = trim( $slug ); /** * Filters a plugin dependency's slug before matching to * the WordPress.org slug format. * * Can be used to switch between free and premium plugin slugs, for example. * * @since 6.5.0 * * @param string $slug The slug. */ $slug = apply_filters( 'wp_plugin_dependencies_slug', $slug ); // Match to WordPress.org slug format. if ( preg_match( '/^[a-z0-9]+(-[a-z0-9]+)*$/mu', $slug ) ) { $sanitized_slugs[] = $slug; } } $sanitized_slugs = array_unique( $sanitized_slugs ); sort( $sanitized_slugs ); return $sanitized_slugs; } /** * Gets the filepath of installed dependencies. * If a dependency is not installed, the filepath defaults to false. * * @since 6.5.0 * * @return array An array of install dependencies filepaths, relative to the plugins directory. */ protected static function get_dependency_filepaths() { if ( is_array( self::$dependency_filepaths ) ) { return self::$dependency_filepaths; } if ( null === self::$dependency_slugs ) { return array(); } self::$dependency_filepaths = array(); $plugin_dirnames = self::get_plugin_dirnames(); foreach ( self::$dependency_slugs as $slug ) { if ( isset( $plugin_dirnames[ $slug ] ) ) { self::$dependency_filepaths[ $slug ] = $plugin_dirnames[ $slug ]; continue; } self::$dependency_filepaths[ $slug ] = false; } return self::$dependency_filepaths; } /** * Retrieves and stores dependency plugin data from the WordPress.org Plugin API. * * @since 6.5.0 * * @global string $pagenow The filename of the current screen. * * @return array|void An array of dependency API data, or void on early exit. */ protected static function get_dependency_api_data() { global $pagenow; if ( ! is_admin() || ( 'plugins.php' !== $pagenow && 'plugin-install.php' !== $pagenow ) ) { return; } if ( is_array( self::$dependency_api_data ) ) { return self::$dependency_api_data; } $plugins = self::get_plugins(); self::$dependency_api_data = (array) get_site_transient( 'wp_plugin_dependencies_plugin_data' ); foreach ( self::$dependency_slugs as $slug ) { // Set transient for individual data, remove from self::$dependency_api_data if transient expired. if ( ! get_site_transient( "wp_plugin_dependencies_plugin_timeout_{$slug}" ) ) { unset( self::$dependency_api_data[ $slug ] ); set_site_transient( "wp_plugin_dependencies_plugin_timeout_{$slug}", true, 12 * HOUR_IN_SECONDS ); } if ( isset( self::$dependency_api_data[ $slug ] ) ) { if ( false === self::$dependency_api_data[ $slug ] ) { $dependency_file = self::get_dependency_filepath( $slug ); if ( false === $dependency_file ) { self::$dependency_api_data[ $slug ] = array( 'Name' => $slug ); } else { self::$dependency_api_data[ $slug ] = array( 'Name' => $plugins[ $dependency_file ]['Name'] ); } continue; } // Don't hit the Plugin API if data exists. if ( ! empty( self::$dependency_api_data[ $slug ]['last_updated'] ) ) { continue; } } if ( ! function_exists( 'plugins_api' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; } $information = plugins_api( 'plugin_information', array( 'slug' => $slug, 'fields' => array( 'short_description' => true, 'icons' => true, ), ) ); if ( is_wp_error( $information ) ) { continue; } self::$dependency_api_data[ $slug ] = (array) $information; // plugins_api() returns 'name' not 'Name'. self::$dependency_api_data[ $slug ]['Name'] = self::$dependency_api_data[ $slug ]['name']; set_site_transient( 'wp_plugin_dependencies_plugin_data', self::$dependency_api_data, 0 ); } // Remove from self::$dependency_api_data if slug no longer a dependency. $differences = array_diff( array_keys( self::$dependency_api_data ), self::$dependency_slugs ); foreach ( $differences as $difference ) { unset( self::$dependency_api_data[ $difference ] ); } ksort( self::$dependency_api_data ); // Remove empty elements. self::$dependency_api_data = array_filter( self::$dependency_api_data ); set_site_transient( 'wp_plugin_dependencies_plugin_data', self::$dependency_api_data, 0 ); return self::$dependency_api_data; } /** * Gets plugin directory names. * * @since 6.5.0 * * @return array An array of plugin directory names. */ protected static function get_plugin_dirnames() { if ( is_array( self::$plugin_dirnames ) ) { return self::$plugin_dirnames; } self::$plugin_dirnames = array(); $plugin_files = array_keys( self::get_plugins() ); foreach ( $plugin_files as $plugin_file ) { $slug = self::convert_to_slug( $plugin_file ); self::$plugin_dirnames[ $slug ] = $plugin_file; } return self::$plugin_dirnames; } /** * Gets circular dependency data. * * @since 6.5.0 * * @return array[] An array of circular dependency pairings. */ protected static function get_circular_dependencies() { if ( is_array( self::$circular_dependencies_pairs ) ) { return self::$circular_dependencies_pairs; } if ( null === self::$dependencies ) { return array(); } self::$circular_dependencies_slugs = array(); self::$circular_dependencies_pairs = array(); foreach ( self::$dependencies as $dependent => $dependencies ) { /* * $dependent is in 'a/a.php' format. Dependencies are stored as slugs, i.e. 'a'. * * Convert $dependent to slug format for checking. */ $dependent_slug = self::convert_to_slug( $dependent ); self::$circular_dependencies_pairs = array_merge( self::$circular_dependencies_pairs, self::check_for_circular_dependencies( array( $dependent_slug ), $dependencies ) ); } return self::$circular_dependencies_pairs; } /** * Checks for circular dependencies. * * @since 6.5.0 * * @param array $dependents Array of dependent plugins. * @param array $dependencies Array of plugins dependencies. * @return array A circular dependency pairing, or an empty array if none exists. */ protected static function check_for_circular_dependencies( $dependents, $dependencies ) { $circular_dependencies_pairs = array(); // Check for a self-dependency. $dependents_location_in_its_own_dependencies = array_intersect( $dependents, $dependencies ); if ( ! empty( $dependents_location_in_its_own_dependencies ) ) { foreach ( $dependents_location_in_its_own_dependencies as $self_dependency ) { self::$circular_dependencies_slugs[] = $self_dependency; $circular_dependencies_pairs[] = array( $self_dependency, $self_dependency ); // No need to check for itself again. unset( $dependencies[ array_search( $self_dependency, $dependencies, true ) ] ); } } /* * Check each dependency to see: * 1. If it has dependencies. * 2. If its list of dependencies includes one of its own dependents. */ foreach ( $dependencies as $dependency ) { // Check if the dependency is also a dependent. $dependency_location_in_dependents = array_search( $dependency, self::$dependent_slugs, true ); if ( false !== $dependency_location_in_dependents ) { $dependencies_of_the_dependency = self::$dependencies[ $dependency_location_in_dependents ]; foreach ( $dependents as $dependent ) { // Check if its dependencies includes one of its own dependents. $dependent_location_in_dependency_dependencies = array_search( $dependent, $dependencies_of_the_dependency, true ); if ( false !== $dependent_location_in_dependency_dependencies ) { self::$circular_dependencies_slugs[] = $dependent; self::$circular_dependencies_slugs[] = $dependency; $circular_dependencies_pairs[] = array( $dependent, $dependency ); // Remove the dependent from its dependency's dependencies. unset( $dependencies_of_the_dependency[ $dependent_location_in_dependency_dependencies ] ); } } $dependents[] = $dependency; /* * Now check the dependencies of the dependency's dependencies for the dependent. * * Yes, that does make sense. */ $circular_dependencies_pairs = array_merge( $circular_dependencies_pairs, self::check_for_circular_dependencies( $dependents, array_unique( $dependencies_of_the_dependency ) ) ); } } return $circular_dependencies_pairs; } /** * Converts a plugin filepath to a slug. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return string The plugin's slug. */ protected static function convert_to_slug( $plugin_file ) { if ( 'hello.php' === $plugin_file ) { return 'hello-dolly'; } return str_contains( $plugin_file, '/' ) ? dirname( $plugin_file ) : str_replace( '.php', '', $plugin_file ); } } rewrite.php 0000666 00000046042 15226762357 0006767 0 ustar 00 <?php /** * WordPress Rewrite API * * @package WordPress * @subpackage Rewrite */ /** * Endpoint mask that matches nothing. * * @since 2.1.0 */ define( 'EP_NONE', 0 ); /** * Endpoint mask that matches post permalinks. * * @since 2.1.0 */ define( 'EP_PERMALINK', 1 ); /** * Endpoint mask that matches attachment permalinks. * * @since 2.1.0 */ define( 'EP_ATTACHMENT', 2 ); /** * Endpoint mask that matches any date archives. * * @since 2.1.0 */ define( 'EP_DATE', 4 ); /** * Endpoint mask that matches yearly archives. * * @since 2.1.0 */ define( 'EP_YEAR', 8 ); /** * Endpoint mask that matches monthly archives. * * @since 2.1.0 */ define( 'EP_MONTH', 16 ); /** * Endpoint mask that matches daily archives. * * @since 2.1.0 */ define( 'EP_DAY', 32 ); /** * Endpoint mask that matches the site root. * * @since 2.1.0 */ define( 'EP_ROOT', 64 ); /** * Endpoint mask that matches comment feeds. * * @since 2.1.0 */ define( 'EP_COMMENTS', 128 ); /** * Endpoint mask that matches searches. * * Note that this only matches a search at a "pretty" URL such as * `/search/my-search-term`, not `?s=my-search-term`. * * @since 2.1.0 */ define( 'EP_SEARCH', 256 ); /** * Endpoint mask that matches category archives. * * @since 2.1.0 */ define( 'EP_CATEGORIES', 512 ); /** * Endpoint mask that matches tag archives. * * @since 2.3.0 */ define( 'EP_TAGS', 1024 ); /** * Endpoint mask that matches author archives. * * @since 2.1.0 */ define( 'EP_AUTHORS', 2048 ); /** * Endpoint mask that matches pages. * * @since 2.1.0 */ define( 'EP_PAGES', 4096 ); /** * Endpoint mask that matches all archive views. * * @since 3.7.0 */ define( 'EP_ALL_ARCHIVES', EP_DATE | EP_YEAR | EP_MONTH | EP_DAY | EP_CATEGORIES | EP_TAGS | EP_AUTHORS ); /** * Endpoint mask that matches everything. * * @since 2.1.0 */ define( 'EP_ALL', EP_PERMALINK | EP_ATTACHMENT | EP_ROOT | EP_COMMENTS | EP_SEARCH | EP_PAGES | EP_ALL_ARCHIVES ); /** * Adds a rewrite rule that transforms a URL structure to a set of query vars. * * Any value in the $after parameter that isn't 'bottom' will result in the rule * being placed at the top of the rewrite rules. * * @since 2.1.0 * @since 4.4.0 Array support was added to the `$query` parameter. * * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * * @param string $regex Regular expression to match request against. * @param string|array $query The corresponding query vars for this rewrite rule. * @param string $after Optional. Priority of the new rule. Accepts 'top' * or 'bottom'. Default 'bottom'. */ function add_rewrite_rule( $regex, $query, $after = 'bottom' ) { global $wp_rewrite; $wp_rewrite->add_rule( $regex, $query, $after ); } /** * Adds a new rewrite tag (like %postname%). * * The `$query` parameter is optional. If it is omitted you must ensure that you call * this on, or before, the {@see 'init'} hook. This is because `$query` defaults to * `$tag=`, and for this to work a new query var has to be added. * * @since 2.1.0 * * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * @global WP $wp Current WordPress environment instance. * * @param string $tag Name of the new rewrite tag. * @param string $regex Regular expression to substitute the tag for in rewrite rules. * @param string $query Optional. String to append to the rewritten query. Must end in '='. Default empty. */ function add_rewrite_tag( $tag, $regex, $query = '' ) { // Validate the tag's name. if ( strlen( $tag ) < 3 || '%' !== $tag[0] || '%' !== $tag[ strlen( $tag ) - 1 ] ) { return; } global $wp_rewrite, $wp; if ( empty( $query ) ) { $qv = trim( $tag, '%' ); $wp->add_query_var( $qv ); $query = $qv . '='; } $wp_rewrite->add_rewrite_tag( $tag, $regex, $query ); } /** * Removes an existing rewrite tag (like %postname%). * * @since 4.5.0 * * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * * @param string $tag Name of the rewrite tag. */ function remove_rewrite_tag( $tag ) { global $wp_rewrite; $wp_rewrite->remove_rewrite_tag( $tag ); } /** * Adds a permalink structure. * * @since 3.0.0 * * @see WP_Rewrite::add_permastruct() * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * * @param string $name Name for permalink structure. * @param string $struct Permalink structure. * @param array $args Optional. Arguments for building the rules from the permalink structure, * see WP_Rewrite::add_permastruct() for full details. Default empty array. */ function add_permastruct( $name, $struct, $args = array() ) { global $wp_rewrite; // Back-compat for the old parameters: $with_front and $ep_mask. if ( ! is_array( $args ) ) { $args = array( 'with_front' => $args ); } if ( func_num_args() === 4 ) { $args['ep_mask'] = func_get_arg( 3 ); } $wp_rewrite->add_permastruct( $name, $struct, $args ); } /** * Removes a permalink structure. * * Can only be used to remove permastructs that were added using add_permastruct(). * Built-in permastructs cannot be removed. * * @since 4.5.0 * * @see WP_Rewrite::remove_permastruct() * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * * @param string $name Name for permalink structure. */ function remove_permastruct( $name ) { global $wp_rewrite; $wp_rewrite->remove_permastruct( $name ); } /** * Adds a new feed type like /atom1/. * * @since 2.1.0 * * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * * @param string $feedname Feed name. Should not start with '_'. * @param callable $callback Callback to run on feed display. * @return string Feed action name. */ function add_feed( $feedname, $callback ) { global $wp_rewrite; if ( ! in_array( $feedname, $wp_rewrite->feeds, true ) ) { $wp_rewrite->feeds[] = $feedname; } $hook = 'do_feed_' . $feedname; // Remove default function hook. remove_action( $hook, $hook ); add_action( $hook, $callback, 10, 2 ); return $hook; } /** * Removes rewrite rules and then recreate rewrite rules. * * @since 3.0.0 * * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * * @param bool $hard Whether to update .htaccess (hard flush) or just update * rewrite_rules option (soft flush). Default is true (hard). */ function flush_rewrite_rules( $hard = true ) { global $wp_rewrite; if ( is_callable( array( $wp_rewrite, 'flush_rules' ) ) ) { $wp_rewrite->flush_rules( $hard ); } } /** * Adds an endpoint, like /trackback/. * * Adding an endpoint creates extra rewrite rules for each of the matching * places specified by the provided bitmask. For example: * * add_rewrite_endpoint( 'json', EP_PERMALINK | EP_PAGES ); * * will add a new rewrite rule ending with "json(/(.*))?/?$" for every permastruct * that describes a permalink (post) or page. This is rewritten to "json=$match" * where $match is the part of the URL matched by the endpoint regex (e.g. "foo" in * "[permalink]/json/foo/"). * * A new query var with the same name as the endpoint will also be created. * * When specifying $places ensure that you are using the EP_* constants (or a * combination of them using the bitwise OR operator) as their values are not * guaranteed to remain static (especially `EP_ALL`). * * Be sure to flush the rewrite rules - see flush_rewrite_rules() - when your plugin gets * activated and deactivated. * * @since 2.1.0 * @since 4.3.0 Added support for skipping query var registration by passing `false` to `$query_var`. * * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * * @param string $name Name of the endpoint. * @param int $places Endpoint mask describing the places the endpoint should be added. * Accepts a mask of: * - `EP_ALL` * - `EP_NONE` * - `EP_ALL_ARCHIVES` * - `EP_ATTACHMENT` * - `EP_AUTHORS` * - `EP_CATEGORIES` * - `EP_COMMENTS` * - `EP_DATE` * - `EP_DAY` * - `EP_MONTH` * - `EP_PAGES` * - `EP_PERMALINK` * - `EP_ROOT` * - `EP_SEARCH` * - `EP_TAGS` * - `EP_YEAR` * @param string|bool $query_var Name of the corresponding query variable. Pass `false` to skip registering a query_var * for this endpoint. Defaults to the value of `$name`. */ function add_rewrite_endpoint( $name, $places, $query_var = true ) { global $wp_rewrite; $wp_rewrite->add_endpoint( $name, $places, $query_var ); } /** * Filters the URL base for taxonomies. * * To remove any manually prepended /index.php/. * * @access private * @since 2.6.0 * * @param string $base The taxonomy base that we're going to filter * @return string */ function _wp_filter_taxonomy_base( $base ) { if ( ! empty( $base ) ) { $base = preg_replace( '|^/index\.php/|', '', $base ); $base = trim( $base, '/' ); } return $base; } /** * Resolves numeric slugs that collide with date permalinks. * * Permalinks of posts with numeric slugs can sometimes look to WP_Query::parse_query() * like a date archive, as when your permalink structure is `/%year%/%postname%/` and * a post with post_name '05' has the URL `/2015/05/`. * * This function detects conflicts of this type and resolves them in favor of the * post permalink. * * Note that, since 4.3.0, wp_unique_post_slug() prevents the creation of post slugs * that would result in a date archive conflict. The resolution performed in this * function is primarily for legacy content, as well as cases when the admin has changed * the site's permalink structure in a way that introduces URL conflicts. * * @since 4.3.0 * * @param array $query_vars Optional. Query variables for setting up the loop, as determined in * WP::parse_request(). Default empty array. * @return array Returns the original array of query vars, with date/post conflicts resolved. */ function wp_resolve_numeric_slug_conflicts( $query_vars = array() ) { if ( ! isset( $query_vars['year'] ) && ! isset( $query_vars['monthnum'] ) && ! isset( $query_vars['day'] ) ) { return $query_vars; } // Identify the 'postname' position in the permastruct array. $permastructs = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) ); $postname_index = array_search( '%postname%', $permastructs, true ); if ( false === $postname_index ) { return $query_vars; } /* * A numeric slug could be confused with a year, month, or day, depending on position. To account for * the possibility of post pagination (eg 2015/2 for the second page of a post called '2015'), our * `is_*` checks are generous: check for year-slug clashes when `is_year` *or* `is_month`, and check * for month-slug clashes when `is_month` *or* `is_day`. */ $compare = ''; if ( 0 === $postname_index && ( isset( $query_vars['year'] ) || isset( $query_vars['monthnum'] ) ) ) { $compare = 'year'; } elseif ( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && ( isset( $query_vars['monthnum'] ) || isset( $query_vars['day'] ) ) ) { $compare = 'monthnum'; } elseif ( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && isset( $query_vars['day'] ) ) { $compare = 'day'; } if ( ! $compare ) { return $query_vars; } // This is the potentially clashing slug. $value = ''; if ( array_key_exists( $compare, $query_vars ) ) { $value = $query_vars[ $compare ]; } $post = get_page_by_path( $value, OBJECT, 'post' ); if ( ! ( $post instanceof WP_Post ) ) { return $query_vars; } // If the date of the post doesn't match the date specified in the URL, resolve to the date archive. if ( preg_match( '/^([0-9]{4})\-([0-9]{2})/', $post->post_date, $matches ) && isset( $query_vars['year'] ) && ( 'monthnum' === $compare || 'day' === $compare ) ) { // $matches[1] is the year the post was published. if ( (int) $query_vars['year'] !== (int) $matches[1] ) { return $query_vars; } // $matches[2] is the month the post was published. if ( 'day' === $compare && isset( $query_vars['monthnum'] ) && (int) $query_vars['monthnum'] !== (int) $matches[2] ) { return $query_vars; } } /* * If the located post contains nextpage pagination, then the URL chunk following postname may be * intended as the page number. Verify that it's a valid page before resolving to it. */ $maybe_page = ''; if ( 'year' === $compare && isset( $query_vars['monthnum'] ) ) { $maybe_page = $query_vars['monthnum']; } elseif ( 'monthnum' === $compare && isset( $query_vars['day'] ) ) { $maybe_page = $query_vars['day']; } // Bug found in #11694 - 'page' was returning '/4'. $maybe_page = (int) trim( $maybe_page, '/' ); $post_page_count = substr_count( $post->post_content, '<!--nextpage-->' ) + 1; // If the post doesn't have multiple pages, but a 'page' candidate is found, resolve to the date archive. if ( 1 === $post_page_count && $maybe_page ) { return $query_vars; } // If the post has multiple pages and the 'page' number isn't valid, resolve to the date archive. if ( $post_page_count > 1 && $maybe_page > $post_page_count ) { return $query_vars; } // If we've gotten to this point, we have a slug/date clash. First, adjust for nextpage. $query_vars['page'] = $maybe_page; // Next, unset autodetected date-related query vars. unset( $query_vars['year'] ); unset( $query_vars['monthnum'] ); unset( $query_vars['day'] ); // Then, set the identified post. $query_vars['name'] = $post->post_name; // Finally, return the modified query vars. return $query_vars; } /** * Examines a URL and try to determine the post ID it represents. * * Checks are supposedly from the hosted site blog. * * @since 1.0.0 * * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * @global WP $wp Current WordPress environment instance. * * @param string $url Permalink to check. * @return int Post ID, or 0 on failure. */ function url_to_postid( $url ) { global $wp_rewrite; /** * Filters the URL to derive the post ID from. * * @since 2.2.0 * * @param string $url The URL to derive the post ID from. */ $url = apply_filters( 'url_to_postid', $url ); $url_host = parse_url( $url, PHP_URL_HOST ); if ( is_string( $url_host ) ) { $url_host = str_replace( 'www.', '', $url_host ); } else { $url_host = ''; } $home_url_host = parse_url( home_url(), PHP_URL_HOST ); if ( is_string( $home_url_host ) ) { $home_url_host = str_replace( 'www.', '', $home_url_host ); } else { $home_url_host = ''; } // Bail early if the URL does not belong to this site. if ( $url_host && $url_host !== $home_url_host ) { return 0; } // First, check to see if there is a 'p=N' or 'page_id=N' to match against. if ( preg_match( '#[?&](p|page_id|attachment_id)=(\d+)#', $url, $values ) ) { $id = absint( $values[2] ); if ( $id ) { return $id; } } // Get rid of the #anchor. $url_split = explode( '#', $url ); $url = $url_split[0]; // Get rid of URL ?query=string. $url_split = explode( '?', $url ); $url = $url_split[0]; // Set the correct URL scheme. $scheme = parse_url( home_url(), PHP_URL_SCHEME ); $url = set_url_scheme( $url, $scheme ); // Add 'www.' if it is absent and should be there. if ( str_contains( home_url(), '://www.' ) && ! str_contains( $url, '://www.' ) ) { $url = str_replace( '://', '://www.', $url ); } // Strip 'www.' if it is present and shouldn't be. if ( ! str_contains( home_url(), '://www.' ) ) { $url = str_replace( '://www.', '://', $url ); } if ( trim( $url, '/' ) === home_url() && 'page' === get_option( 'show_on_front' ) ) { $page_on_front = get_option( 'page_on_front' ); if ( $page_on_front && get_post( $page_on_front ) instanceof WP_Post ) { return (int) $page_on_front; } } // Check to see if we are using rewrite rules. $rewrite = $wp_rewrite->wp_rewrite_rules(); // Not using rewrite rules, and 'p=N' and 'page_id=N' methods failed, so we're out of options. if ( empty( $rewrite ) ) { return 0; } // Strip 'index.php/' if we're not using path info permalinks. if ( ! $wp_rewrite->using_index_permalinks() ) { $url = str_replace( $wp_rewrite->index . '/', '', $url ); } if ( str_contains( trailingslashit( $url ), home_url( '/' ) ) ) { // Chop off http://domain.com/[path]. $url = str_replace( home_url(), '', $url ); } else { // Chop off /path/to/blog. $home_path = parse_url( home_url( '/' ) ); $home_path = isset( $home_path['path'] ) ? $home_path['path'] : ''; $url = preg_replace( sprintf( '#^%s#', preg_quote( $home_path ) ), '', trailingslashit( $url ) ); } // Trim leading and lagging slashes. $url = trim( $url, '/' ); $request = $url; $post_type_query_vars = array(); foreach ( get_post_types( array(), 'objects' ) as $post_type => $t ) { if ( ! empty( $t->query_var ) ) { $post_type_query_vars[ $t->query_var ] = $post_type; } } // Look for matches. $request_match = $request; foreach ( (array) $rewrite as $match => $query ) { /* * If the requesting file is the anchor of the match, * prepend it to the path info. */ if ( ! empty( $url ) && ( $url !== $request ) && str_starts_with( $match, $url ) ) { $request_match = $url . '/' . $request; } if ( preg_match( "#^$match#", $request_match, $matches ) ) { if ( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)\]/', $query, $varmatch ) ) { // This is a verbose page match, let's check to be sure about it. $page = get_page_by_path( $matches[ $varmatch[1] ] ); if ( ! $page ) { continue; } $post_status_obj = get_post_status_object( $page->post_status ); if ( ! $post_status_obj->public && ! $post_status_obj->protected && ! $post_status_obj->private && $post_status_obj->exclude_from_search ) { continue; } } /* * Got a match. * Trim the query of everything up to the '?'. */ $query = preg_replace( '!^.+\?!', '', $query ); // Substitute the substring matches into the query. $query = addslashes( WP_MatchesMapRegex::apply( $query, $matches ) ); // Filter out non-public query vars. global $wp; parse_str( $query, $query_vars ); $query = array(); foreach ( (array) $query_vars as $key => $value ) { if ( in_array( (string) $key, $wp->public_query_vars, true ) ) { $query[ $key ] = $value; if ( isset( $post_type_query_vars[ $key ] ) ) { $query['post_type'] = $post_type_query_vars[ $key ]; $query['name'] = $value; } } } // Resolve conflicts between posts with numeric slugs and date archive queries. $query = wp_resolve_numeric_slug_conflicts( $query ); // Do the query. $query = new WP_Query( $query ); if ( ! empty( $query->posts ) && $query->is_singular ) { return $query->post->ID; } else { return 0; } } } return 0; } class-wp-customize-new-menu-section.php 0000666 00000003235 15226762357 0014247 0 ustar 00 <?php /** * Customize API: WP_Customize_New_Menu_Section class * * @package WordPress * @subpackage Customize * @since 4.4.0 * @deprecated 4.9.0 This file is no longer used as of the menu creation UX introduced in #40104. */ _deprecated_file( basename( __FILE__ ), '4.9.0' ); /** * Customize Menu Section Class * * @since 4.3.0 * @deprecated 4.9.0 This class is no longer used as of the menu creation UX introduced in #40104. * * @see WP_Customize_Section */ class WP_Customize_New_Menu_Section extends WP_Customize_Section { /** * Control type. * * @since 4.3.0 * @var string */ public $type = 'new_menu'; /** * Constructor. * * Any supplied $args override class property defaults. * * @since 4.9.0 * @deprecated 4.9.0 * * @param WP_Customize_Manager $manager Customizer bootstrap instance. * @param string $id A specific ID of the section. * @param array $args Section arguments. */ public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) { _deprecated_function( __METHOD__, '4.9.0' ); parent::__construct( $manager, $id, $args ); } /** * Render the section, and the controls that have been added to it. * * @since 4.3.0 * @deprecated 4.9.0 */ protected function render() { _deprecated_function( __METHOD__, '4.9.0' ); ?> <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="accordion-section-new-menu"> <button type="button" class="button add-new-menu-item add-menu-toggle" aria-expanded="false"> <?php echo esc_html( $this->title ); ?> </button> <ul class="new-menu-section-content"></ul> </li> <?php } } https-detection-20260605234503.php 0000666 00000013341 15226762357 0012003 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260605120939-20260621215521.php 0000666 00000013341 15226762357 0013372 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260605081716.php 0000666 00000013341 15226762357 0012011 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-customize-media-control.php 0000666 00000022275 15226762357 0013614 0 ustar 00 <?php /** * Customize API: WP_Customize_Media_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Media Control class. * * @since 4.2.0 * * @see WP_Customize_Control */ class WP_Customize_Media_Control extends WP_Customize_Control { /** * Control type. * * @since 4.2.0 * @var string */ public $type = 'media'; /** * Media control mime type. * * @since 4.2.0 * @var string */ public $mime_type = ''; /** * Button labels. * * @since 4.2.0 * @var array */ public $button_labels = array(); /** * Constructor. * * @since 4.1.0 * @since 4.2.0 Moved from WP_Customize_Upload_Control. * * @see WP_Customize_Control::__construct() * * @param WP_Customize_Manager $manager Customizer bootstrap instance. * @param string $id Control ID. * @param array $args Optional. Arguments to override class property defaults. * See WP_Customize_Control::__construct() for information * on accepted arguments. Default empty array. */ public function __construct( $manager, $id, $args = array() ) { parent::__construct( $manager, $id, $args ); $this->button_labels = wp_parse_args( $this->button_labels, $this->get_default_button_labels() ); } /** * Enqueue control related scripts/styles. * * @since 3.4.0 * @since 4.2.0 Moved from WP_Customize_Upload_Control. */ public function enqueue() { wp_enqueue_media(); } /** * Refresh the parameters passed to the JavaScript via JSON. * * @since 3.4.0 * @since 4.2.0 Moved from WP_Customize_Upload_Control. * * @see WP_Customize_Control::to_json() */ public function to_json() { parent::to_json(); $this->json['label'] = html_entity_decode( $this->label, ENT_QUOTES, get_bloginfo( 'charset' ) ); $this->json['mime_type'] = $this->mime_type; $this->json['button_labels'] = $this->button_labels; $this->json['canUpload'] = current_user_can( 'upload_files' ); $value = $this->value(); if ( is_object( $this->setting ) ) { if ( $this->setting->default ) { /* * Fake an attachment model - needs all fields used by template. * Note that the default value must be a URL, NOT an attachment ID. */ $ext = substr( $this->setting->default, -3 ); $type = in_array( $ext, array( 'jpg', 'png', 'gif', 'bmp', 'webp', 'avif' ), true ) ? 'image' : 'document'; $default_attachment = array( 'id' => 1, 'url' => $this->setting->default, 'type' => $type, 'icon' => wp_mime_type_icon( $type, '.svg' ), 'title' => wp_basename( $this->setting->default ), ); if ( 'image' === $type ) { $default_attachment['sizes'] = array( 'full' => array( 'url' => $this->setting->default ), ); } $this->json['defaultAttachment'] = $default_attachment; } if ( $value && $this->setting->default && $value === $this->setting->default ) { // Set the default as the attachment. $this->json['attachment'] = $this->json['defaultAttachment']; } elseif ( $value ) { $this->json['attachment'] = wp_prepare_attachment_for_js( $value ); } } } /** * Don't render any content for this control from PHP. * * @since 3.4.0 * @since 4.2.0 Moved from WP_Customize_Upload_Control. * * @see WP_Customize_Media_Control::content_template() */ public function render_content() {} /** * Render a JS template for the content of the media control. * * @since 4.1.0 * @since 4.2.0 Moved from WP_Customize_Upload_Control. */ public function content_template() { ?> <# var descriptionId = _.uniqueId( 'customize-media-control-description-' ); var describedByAttr = data.description ? ' aria-describedby="' + descriptionId + '" ' : ''; #> <# if ( data.label ) { #> <span class="customize-control-title">{{ data.label }}</span> <# } #> <div class="customize-control-notifications-container"></div> <# if ( data.description ) { #> <span id="{{ descriptionId }}" class="description customize-control-description">{{{ data.description }}}</span> <# } #> <# if ( data.attachment && data.attachment.id ) { #> <div class="attachment-media-view attachment-media-view-{{ data.attachment.type }} {{ data.attachment.orientation }}"> <div class="thumbnail thumbnail-{{ data.attachment.type }}"> <# if ( 'image' === data.attachment.type && data.attachment.sizes && data.attachment.sizes.medium ) { #> <img class="attachment-thumb" src="{{ data.attachment.sizes.medium.url }}" draggable="false" alt="" /> <# } else if ( 'image' === data.attachment.type && data.attachment.sizes && data.attachment.sizes.full ) { #> <img class="attachment-thumb" src="{{ data.attachment.sizes.full.url }}" draggable="false" alt="" /> <# } else if ( 'audio' === data.attachment.type ) { #> <# if ( data.attachment.image && data.attachment.image.src && data.attachment.image.src !== data.attachment.icon ) { #> <img src="{{ data.attachment.image.src }}" class="thumbnail" draggable="false" alt="" /> <# } else { #> <img src="{{ data.attachment.icon }}" class="attachment-thumb type-icon" draggable="false" alt="" /> <# } #> <p class="attachment-meta attachment-meta-title">“{{ data.attachment.title }}”</p> <# if ( data.attachment.album || data.attachment.meta.album ) { #> <p class="attachment-meta"><em>{{ data.attachment.album || data.attachment.meta.album }}</em></p> <# } #> <# if ( data.attachment.artist || data.attachment.meta.artist ) { #> <p class="attachment-meta">{{ data.attachment.artist || data.attachment.meta.artist }}</p> <# } #> <audio style="visibility: hidden" controls class="wp-audio-shortcode" width="100%" preload="none"> <source type="{{ data.attachment.mime }}" src="{{ data.attachment.url }}" /> </audio> <# } else if ( 'video' === data.attachment.type ) { #> <div class="wp-media-wrapper wp-video"> <video controls="controls" class="wp-video-shortcode" preload="metadata" <# if ( data.attachment.image && data.attachment.image.src !== data.attachment.icon ) { #>poster="{{ data.attachment.image.src }}"<# } #>> <source type="{{ data.attachment.mime }}" src="{{ data.attachment.url }}" /> </video> </div> <# } else { #> <img class="attachment-thumb type-icon icon" src="{{ data.attachment.icon }}" draggable="false" alt="" /> <p class="attachment-title">{{ data.attachment.title }}</p> <# } #> </div> <div class="actions"> <# if ( data.canUpload ) { #> <button type="button" class="button remove-button">{{ data.button_labels.remove }}</button> <button type="button" class="button upload-button control-focus" {{{ describedByAttr }}}>{{ data.button_labels.change }}</button> <# } #> </div> </div> <# } else { #> <div class="attachment-media-view"> <# if ( data.canUpload ) { #> <button type="button" class="upload-button button" {{{ describedByAttr }}}>{{ data.button_labels.select }}</button> <# } #> <div class="actions"> <# if ( data.defaultAttachment ) { #> <button type="button" class="button default-button">{{ data.button_labels['default'] }}</button> <# } #> </div> </div> <# } #> <?php } /** * Get default button labels. * * Provides an array of the default button labels based on the mime type of the current control. * * @since 4.9.0 * * @return string[] An associative array of default button labels keyed by the button name. */ public function get_default_button_labels() { // Get just the mime type and strip the mime subtype if present. $mime_type = ! empty( $this->mime_type ) ? strtok( ltrim( $this->mime_type, '/' ), '/' ) : 'default'; switch ( $mime_type ) { case 'video': return array( 'select' => __( 'Select video' ), 'change' => __( 'Change video' ), 'default' => __( 'Default' ), 'remove' => __( 'Remove' ), 'placeholder' => __( 'No video selected' ), 'frame_title' => __( 'Select video' ), 'frame_button' => __( 'Choose video' ), ); case 'audio': return array( 'select' => __( 'Select audio' ), 'change' => __( 'Change audio' ), 'default' => __( 'Default' ), 'remove' => __( 'Remove' ), 'placeholder' => __( 'No audio selected' ), 'frame_title' => __( 'Select audio' ), 'frame_button' => __( 'Choose audio' ), ); case 'image': return array( 'select' => __( 'Select image' ), 'site_icon' => __( 'Select Site Icon' ), 'change' => __( 'Change image' ), 'default' => __( 'Default' ), 'remove' => __( 'Remove' ), 'placeholder' => __( 'No image selected' ), 'frame_title' => __( 'Select image' ), 'frame_button' => __( 'Choose image' ), ); default: return array( 'select' => __( 'Select file' ), 'change' => __( 'Change file' ), 'default' => __( 'Default' ), 'remove' => __( 'Remove' ), 'placeholder' => __( 'No file selected' ), 'frame_title' => __( 'Select file' ), 'frame_button' => __( 'Choose file' ), ); } // End switch(). } } class-wp-editor.php 0000666 00000215217 15226762357 0010325 0 ustar 00 <?php /** * Facilitates adding of the WordPress editor as used on the Write and Edit screens. * * @package WordPress * @since 3.3.0 * * Private, not included by default. See wp_editor() in wp-includes/general-template.php. */ #[AllowDynamicProperties] final class _WP_Editors { public static $mce_locale; private static $mce_settings = array(); private static $qt_settings = array(); private static $plugins = array(); private static $qt_buttons = array(); private static $ext_plugins; private static $baseurl; private static $first_init; private static $this_tinymce = false; private static $this_quicktags = false; private static $has_tinymce = false; private static $has_quicktags = false; private static $has_medialib = false; private static $editor_buttons_css = true; private static $drag_drop_upload = false; private static $translation; private static $tinymce_scripts_printed = false; private static $link_dialog_printed = false; private function __construct() {} /** * Parse default arguments for the editor instance. * * @since 3.3.0 * * @param string $editor_id HTML ID for the textarea and TinyMCE and Quicktags instances. * Should not contain square brackets. * @param array $settings { * Array of editor arguments. * * @type bool $wpautop Whether to use wpautop(). Default true. * @type bool $media_buttons Whether to show the Add Media/other media buttons. * @type string $default_editor When both TinyMCE and Quicktags are used, set which * editor is shown on page load. Default empty. * @type bool $drag_drop_upload Whether to enable drag & drop on the editor uploading. Default false. * Requires the media modal. * @type string $textarea_name Give the textarea a unique name here. Square brackets * can be used here. Default $editor_id. * @type int $textarea_rows Number rows in the editor textarea. Default 20. * @type string|int $tabindex Tabindex value to use. Default empty. * @type string $tabfocus_elements The previous and next element ID to move the focus to * when pressing the Tab key in TinyMCE. Default ':prev,:next'. * @type string $editor_css Intended for extra styles for both Visual and Code editors. * Should include `<style>` tags, and can use "scoped". Default empty. * @type string $editor_class Extra classes to add to the editor textarea element. Default empty. * @type bool $teeny Whether to output the minimal editor config. Examples include * Press This and the Comment editor. Default false. * @type bool $dfw Deprecated in 4.1. Unused. * @type bool|array $tinymce Whether to load TinyMCE. Can be used to pass settings directly to * TinyMCE using an array. Default true. * @type bool|array $quicktags Whether to load Quicktags. Can be used to pass settings directly to * Quicktags using an array. Default true. * } * @return array Parsed arguments array. */ public static function parse_settings( $editor_id, $settings ) { /** * Filters the wp_editor() settings. * * @since 4.0.0 * * @see _WP_Editors::parse_settings() * * @param array $settings Array of editor arguments. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block' * when called from block editor's Classic block. */ $settings = apply_filters( 'wp_editor_settings', $settings, $editor_id ); $set = wp_parse_args( $settings, array( // Disable autop if the current post has blocks in it. 'wpautop' => ! has_blocks(), 'media_buttons' => true, 'default_editor' => '', 'drag_drop_upload' => false, 'textarea_name' => $editor_id, 'textarea_rows' => 20, 'tabindex' => '', 'tabfocus_elements' => ':prev,:next', 'editor_css' => '', 'editor_class' => '', 'teeny' => false, '_content_editor_dfw' => false, 'tinymce' => true, 'quicktags' => true, ) ); self::$this_tinymce = ( $set['tinymce'] && user_can_richedit() ); if ( self::$this_tinymce ) { if ( str_contains( $editor_id, '[' ) ) { self::$this_tinymce = false; _deprecated_argument( 'wp_editor()', '3.9.0', 'TinyMCE editor IDs cannot have brackets.' ); } } self::$this_quicktags = (bool) $set['quicktags']; if ( self::$this_tinymce ) { self::$has_tinymce = true; } if ( self::$this_quicktags ) { self::$has_quicktags = true; } if ( empty( $set['editor_height'] ) ) { return $set; } if ( 'content' === $editor_id && empty( $set['tinymce']['wp_autoresize_on'] ) ) { // A cookie (set when a user resizes the editor) overrides the height. $cookie = (int) get_user_setting( 'ed_size' ); if ( $cookie ) { $set['editor_height'] = $cookie; } } if ( $set['editor_height'] < 50 ) { $set['editor_height'] = 50; } elseif ( $set['editor_height'] > 5000 ) { $set['editor_height'] = 5000; } return $set; } /** * Outputs the HTML for a single instance of the editor. * * @since 3.3.0 * * @global WP_Screen $current_screen WordPress current screen object. * * @param string $content Initial content for the editor. * @param string $editor_id HTML ID for the textarea and TinyMCE and Quicktags instances. * Should not contain square brackets. * @param array $settings See _WP_Editors::parse_settings() for description. */ public static function editor( $content, $editor_id, $settings = array() ) { $set = self::parse_settings( $editor_id, $settings ); $editor_class = ' class="' . trim( esc_attr( $set['editor_class'] ) . ' wp-editor-area' ) . '"'; $tabindex = $set['tabindex'] ? ' tabindex="' . (int) $set['tabindex'] . '"' : ''; $default_editor = 'html'; $buttons = ''; $autocomplete = ''; $editor_id_attr = esc_attr( $editor_id ); if ( $set['drag_drop_upload'] ) { self::$drag_drop_upload = true; } if ( ! empty( $set['editor_height'] ) ) { $height = ' style="height: ' . (int) $set['editor_height'] . 'px"'; } else { $height = ' rows="' . (int) $set['textarea_rows'] . '"'; } if ( ! current_user_can( 'upload_files' ) ) { $set['media_buttons'] = false; } if ( self::$this_tinymce ) { $autocomplete = ' autocomplete="off"'; if ( self::$this_quicktags ) { $default_editor = $set['default_editor'] ? $set['default_editor'] : wp_default_editor(); // 'html' is used for the "Code" editor tab. if ( 'html' !== $default_editor ) { $default_editor = 'tinymce'; } $tmce_active = ( 'html' === $default_editor ) ? ' aria-pressed="true"' : ''; $html_active = ( 'html' === $default_editor ) ? '' : ' aria-pressed="true"'; $buttons .= '<button type="button" id="' . $editor_id_attr . '-tmce"' . $html_active . ' class="wp-switch-editor switch-tmce"' . ' data-wp-editor-id="' . $editor_id_attr . '">' . _x( 'Visual', 'Name for the Visual editor tab' ) . "</button>\n"; $buttons .= '<button type="button" id="' . $editor_id_attr . '-html"' . $tmce_active . ' class="wp-switch-editor switch-html"' . ' data-wp-editor-id="' . $editor_id_attr . '">' . _x( 'Code', 'Name for the Code editor tab (formerly Text)' ) . "</button>\n"; } else { $default_editor = 'tinymce'; } } $switch_class = 'html' === $default_editor ? 'html-active' : 'tmce-active'; $wrap_class = 'wp-core-ui wp-editor-wrap ' . $switch_class; if ( $set['_content_editor_dfw'] ) { $wrap_class .= ' has-dfw'; } echo '<div id="wp-' . $editor_id_attr . '-wrap" class="' . $wrap_class . '">'; if ( self::$editor_buttons_css ) { wp_print_styles( 'editor-buttons' ); self::$editor_buttons_css = false; } if ( ! empty( $set['editor_css'] ) ) { echo $set['editor_css'] . "\n"; } if ( ! empty( $buttons ) || $set['media_buttons'] ) { echo '<div id="wp-' . $editor_id_attr . '-editor-tools" class="wp-editor-tools hide-if-no-js">'; if ( $set['media_buttons'] ) { self::$has_medialib = true; if ( ! function_exists( 'media_buttons' ) ) { require ABSPATH . 'wp-admin/includes/media.php'; } echo '<div id="wp-' . $editor_id_attr . '-media-buttons" class="wp-media-buttons">'; /** * Fires after the default media button(s) are displayed. * * @since 2.5.0 * * @param string $editor_id Unique editor identifier, e.g. 'content'. */ do_action( 'media_buttons', $editor_id ); echo "</div>\n"; } echo '<div class="wp-editor-tabs">' . $buttons . "</div>\n"; echo "</div>\n"; } $quicktags_toolbar = ''; if ( self::$this_quicktags ) { if ( 'content' === $editor_id && ! empty( $GLOBALS['current_screen'] ) && 'post' === $GLOBALS['current_screen']->base ) { $toolbar_id = 'ed_toolbar'; } else { $toolbar_id = 'qt_' . $editor_id_attr . '_toolbar'; } $quicktags_toolbar = '<div id="' . $toolbar_id . '" class="quicktags-toolbar hide-if-no-js"></div>'; } /** * Filters the HTML markup output that displays the editor. * * @since 2.1.0 * * @param string $output Editor's HTML markup. */ $the_editor = apply_filters( 'the_editor', '<div id="wp-' . $editor_id_attr . '-editor-container" class="wp-editor-container">' . $quicktags_toolbar . '<textarea' . $editor_class . $height . $tabindex . $autocomplete . ' cols="40" name="' . esc_attr( $set['textarea_name'] ) . '" ' . 'id="' . $editor_id_attr . '">%s</textarea></div>' ); // Prepare the content for the Visual or Code editor, only when TinyMCE is used (back-compat). if ( self::$this_tinymce ) { add_filter( 'the_editor_content', 'format_for_editor', 10, 2 ); } /** * Filters the default editor content. * * @since 2.1.0 * * @param string $content Default editor content. * @param string $default_editor The default editor for the current user. * Either 'html' or 'tinymce'. */ $content = apply_filters( 'the_editor_content', $content, $default_editor ); // Remove the filter as the next editor on the same page may not need it. if ( self::$this_tinymce ) { remove_filter( 'the_editor_content', 'format_for_editor' ); } // Back-compat for the `htmledit_pre` and `richedit_pre` filters. if ( 'html' === $default_editor && has_filter( 'htmledit_pre' ) ) { /** This filter is documented in wp-includes/deprecated.php */ $content = apply_filters_deprecated( 'htmledit_pre', array( $content ), '4.3.0', 'format_for_editor' ); } elseif ( 'tinymce' === $default_editor && has_filter( 'richedit_pre' ) ) { /** This filter is documented in wp-includes/deprecated.php */ $content = apply_filters_deprecated( 'richedit_pre', array( $content ), '4.3.0', 'format_for_editor' ); } if ( false !== stripos( $content, 'textarea' ) ) { $content = preg_replace( '%</textarea%i', '</textarea', $content ); } printf( $the_editor, $content ); echo "\n</div>\n\n"; self::editor_settings( $editor_id, $set ); } /** * @since 3.3.0 * * @param string $editor_id Unique editor identifier, e.g. 'content'. * @param array $set Array of editor arguments. */ public static function editor_settings( $editor_id, $set ) { if ( empty( self::$first_init ) ) { if ( is_admin() ) { add_action( 'admin_print_footer_scripts', array( __CLASS__, 'editor_js' ), 50 ); add_action( 'admin_print_footer_scripts', array( __CLASS__, 'force_uncompressed_tinymce' ), 1 ); add_action( 'admin_print_footer_scripts', array( __CLASS__, 'enqueue_scripts' ), 1 ); } else { add_action( 'wp_print_footer_scripts', array( __CLASS__, 'editor_js' ), 50 ); add_action( 'wp_print_footer_scripts', array( __CLASS__, 'force_uncompressed_tinymce' ), 1 ); add_action( 'wp_print_footer_scripts', array( __CLASS__, 'enqueue_scripts' ), 1 ); } } if ( self::$this_quicktags ) { $qt_init = array( 'id' => $editor_id, 'buttons' => '', ); if ( is_array( $set['quicktags'] ) ) { $qt_init = array_merge( $qt_init, $set['quicktags'] ); } if ( empty( $qt_init['buttons'] ) ) { $qt_init['buttons'] = 'strong,em,link,block,del,ins,img,ul,ol,li,code,more,close'; } if ( $set['_content_editor_dfw'] ) { $qt_init['buttons'] .= ',dfw'; } /** * Filters the Quicktags settings. * * @since 3.3.0 * * @param array $qt_init Quicktags settings. * @param string $editor_id Unique editor identifier, e.g. 'content'. */ $qt_init = apply_filters( 'quicktags_settings', $qt_init, $editor_id ); self::$qt_settings[ $editor_id ] = $qt_init; self::$qt_buttons = array_merge( self::$qt_buttons, explode( ',', $qt_init['buttons'] ) ); } if ( self::$this_tinymce ) { if ( empty( self::$first_init ) ) { $baseurl = self::get_baseurl(); $mce_locale = self::get_mce_locale(); $ext_plugins = ''; if ( $set['teeny'] ) { /** * Filters the list of teenyMCE plugins. * * @since 2.7.0 * @since 3.3.0 The `$editor_id` parameter was added. * * @param array $plugins An array of teenyMCE plugins. * @param string $editor_id Unique editor identifier, e.g. 'content'. */ $plugins = apply_filters( 'teeny_mce_plugins', array( 'colorpicker', 'lists', 'fullscreen', 'image', 'wordpress', 'wpeditimage', 'wplink', ), $editor_id ); } else { /** * Filters the list of TinyMCE external plugins. * * The filter takes an associative array of external plugins for * TinyMCE in the form 'plugin_name' => 'url'. * * The url should be absolute, and should include the js filename * to be loaded. For example: * 'myplugin' => 'http://mysite.com/wp-content/plugins/myfolder/mce_plugin.js'. * * If the external plugin adds a button, it should be added with * one of the 'mce_buttons' filters. * * @since 2.5.0 * @since 5.3.0 The `$editor_id` parameter was added. * * @param array $external_plugins An array of external TinyMCE plugins. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block' * when called from block editor's Classic block. */ $mce_external_plugins = apply_filters( 'mce_external_plugins', array(), $editor_id ); $plugins = array( 'charmap', 'colorpicker', 'hr', 'lists', 'media', 'paste', 'tabfocus', 'textcolor', 'fullscreen', 'wordpress', 'wpautoresize', 'wpeditimage', 'wpemoji', 'wpgallery', 'wplink', 'wpdialogs', 'wptextpattern', 'wpview', ); if ( ! self::$has_medialib ) { $plugins[] = 'image'; } /** * Filters the list of default TinyMCE plugins. * * The filter specifies which of the default plugins included * in WordPress should be added to the TinyMCE instance. * * @since 3.3.0 * @since 5.3.0 The `$editor_id` parameter was added. * * @param array $plugins An array of default TinyMCE plugins. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block' * when called from block editor's Classic block. */ $plugins = array_unique( apply_filters( 'tiny_mce_plugins', $plugins, $editor_id ) ); $key = array_search( 'spellchecker', $plugins, true ); if ( false !== $key ) { /* * Remove 'spellchecker' from the internal plugins if added with 'tiny_mce_plugins' filter to prevent errors. * It can be added with 'mce_external_plugins'. */ unset( $plugins[ $key ] ); } if ( ! empty( $mce_external_plugins ) ) { /** * Filters the translations loaded for external TinyMCE 3.x plugins. * * The filter takes an associative array ('plugin_name' => 'path') * where 'path' is the include path to the file. * * The language file should follow the same format as wp_mce_translation(), * and should define a variable ($strings) that holds all translated strings. * * @since 2.5.0 * @since 5.3.0 The `$editor_id` parameter was added. * * @param array $translations Translations for external TinyMCE plugins. * @param string $editor_id Unique editor identifier, e.g. 'content'. */ $mce_external_languages = apply_filters( 'mce_external_languages', array(), $editor_id ); $loaded_langs = array(); $strings = ''; if ( ! empty( $mce_external_languages ) ) { foreach ( $mce_external_languages as $name => $path ) { if ( @is_file( $path ) && @is_readable( $path ) ) { include_once $path; $ext_plugins .= $strings . "\n"; $loaded_langs[] = $name; } } } foreach ( $mce_external_plugins as $name => $url ) { if ( in_array( $name, $plugins, true ) ) { unset( $mce_external_plugins[ $name ] ); continue; } $url = set_url_scheme( $url ); $mce_external_plugins[ $name ] = $url; $plugurl = dirname( $url ); $strings = ''; // Try to load langs/[locale].js and langs/[locale]_dlg.js. if ( ! in_array( $name, $loaded_langs, true ) ) { $path = str_replace( content_url(), '', $plugurl ); $path = realpath( WP_CONTENT_DIR . $path . '/langs/' ); if ( ! $path ) { continue; } $path = trailingslashit( $path ); if ( @is_file( $path . $mce_locale . '.js' ) ) { $strings .= @file_get_contents( $path . $mce_locale . '.js' ) . "\n"; } if ( @is_file( $path . $mce_locale . '_dlg.js' ) ) { $strings .= @file_get_contents( $path . $mce_locale . '_dlg.js' ) . "\n"; } if ( 'en' !== $mce_locale && empty( $strings ) ) { if ( @is_file( $path . 'en.js' ) ) { $str1 = @file_get_contents( $path . 'en.js' ); $strings .= preg_replace( '/([\'"])en\./', '$1' . $mce_locale . '.', $str1, 1 ) . "\n"; } if ( @is_file( $path . 'en_dlg.js' ) ) { $str2 = @file_get_contents( $path . 'en_dlg.js' ); $strings .= preg_replace( '/([\'"])en\./', '$1' . $mce_locale . '.', $str2, 1 ) . "\n"; } } if ( ! empty( $strings ) ) { $ext_plugins .= "\n" . $strings . "\n"; } } $ext_plugins .= 'tinyMCEPreInit.load_ext("' . $plugurl . '", "' . $mce_locale . '");' . "\n"; } } } self::$plugins = $plugins; self::$ext_plugins = $ext_plugins; $settings = self::default_settings(); $settings['plugins'] = implode( ',', $plugins ); if ( ! empty( $mce_external_plugins ) ) { $settings['external_plugins'] = wp_json_encode( $mce_external_plugins ); } /** This filter is documented in wp-admin/includes/media.php */ if ( apply_filters( 'disable_captions', '' ) ) { $settings['wpeditimage_disable_captions'] = true; } $mce_css = $settings['content_css']; /* * The `editor-style.css` added by the theme is generally intended for the editor instance on the Edit Post screen. * Plugins that use wp_editor() on the front-end can decide whether to add the theme stylesheet * by using `get_editor_stylesheets()` and the `mce_css` or `tiny_mce_before_init` filters, see below. */ if ( is_admin() ) { $editor_styles = get_editor_stylesheets(); if ( ! empty( $editor_styles ) ) { // Force urlencoding of commas. foreach ( $editor_styles as $key => $url ) { if ( str_contains( $url, ',' ) ) { $editor_styles[ $key ] = str_replace( ',', '%2C', $url ); } } $mce_css .= ',' . implode( ',', $editor_styles ); } } /** * Filters the comma-delimited list of stylesheets to load in TinyMCE. * * @since 2.1.0 * * @param string $stylesheets Comma-delimited list of stylesheets. */ $mce_css = trim( apply_filters( 'mce_css', $mce_css ), ' ,' ); if ( ! empty( $mce_css ) ) { $settings['content_css'] = $mce_css; } else { unset( $settings['content_css'] ); } self::$first_init = $settings; } if ( $set['teeny'] ) { $mce_buttons = array( 'bold', 'italic', 'underline', 'blockquote', 'strikethrough', 'bullist', 'numlist', 'alignleft', 'aligncenter', 'alignright', 'undo', 'redo', 'link', 'fullscreen', ); /** * Filters the list of teenyMCE buttons (Code tab). * * @since 2.7.0 * @since 3.3.0 The `$editor_id` parameter was added. * * @param array $mce_buttons An array of teenyMCE buttons. * @param string $editor_id Unique editor identifier, e.g. 'content'. */ $mce_buttons = apply_filters( 'teeny_mce_buttons', $mce_buttons, $editor_id ); $mce_buttons_2 = array(); $mce_buttons_3 = array(); $mce_buttons_4 = array(); } else { $mce_buttons = array( 'formatselect', 'bold', 'italic', 'bullist', 'numlist', 'blockquote', 'alignleft', 'aligncenter', 'alignright', 'link', 'wp_more', 'spellchecker', ); if ( ! wp_is_mobile() ) { if ( $set['_content_editor_dfw'] ) { $mce_buttons[] = 'wp_adv'; $mce_buttons[] = 'dfw'; } else { $mce_buttons[] = 'fullscreen'; $mce_buttons[] = 'wp_adv'; } } else { $mce_buttons[] = 'wp_adv'; } /** * Filters the first-row list of TinyMCE buttons (Visual tab). * * @since 2.0.0 * @since 3.3.0 The `$editor_id` parameter was added. * * @param array $mce_buttons First-row list of buttons. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block' * when called from block editor's Classic block. */ $mce_buttons = apply_filters( 'mce_buttons', $mce_buttons, $editor_id ); $mce_buttons_2 = array( 'strikethrough', 'hr', 'forecolor', 'pastetext', 'removeformat', 'charmap', 'outdent', 'indent', 'undo', 'redo', ); if ( ! wp_is_mobile() ) { $mce_buttons_2[] = 'wp_help'; } /** * Filters the second-row list of TinyMCE buttons (Visual tab). * * @since 2.0.0 * @since 3.3.0 The `$editor_id` parameter was added. * * @param array $mce_buttons_2 Second-row list of buttons. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block' * when called from block editor's Classic block. */ $mce_buttons_2 = apply_filters( 'mce_buttons_2', $mce_buttons_2, $editor_id ); /** * Filters the third-row list of TinyMCE buttons (Visual tab). * * @since 2.0.0 * @since 3.3.0 The `$editor_id` parameter was added. * * @param array $mce_buttons_3 Third-row list of buttons. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block' * when called from block editor's Classic block. */ $mce_buttons_3 = apply_filters( 'mce_buttons_3', array(), $editor_id ); /** * Filters the fourth-row list of TinyMCE buttons (Visual tab). * * @since 2.5.0 * @since 3.3.0 The `$editor_id` parameter was added. * * @param array $mce_buttons_4 Fourth-row list of buttons. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block' * when called from block editor's Classic block. */ $mce_buttons_4 = apply_filters( 'mce_buttons_4', array(), $editor_id ); } $body_class = $editor_id; $post = get_post(); if ( $post ) { $body_class .= ' post-type-' . sanitize_html_class( $post->post_type ) . ' post-status-' . sanitize_html_class( $post->post_status ); if ( post_type_supports( $post->post_type, 'post-formats' ) ) { $post_format = get_post_format( $post ); if ( $post_format && ! is_wp_error( $post_format ) ) { $body_class .= ' post-format-' . sanitize_html_class( $post_format ); } else { $body_class .= ' post-format-standard'; } } $page_template = get_page_template_slug( $post ); if ( false !== $page_template ) { $page_template = empty( $page_template ) ? 'default' : str_replace( '.', '-', basename( $page_template, '.php' ) ); $body_class .= ' page-template-' . sanitize_html_class( $page_template ); } } $body_class .= ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_user_locale() ) ) ); if ( ! empty( $set['tinymce']['body_class'] ) ) { $body_class .= ' ' . $set['tinymce']['body_class']; unset( $set['tinymce']['body_class'] ); } $mce_init = array( 'selector' => "#$editor_id", 'wpautop' => (bool) $set['wpautop'], 'indent' => ! $set['wpautop'], 'toolbar1' => implode( ',', $mce_buttons ), 'toolbar2' => implode( ',', $mce_buttons_2 ), 'toolbar3' => implode( ',', $mce_buttons_3 ), 'toolbar4' => implode( ',', $mce_buttons_4 ), 'tabfocus_elements' => $set['tabfocus_elements'], 'body_class' => $body_class, ); // Merge with the first part of the init array. $mce_init = array_merge( self::$first_init, $mce_init ); if ( is_array( $set['tinymce'] ) ) { $mce_init = array_merge( $mce_init, $set['tinymce'] ); } /* * For people who really REALLY know what they're doing with TinyMCE * You can modify $mceInit to add, remove, change elements of the config * before tinyMCE.init. Setting "valid_elements", "invalid_elements" * and "extended_valid_elements" can be done through this filter. Best * is to use the default cleanup by not specifying valid_elements, * as TinyMCE checks against the full set of HTML 5.0 elements and attributes. */ if ( $set['teeny'] ) { /** * Filters the teenyMCE config before init. * * @since 2.7.0 * @since 3.3.0 The `$editor_id` parameter was added. * * @param array $mce_init An array with teenyMCE config. * @param string $editor_id Unique editor identifier, e.g. 'content'. */ $mce_init = apply_filters( 'teeny_mce_before_init', $mce_init, $editor_id ); } else { /** * Filters the TinyMCE config before init. * * @since 2.5.0 * @since 3.3.0 The `$editor_id` parameter was added. * * @param array $mce_init An array with TinyMCE config. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block' * when called from block editor's Classic block. */ $mce_init = apply_filters( 'tiny_mce_before_init', $mce_init, $editor_id ); } if ( empty( $mce_init['toolbar3'] ) && ! empty( $mce_init['toolbar4'] ) ) { $mce_init['toolbar3'] = $mce_init['toolbar4']; $mce_init['toolbar4'] = ''; } self::$mce_settings[ $editor_id ] = $mce_init; } // End if self::$this_tinymce. } /** * @since 3.3.0 * * @param array $init * @return string */ private static function _parse_init( $init ) { $options = ''; foreach ( $init as $key => $value ) { if ( is_bool( $value ) ) { $val = $value ? 'true' : 'false'; $options .= $key . ':' . $val . ','; continue; } elseif ( ! empty( $value ) && is_string( $value ) && ( ( '{' === $value[0] && '}' === $value[ strlen( $value ) - 1 ] ) || ( '[' === $value[0] && ']' === $value[ strlen( $value ) - 1 ] ) || preg_match( '/^\(?function ?\(/', $value ) ) ) { $options .= $key . ':' . $value . ','; continue; } $options .= $key . ':"' . $value . '",'; } return '{' . trim( $options, ' ,' ) . '}'; } /** * @since 3.3.0 * * @param bool $default_scripts Optional. Whether default scripts should be enqueued. Default false. */ public static function enqueue_scripts( $default_scripts = false ) { if ( $default_scripts || self::$has_tinymce ) { wp_enqueue_script( 'editor' ); } if ( $default_scripts || self::$has_quicktags ) { wp_enqueue_script( 'quicktags' ); wp_enqueue_style( 'buttons' ); } if ( $default_scripts || in_array( 'wplink', self::$plugins, true ) || in_array( 'link', self::$qt_buttons, true ) ) { wp_enqueue_script( 'wplink' ); wp_enqueue_script( 'jquery-ui-autocomplete' ); } if ( self::$has_medialib ) { add_thickbox(); wp_enqueue_script( 'media-upload' ); wp_enqueue_script( 'wp-embed' ); } elseif ( $default_scripts ) { wp_enqueue_script( 'media-upload' ); } /** * Fires when scripts and styles are enqueued for the editor. * * @since 3.9.0 * * @param array $to_load An array containing boolean values whether TinyMCE * and Quicktags are being loaded. */ do_action( 'wp_enqueue_editor', array( 'tinymce' => ( $default_scripts || self::$has_tinymce ), 'quicktags' => ( $default_scripts || self::$has_quicktags ), ) ); } /** * Enqueue all editor scripts. * For use when the editor is going to be initialized after page load. * * @since 4.8.0 */ public static function enqueue_default_editor() { // We are past the point where scripts can be enqueued properly. if ( did_action( 'wp_enqueue_editor' ) ) { return; } self::enqueue_scripts( true ); // Also add wp-includes/css/editor.css. wp_enqueue_style( 'editor-buttons' ); if ( is_admin() ) { add_action( 'admin_print_footer_scripts', array( __CLASS__, 'force_uncompressed_tinymce' ), 1 ); add_action( 'admin_print_footer_scripts', array( __CLASS__, 'print_default_editor_scripts' ), 45 ); } else { add_action( 'wp_print_footer_scripts', array( __CLASS__, 'force_uncompressed_tinymce' ), 1 ); add_action( 'wp_print_footer_scripts', array( __CLASS__, 'print_default_editor_scripts' ), 45 ); } } /** * Print (output) all editor scripts and default settings. * For use when the editor is going to be initialized after page load. * * @since 4.8.0 */ public static function print_default_editor_scripts() { $user_can_richedit = user_can_richedit(); if ( $user_can_richedit ) { $settings = self::default_settings(); $settings['toolbar1'] = 'bold,italic,bullist,numlist,link'; $settings['wpautop'] = false; $settings['indent'] = true; $settings['elementpath'] = false; if ( is_rtl() ) { $settings['directionality'] = 'rtl'; } /* * In production all plugins are loaded (they are in wp-editor.js.gz). * The 'wpview', 'wpdialogs', and 'media' TinyMCE plugins are not initialized by default. * Can be added from js by using the 'wp-before-tinymce-init' event. */ $settings['plugins'] = implode( ',', array( 'charmap', 'colorpicker', 'hr', 'lists', 'paste', 'tabfocus', 'textcolor', 'fullscreen', 'wordpress', 'wpautoresize', 'wpeditimage', 'wpemoji', 'wpgallery', 'wplink', 'wptextpattern', ) ); $settings = self::_parse_init( $settings ); } else { $settings = '{}'; } ?> <script type="text/javascript"> window.wp = window.wp || {}; window.wp.editor = window.wp.editor || {}; window.wp.editor.getDefaultSettings = function() { return { tinymce: <?php echo $settings; ?>, quicktags: { buttons: 'strong,em,link,ul,ol,li,code' } }; }; <?php if ( $user_can_richedit ) { $suffix = SCRIPT_DEBUG ? '' : '.min'; $baseurl = self::get_baseurl(); ?> var tinyMCEPreInit = { baseURL: "<?php echo $baseurl; ?>", suffix: "<?php echo $suffix; ?>", mceInit: {}, qtInit: {}, load_ext: function(url,lang){var sl=tinymce.ScriptLoader;sl.markDone(url+'/langs/'+lang+'.js');sl.markDone(url+'/langs/'+lang+'_dlg.js');} }; <?php } ?> </script> <?php if ( $user_can_richedit ) { self::print_tinymce_scripts(); } /** * Fires when the editor scripts are loaded for later initialization, * after all scripts and settings are printed. * * @since 4.8.0 */ do_action( 'print_default_editor_scripts' ); self::wp_link_dialog(); } /** * Returns the TinyMCE locale. * * @since 4.8.0 * * @return string */ public static function get_mce_locale() { if ( empty( self::$mce_locale ) ) { $mce_locale = get_user_locale(); self::$mce_locale = empty( $mce_locale ) ? 'en' : strtolower( substr( $mce_locale, 0, 2 ) ); // ISO 639-1. } return self::$mce_locale; } /** * Returns the TinyMCE base URL. * * @since 4.8.0 * * @return string */ public static function get_baseurl() { if ( empty( self::$baseurl ) ) { self::$baseurl = includes_url( 'js/tinymce' ); } return self::$baseurl; } /** * Returns the default TinyMCE settings. * Doesn't include plugins, buttons, editor selector. * * @since 4.8.0 * * @global string $tinymce_version * * @return array */ private static function default_settings() { global $tinymce_version; $shortcut_labels = array(); foreach ( self::get_translation() as $name => $value ) { if ( is_array( $value ) ) { $shortcut_labels[ $name ] = $value[1]; } } $settings = array( 'theme' => 'modern', 'skin' => 'lightgray', 'language' => self::get_mce_locale(), 'formats' => '{' . 'alignleft: [' . '{selector: "p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li", styles: {textAlign:"left"}},' . '{selector: "img,table,dl.wp-caption", classes: "alignleft"}' . '],' . 'aligncenter: [' . '{selector: "p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li", styles: {textAlign:"center"}},' . '{selector: "img,table,dl.wp-caption", classes: "aligncenter"}' . '],' . 'alignright: [' . '{selector: "p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li", styles: {textAlign:"right"}},' . '{selector: "img,table,dl.wp-caption", classes: "alignright"}' . '],' . 'strikethrough: {inline: "del"}' . '}', 'relative_urls' => false, 'remove_script_host' => false, 'convert_urls' => false, 'browser_spellcheck' => true, 'fix_list_elements' => true, 'entities' => '38,amp,60,lt,62,gt', 'entity_encoding' => 'raw', 'keep_styles' => false, 'cache_suffix' => 'wp-mce-' . $tinymce_version, 'resize' => 'vertical', 'menubar' => false, 'branding' => false, // Limit the preview styles in the menu/toolbar. 'preview_styles' => 'font-family font-size font-weight font-style text-decoration text-transform', 'end_container_on_empty_block' => true, 'wpeditimage_html5_captions' => true, 'wp_lang_attr' => get_bloginfo( 'language' ), 'wp_shortcut_labels' => wp_json_encode( $shortcut_labels ), ); $suffix = SCRIPT_DEBUG ? '' : '.min'; $version = 'ver=' . get_bloginfo( 'version' ); // Default stylesheets. $settings['content_css'] = includes_url( "css/dashicons$suffix.css?$version" ) . ',' . includes_url( "js/tinymce/skins/wordpress/wp-content.css?$version" ); return $settings; } /** * @since 4.7.0 * * @return array */ private static function get_translation() { if ( empty( self::$translation ) ) { self::$translation = array( // Default TinyMCE strings. 'New document' => __( 'New document' ), 'Formats' => _x( 'Formats', 'TinyMCE' ), 'Headings' => _x( 'Headings', 'TinyMCE' ), 'Heading 1' => array( __( 'Heading 1' ), 'access1' ), 'Heading 2' => array( __( 'Heading 2' ), 'access2' ), 'Heading 3' => array( __( 'Heading 3' ), 'access3' ), 'Heading 4' => array( __( 'Heading 4' ), 'access4' ), 'Heading 5' => array( __( 'Heading 5' ), 'access5' ), 'Heading 6' => array( __( 'Heading 6' ), 'access6' ), /* translators: Block tags. */ 'Blocks' => _x( 'Blocks', 'TinyMCE' ), 'Paragraph' => array( __( 'Paragraph' ), 'access7' ), 'Blockquote' => array( __( 'Blockquote' ), 'accessQ' ), 'Div' => _x( 'Div', 'HTML tag' ), 'Pre' => _x( 'Pre', 'HTML tag' ), 'Preformatted' => _x( 'Preformatted', 'HTML tag' ), 'Address' => _x( 'Address', 'HTML tag' ), 'Inline' => _x( 'Inline', 'HTML elements' ), 'Underline' => array( __( 'Underline' ), 'metaU' ), 'Strikethrough' => array( __( 'Strikethrough' ), 'accessD' ), 'Subscript' => __( 'Subscript' ), 'Superscript' => __( 'Superscript' ), 'Clear formatting' => __( 'Clear formatting' ), 'Bold' => array( __( 'Bold' ), 'metaB' ), 'Italic' => array( __( 'Italic' ), 'metaI' ), 'Code' => array( __( 'Code' ), 'accessX' ), 'Source code' => __( 'Source code' ), 'Font Family' => __( 'Font Family' ), 'Font Sizes' => __( 'Font Sizes' ), 'Align center' => array( __( 'Align center' ), 'accessC' ), 'Align right' => array( __( 'Align right' ), 'accessR' ), 'Align left' => array( __( 'Align left' ), 'accessL' ), 'Justify' => array( __( 'Justify' ), 'accessJ' ), 'Increase indent' => __( 'Increase indent' ), 'Decrease indent' => __( 'Decrease indent' ), 'Cut' => array( __( 'Cut' ), 'metaX' ), 'Copy' => array( __( 'Copy' ), 'metaC' ), 'Paste' => array( __( 'Paste' ), 'metaV' ), 'Select all' => array( __( 'Select all' ), 'metaA' ), 'Undo' => array( __( 'Undo' ), 'metaZ' ), 'Redo' => array( __( 'Redo' ), 'metaY' ), 'Ok' => __( 'OK' ), 'Cancel' => __( 'Cancel' ), 'Close' => __( 'Close' ), 'Visual aids' => __( 'Visual aids' ), 'Bullet list' => array( __( 'Bulleted list' ), 'accessU' ), 'Numbered list' => array( __( 'Numbered list' ), 'accessO' ), 'Square' => _x( 'Square', 'list style' ), 'Default' => _x( 'Default', 'list style' ), 'Circle' => _x( 'Circle', 'list style' ), 'Disc' => _x( 'Disc', 'list style' ), 'Lower Greek' => _x( 'Lower Greek', 'list style' ), 'Lower Alpha' => _x( 'Lower Alpha', 'list style' ), 'Upper Alpha' => _x( 'Upper Alpha', 'list style' ), 'Upper Roman' => _x( 'Upper Roman', 'list style' ), 'Lower Roman' => _x( 'Lower Roman', 'list style' ), // Anchor plugin. 'Name' => _x( 'Name', 'Name of link anchor (TinyMCE)' ), 'Anchor' => _x( 'Anchor', 'Link anchor (TinyMCE)' ), 'Anchors' => _x( 'Anchors', 'Link anchors (TinyMCE)' ), 'Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.' => __( 'Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.' ), 'Id' => _x( 'Id', 'Id for link anchor (TinyMCE)' ), // Fullpage plugin. 'Document properties' => __( 'Document properties' ), 'Robots' => __( 'Robots' ), 'Title' => __( 'Title' ), 'Keywords' => __( 'Keywords' ), 'Encoding' => __( 'Encoding' ), 'Description' => __( 'Description' ), 'Author' => __( 'Author' ), // Media, image plugins. 'Image' => __( 'Image' ), 'Insert/edit image' => array( __( 'Insert/edit image' ), 'accessM' ), 'General' => __( 'General' ), 'Advanced' => __( 'Advanced' ), 'Source' => __( 'Source' ), 'Border' => __( 'Border' ), 'Constrain proportions' => __( 'Constrain proportions' ), 'Vertical space' => __( 'Vertical space' ), 'Image description' => __( 'Image description' ), 'Style' => __( 'Style' ), 'Dimensions' => __( 'Dimensions' ), 'Insert image' => __( 'Insert image' ), 'Date/time' => __( 'Date/time' ), 'Insert date/time' => __( 'Insert date/time' ), 'Table of Contents' => __( 'Table of Contents' ), 'Insert/Edit code sample' => __( 'Insert/edit code sample' ), 'Language' => __( 'Language' ), 'Media' => __( 'Media' ), 'Insert/edit media' => __( 'Insert/edit media' ), 'Poster' => __( 'Poster' ), 'Alternative source' => __( 'Alternative source' ), 'Paste your embed code below:' => __( 'Paste your embed code below:' ), 'Insert video' => __( 'Insert video' ), 'Embed' => __( 'Embed' ), // Each of these have a corresponding plugin. 'Special character' => __( 'Special character' ), 'Right to left' => _x( 'Right to left', 'editor button' ), 'Left to right' => _x( 'Left to right', 'editor button' ), 'Emoticons' => __( 'Emoticons' ), 'Nonbreaking space' => __( 'Nonbreaking space' ), 'Page break' => __( 'Page break' ), 'Paste as text' => __( 'Paste as text' ), 'Preview' => __( 'Preview' ), 'Print' => __( 'Print' ), 'Save' => __( 'Save' ), 'Fullscreen' => __( 'Fullscreen' ), 'Horizontal line' => __( 'Horizontal line' ), 'Horizontal space' => __( 'Horizontal space' ), 'Restore last draft' => __( 'Restore last draft' ), 'Insert/edit link' => array( __( 'Insert/edit link' ), 'metaK' ), 'Remove link' => array( __( 'Remove link' ), 'accessS' ), // Link plugin. 'Link' => __( 'Link' ), 'Insert link' => __( 'Insert link' ), 'Target' => __( 'Target' ), 'New window' => __( 'New window' ), 'Text to display' => __( 'Text to display' ), 'Url' => __( 'URL' ), 'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?' => __( 'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?' ), 'The URL you entered seems to be an external link. Do you want to add the required http:// prefix?' => __( 'The URL you entered seems to be an external link. Do you want to add the required http:// prefix?' ), 'Color' => __( 'Color' ), 'Custom color' => __( 'Custom color' ), 'Custom...' => _x( 'Custom...', 'label for custom color' ), // No ellipsis. 'No color' => __( 'No color' ), 'R' => _x( 'R', 'Short for red in RGB' ), 'G' => _x( 'G', 'Short for green in RGB' ), 'B' => _x( 'B', 'Short for blue in RGB' ), // Spelling, search/replace plugins. 'Could not find the specified string.' => __( 'Could not find the specified string.' ), 'Replace' => _x( 'Replace', 'find/replace' ), 'Next' => _x( 'Next', 'find/replace' ), /* translators: Previous. */ 'Prev' => _x( 'Prev', 'find/replace' ), 'Whole words' => _x( 'Whole words', 'find/replace' ), 'Find and replace' => __( 'Find and replace' ), 'Replace with' => _x( 'Replace with', 'find/replace' ), 'Find' => _x( 'Find', 'find/replace' ), 'Replace all' => _x( 'Replace all', 'find/replace' ), 'Match case' => __( 'Match case' ), 'Spellcheck' => __( 'Check Spelling' ), 'Finish' => _x( 'Finish', 'spellcheck' ), 'Ignore all' => _x( 'Ignore all', 'spellcheck' ), 'Ignore' => _x( 'Ignore', 'spellcheck' ), 'Add to Dictionary' => __( 'Add to Dictionary' ), // TinyMCE tables. 'Insert table' => __( 'Insert table' ), 'Delete table' => __( 'Delete table' ), 'Table properties' => __( 'Table properties' ), 'Row properties' => __( 'Table row properties' ), 'Cell properties' => __( 'Table cell properties' ), 'Border color' => __( 'Border color' ), 'Row' => __( 'Row' ), 'Rows' => __( 'Rows' ), 'Column' => __( 'Column' ), 'Cols' => __( 'Columns' ), 'Cell' => _x( 'Cell', 'table cell' ), 'Header cell' => __( 'Header cell' ), 'Header' => _x( 'Header', 'table header' ), 'Body' => _x( 'Body', 'table body' ), 'Footer' => _x( 'Footer', 'table footer' ), 'Insert row before' => __( 'Insert row before' ), 'Insert row after' => __( 'Insert row after' ), 'Insert column before' => __( 'Insert column before' ), 'Insert column after' => __( 'Insert column after' ), 'Paste row before' => __( 'Paste table row before' ), 'Paste row after' => __( 'Paste table row after' ), 'Delete row' => __( 'Delete row' ), 'Delete column' => __( 'Delete column' ), 'Cut row' => __( 'Cut table row' ), 'Copy row' => __( 'Copy table row' ), 'Merge cells' => __( 'Merge table cells' ), 'Split cell' => __( 'Split table cell' ), 'Height' => __( 'Height' ), 'Width' => __( 'Width' ), 'Caption' => __( 'Caption' ), 'Alignment' => __( 'Alignment' ), 'H Align' => _x( 'H Align', 'horizontal table cell alignment' ), 'Left' => __( 'Left' ), 'Center' => __( 'Center' ), 'Right' => __( 'Right' ), 'None' => _x( 'None', 'table cell alignment attribute' ), 'V Align' => _x( 'V Align', 'vertical table cell alignment' ), 'Top' => __( 'Top' ), 'Middle' => __( 'Middle' ), 'Bottom' => __( 'Bottom' ), 'Row group' => __( 'Row group' ), 'Column group' => __( 'Column group' ), 'Row type' => __( 'Row type' ), 'Cell type' => __( 'Cell type' ), 'Cell padding' => __( 'Cell padding' ), 'Cell spacing' => __( 'Cell spacing' ), 'Scope' => _x( 'Scope', 'table cell scope attribute' ), 'Insert template' => _x( 'Insert template', 'TinyMCE' ), 'Templates' => _x( 'Templates', 'TinyMCE' ), 'Background color' => __( 'Background color' ), 'Text color' => __( 'Text color' ), 'Show blocks' => _x( 'Show blocks', 'editor button' ), 'Show invisible characters' => __( 'Show invisible characters' ), /* translators: Word count. */ 'Words: {0}' => sprintf( __( 'Words: %s' ), '{0}' ), 'Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.' => __( 'Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.' ) . "\n\n" . __( 'If you are looking to paste rich content from Microsoft Word, try turning this option off. The editor will clean up text pasted from Word automatically.' ), 'Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help' => __( 'Rich Text Area. Press Alt-Shift-H for help.' ), 'Rich Text Area. Press Control-Option-H for help.' => __( 'Rich Text Area. Press Control-Option-H for help.' ), 'You have unsaved changes are you sure you want to navigate away?' => __( 'The changes you made will be lost if you navigate away from this page.' ), 'Your browser doesn\'t support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.' => __( 'Your browser does not support direct access to the clipboard. Please use keyboard shortcuts or your browser’s edit menu instead.' ), // TinyMCE menus. 'Insert' => _x( 'Insert', 'TinyMCE menu' ), 'File' => _x( 'File', 'TinyMCE menu' ), 'Edit' => _x( 'Edit', 'TinyMCE menu' ), 'Tools' => _x( 'Tools', 'TinyMCE menu' ), 'View' => _x( 'View', 'TinyMCE menu' ), 'Table' => _x( 'Table', 'TinyMCE menu' ), 'Format' => _x( 'Format', 'TinyMCE menu' ), // WordPress strings. 'Toolbar Toggle' => array( __( 'Toolbar Toggle' ), 'accessZ' ), 'Insert Read More tag' => array( __( 'Insert Read More tag' ), 'accessT' ), 'Insert Page Break tag' => array( __( 'Insert Page Break tag' ), 'accessP' ), 'Read more...' => __( 'Read more...' ), // Title on the placeholder inside the editor (no ellipsis). 'Distraction-free writing mode' => array( __( 'Distraction-free writing mode' ), 'accessW' ), 'No alignment' => __( 'No alignment' ), // Tooltip for the 'alignnone' button in the image toolbar. 'Remove' => __( 'Remove' ), // Tooltip for the 'remove' button in the image toolbar. 'Edit|button' => __( 'Edit' ), // Tooltip for the 'edit' button in the image toolbar. 'Paste URL or type to search' => __( 'Paste URL or type to search' ), // Placeholder for the inline link dialog. 'Apply' => __( 'Apply' ), // Tooltip for the 'apply' button in the inline link dialog. 'Link options' => __( 'Link options' ), // Tooltip for the 'link options' button in the inline link dialog. 'Visual' => _x( 'Visual', 'Name for the Visual editor tab' ), // Editor switch tab label. 'Code|tab' => _x( 'Code', 'Name for the Code editor tab (formerly Text)' ), // Editor switch tab label. 'Add Media' => array( __( 'Add Media' ), 'accessM' ), // Tooltip for the 'Add Media' button in the block editor Classic block. // Shortcuts help modal. 'Keyboard Shortcuts' => array( __( 'Keyboard Shortcuts' ), 'accessH' ), 'Classic Block Keyboard Shortcuts' => __( 'Classic Block Keyboard Shortcuts' ), 'Default shortcuts,' => __( 'Default shortcuts,' ), 'Additional shortcuts,' => __( 'Additional shortcuts,' ), 'Focus shortcuts:' => __( 'Focus shortcuts:' ), 'Inline toolbar (when an image, link or preview is selected)' => __( 'Inline toolbar (when an image, link or preview is selected)' ), 'Editor menu (when enabled)' => __( 'Editor menu (when enabled)' ), 'Editor toolbar' => __( 'Editor toolbar' ), 'Elements path' => __( 'Elements path' ), 'Ctrl + Alt + letter:' => __( 'Ctrl + Alt + letter:' ), 'Shift + Alt + letter:' => __( 'Shift + Alt + letter:' ), 'Cmd + letter:' => __( 'Cmd + letter:' ), 'Ctrl + letter:' => __( 'Ctrl + letter:' ), 'Letter' => __( 'Letter' ), 'Action' => __( 'Action' ), 'Warning: the link has been inserted but may have errors. Please test it.' => __( 'Warning: the link has been inserted but may have errors. Please test it.' ), 'To move focus to other buttons use Tab or the arrow keys. To return focus to the editor press Escape or use one of the buttons.' => __( 'To move focus to other buttons use Tab or the arrow keys. To return focus to the editor press Escape or use one of the buttons.' ), 'When starting a new paragraph with one of these formatting shortcuts followed by a space, the formatting will be applied automatically. Press Backspace or Escape to undo.' => __( 'When starting a new paragraph with one of these formatting shortcuts followed by a space, the formatting will be applied automatically. Press Backspace or Escape to undo.' ), 'The following formatting shortcuts are replaced when pressing Enter. Press Escape or the Undo button to undo.' => __( 'The following formatting shortcuts are replaced when pressing Enter. Press Escape or the Undo button to undo.' ), 'The next group of formatting shortcuts are applied as you type or when you insert them around plain text in the same paragraph. Press Escape or the Undo button to undo.' => __( 'The next group of formatting shortcuts are applied as you type or when you insert them around plain text in the same paragraph. Press Escape or the Undo button to undo.' ), ); } /* Imagetools plugin (not included): 'Edit image' => __( 'Edit image' ), 'Image options' => __( 'Image options' ), 'Back' => __( 'Back' ), 'Invert' => __( 'Invert' ), 'Flip horizontally' => __( 'Flip horizontal' ), 'Flip vertically' => __( 'Flip vertical' ), 'Crop' => __( 'Crop' ), 'Orientation' => __( 'Orientation' ), 'Resize' => __( 'Resize' ), 'Rotate clockwise' => __( 'Rotate right' ), 'Rotate counterclockwise' => __( 'Rotate left' ), 'Sharpen' => __( 'Sharpen' ), 'Brightness' => __( 'Brightness' ), 'Color levels' => __( 'Color levels' ), 'Contrast' => __( 'Contrast' ), 'Gamma' => __( 'Gamma' ), 'Zoom in' => __( 'Zoom in' ), 'Zoom out' => __( 'Zoom out' ), */ return self::$translation; } /** * Translates the default TinyMCE strings and returns them as JSON encoded object ready to be loaded with tinymce.addI18n(), * or as JS snippet that should run after tinymce.js is loaded. * * @since 3.9.0 * * @param string $mce_locale The locale used for the editor. * @param bool $json_only Optional. Whether to include the JavaScript calls to tinymce.addI18n() and * tinymce.ScriptLoader.markDone(). Default false. * @return string Translation object, JSON encoded. */ public static function wp_mce_translation( $mce_locale = '', $json_only = false ) { if ( ! $mce_locale ) { $mce_locale = self::get_mce_locale(); } $mce_translation = self::get_translation(); foreach ( $mce_translation as $name => $value ) { if ( is_array( $value ) ) { $mce_translation[ $name ] = $value[0]; } } /** * Filters translated strings prepared for TinyMCE. * * @since 3.9.0 * * @param array $mce_translation Key/value pairs of strings. * @param string $mce_locale Locale. */ $mce_translation = apply_filters( 'wp_mce_translation', $mce_translation, $mce_locale ); foreach ( $mce_translation as $key => $value ) { // Remove strings that are not translated. if ( $key === $value ) { unset( $mce_translation[ $key ] ); continue; } if ( str_contains( $value, '&' ) ) { $mce_translation[ $key ] = html_entity_decode( $value, ENT_QUOTES, 'UTF-8' ); } } // Set direction. if ( is_rtl() ) { $mce_translation['_dir'] = 'rtl'; } if ( $json_only ) { return wp_json_encode( $mce_translation ); } $baseurl = self::get_baseurl(); return "tinymce.addI18n( '$mce_locale', " . wp_json_encode( $mce_translation ) . ");\n" . "tinymce.ScriptLoader.markDone( '$baseurl/langs/$mce_locale.js' );\n"; } /** * Force uncompressed TinyMCE when a custom theme has been defined. * * The compressed TinyMCE file cannot deal with custom themes, so this makes * sure that WordPress uses the uncompressed TinyMCE file if a theme is defined. * Even if the website is running on a production environment. * * @since 5.0.0 */ public static function force_uncompressed_tinymce() { $has_custom_theme = false; foreach ( self::$mce_settings as $init ) { if ( ! empty( $init['theme_url'] ) ) { $has_custom_theme = true; break; } } if ( ! $has_custom_theme ) { return; } $wp_scripts = wp_scripts(); $wp_scripts->remove( 'wp-tinymce' ); wp_register_tinymce_scripts( $wp_scripts, true ); } /** * Print (output) the main TinyMCE scripts. * * @since 4.8.0 * * @global bool $concatenate_scripts */ public static function print_tinymce_scripts() { global $concatenate_scripts; if ( self::$tinymce_scripts_printed ) { return; } self::$tinymce_scripts_printed = true; if ( ! isset( $concatenate_scripts ) ) { script_concat_settings(); } wp_print_scripts( array( 'wp-tinymce' ) ); echo "<script type='text/javascript'>\n" . self::wp_mce_translation() . "</script>\n"; } /** * Print (output) the TinyMCE configuration and initialization scripts. * * @since 3.3.0 * * @global string $tinymce_version */ public static function editor_js() { global $tinymce_version; $tmce_on = ! empty( self::$mce_settings ); $mce_init = ''; $qt_init = ''; if ( $tmce_on ) { foreach ( self::$mce_settings as $editor_id => $init ) { $options = self::_parse_init( $init ); $mce_init .= "'$editor_id':{$options},"; } $mce_init = '{' . trim( $mce_init, ',' ) . '}'; } else { $mce_init = '{}'; } if ( ! empty( self::$qt_settings ) ) { foreach ( self::$qt_settings as $editor_id => $init ) { $options = self::_parse_init( $init ); $qt_init .= "'$editor_id':{$options},"; } $qt_init = '{' . trim( $qt_init, ',' ) . '}'; } else { $qt_init = '{}'; } $ref = array( 'plugins' => implode( ',', self::$plugins ), 'theme' => 'modern', 'language' => self::$mce_locale, ); $suffix = SCRIPT_DEBUG ? '' : '.min'; $baseurl = self::get_baseurl(); $version = 'ver=' . $tinymce_version; /** * Fires immediately before the TinyMCE settings are printed. * * @since 3.2.0 * * @param array $mce_settings TinyMCE settings array. */ do_action( 'before_wp_tiny_mce', self::$mce_settings ); ?> <script type="text/javascript"> tinyMCEPreInit = { baseURL: "<?php echo $baseurl; ?>", suffix: "<?php echo $suffix; ?>", <?php if ( self::$drag_drop_upload ) { echo 'dragDropUpload: true,'; } ?> mceInit: <?php echo $mce_init; ?>, qtInit: <?php echo $qt_init; ?>, ref: <?php echo self::_parse_init( $ref ); ?>, load_ext: function(url,lang){var sl=tinymce.ScriptLoader;sl.markDone(url+'/langs/'+lang+'.js');sl.markDone(url+'/langs/'+lang+'_dlg.js');} }; </script> <?php if ( $tmce_on ) { self::print_tinymce_scripts(); if ( self::$ext_plugins ) { // Load the old-format English strings to prevent unsightly labels in old style popups. echo "<script type='text/javascript' src='{$baseurl}/langs/wp-langs-en.js?$version'></script>\n"; } } /** * Fires after tinymce.js is loaded, but before any TinyMCE editor * instances are created. * * @since 3.9.0 * * @param array $mce_settings TinyMCE settings array. */ do_action( 'wp_tiny_mce_init', self::$mce_settings ); ?> <script type="text/javascript"> <?php if ( self::$ext_plugins ) { echo self::$ext_plugins . "\n"; } if ( ! is_admin() ) { echo 'var ajaxurl = "' . admin_url( 'admin-ajax.php', 'relative' ) . '";'; } ?> ( function() { var initialized = []; var initialize = function() { var init, id, inPostbox, $wrap; var readyState = document.readyState; if ( readyState !== 'complete' && readyState !== 'interactive' ) { return; } for ( id in tinyMCEPreInit.mceInit ) { if ( initialized.indexOf( id ) > -1 ) { continue; } init = tinyMCEPreInit.mceInit[id]; $wrap = tinymce.$( '#wp-' + id + '-wrap' ); inPostbox = $wrap.parents( '.postbox' ).length > 0; if ( ! init.wp_skip_init && ( $wrap.hasClass( 'tmce-active' ) || ! tinyMCEPreInit.qtInit.hasOwnProperty( id ) ) && ( readyState === 'complete' || ( ! inPostbox && readyState === 'interactive' ) ) ) { tinymce.init( init ); initialized.push( id ); if ( ! window.wpActiveEditor ) { window.wpActiveEditor = id; } } } } if ( typeof tinymce !== 'undefined' ) { if ( tinymce.Env.ie && tinymce.Env.ie < 11 ) { tinymce.$( '.wp-editor-wrap ' ).removeClass( 'tmce-active' ).addClass( 'html-active' ); } else { if ( document.readyState === 'complete' ) { initialize(); } else { document.addEventListener( 'readystatechange', initialize ); } } } if ( typeof quicktags !== 'undefined' ) { for ( id in tinyMCEPreInit.qtInit ) { quicktags( tinyMCEPreInit.qtInit[id] ); if ( ! window.wpActiveEditor ) { window.wpActiveEditor = id; } } } }()); </script> <?php if ( in_array( 'wplink', self::$plugins, true ) || in_array( 'link', self::$qt_buttons, true ) ) { self::wp_link_dialog(); } /** * Fires after any core TinyMCE editor instances are created. * * @since 3.2.0 * * @param array $mce_settings TinyMCE settings array. */ do_action( 'after_wp_tiny_mce', self::$mce_settings ); } /** * Outputs the HTML for distraction-free writing mode. * * @since 3.2.0 * @deprecated 4.3.0 */ public static function wp_fullscreen_html() { _deprecated_function( __FUNCTION__, '4.3.0' ); } /** * Performs post queries for internal linking. * * @since 3.1.0 * * @param array $args { * Optional. Array of link query arguments. * * @type int $pagenum Page number. Default 1. * @type string $s Search keywords. * } * @return array|false $results { * An array of associative arrays of query results, false if there are none. * * @type array ...$0 { * @type int $ID Post ID. * @type string $title The trimmed, escaped post title. * @type string $permalink Post permalink. * @type string $info A 'Y/m/d'-formatted date for 'post' post type, * the 'singular_name' post type label otherwise. * } * } */ public static function wp_link_query( $args = array() ) { $pts = get_post_types( array( 'public' => true ), 'objects' ); $pt_names = array_keys( $pts ); $query = array( 'post_type' => $pt_names, 'suppress_filters' => true, 'update_post_term_cache' => false, 'update_post_meta_cache' => false, 'post_status' => 'publish', 'posts_per_page' => 20, ); $args['pagenum'] = isset( $args['pagenum'] ) ? absint( $args['pagenum'] ) : 1; if ( isset( $args['s'] ) ) { $query['s'] = $args['s']; } $query['offset'] = $args['pagenum'] > 1 ? $query['posts_per_page'] * ( $args['pagenum'] - 1 ) : 0; /** * Filters the link query arguments. * * Allows modification of the link query arguments before querying. * * @see WP_Query for a full list of arguments * * @since 3.7.0 * * @param array $query An array of WP_Query arguments. */ $query = apply_filters( 'wp_link_query_args', $query ); // Do main query. $get_posts = new WP_Query(); $posts = $get_posts->query( $query ); // Build results. $results = array(); foreach ( $posts as $post ) { if ( 'post' === $post->post_type ) { $info = mysql2date( __( 'Y/m/d' ), $post->post_date ); } else { $info = $pts[ $post->post_type ]->labels->singular_name; } $results[] = array( 'ID' => $post->ID, 'title' => trim( esc_html( strip_tags( get_the_title( $post ) ) ) ), 'permalink' => get_permalink( $post->ID ), 'info' => $info, ); } /** * Filters the link query results. * * Allows modification of the returned link query results. * * @since 3.7.0 * * @see 'wp_link_query_args' filter * * @param array $results { * An array of associative arrays of query results. * * @type array ...$0 { * @type int $ID Post ID. * @type string $title The trimmed, escaped post title. * @type string $permalink Post permalink. * @type string $info A 'Y/m/d'-formatted date for 'post' post type, * the 'singular_name' post type label otherwise. * } * } * @param array $query An array of WP_Query arguments. */ $results = apply_filters( 'wp_link_query', $results, $query ); return ! empty( $results ) ? $results : false; } /** * Dialog for internal linking. * * @since 3.1.0 */ public static function wp_link_dialog() { // Run once. if ( self::$link_dialog_printed ) { return; } self::$link_dialog_printed = true; // `display: none` is required here, see #WP27605. ?> <div id="wp-link-backdrop" style="display: none"></div> <div id="wp-link-wrap" class="wp-core-ui" style="display: none" role="dialog" aria-modal="true" aria-labelledby="link-modal-title"> <form id="wp-link" tabindex="-1"> <?php wp_nonce_field( 'internal-linking', '_ajax_linking_nonce', false ); ?> <h1 id="link-modal-title"><?php _e( 'Insert/edit link' ); ?></h1> <button type="button" id="wp-link-close"><span class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ _e( 'Close' ); ?> </span></button> <div id="link-selector"> <div id="link-options"> <p class="howto" id="wplink-enter-url"><?php _e( 'Enter the destination URL' ); ?></p> <div> <label><span><?php _e( 'URL' ); ?></span> <input id="wp-link-url" type="text" aria-describedby="wplink-enter-url" /></label> </div> <div class="wp-link-text-field"> <label><span><?php _e( 'Link Text' ); ?></span> <input id="wp-link-text" type="text" /></label> </div> <div class="link-target"> <label><span></span> <input type="checkbox" id="wp-link-target" /> <?php _e( 'Open link in a new tab' ); ?></label> </div> </div> <p class="howto" id="wplink-link-existing-content"><?php _e( 'Or link to existing content' ); ?></p> <div id="search-panel"> <div class="link-search-wrapper"> <label> <span class="search-label"><?php _e( 'Search' ); ?></span> <input type="search" id="wp-link-search" class="link-search-field" autocomplete="off" aria-describedby="wplink-link-existing-content" /> <span class="spinner"></span> </label> </div> <div id="search-results" class="query-results" tabindex="0"> <ul></ul> <div class="river-waiting"> <span class="spinner"></span> </div> </div> <div id="most-recent-results" class="query-results" tabindex="0"> <div class="query-notice" id="query-notice-message"> <em class="query-notice-default"><?php _e( 'No search term specified. Showing recent items.' ); ?></em> <em class="query-notice-hint screen-reader-text"> <?php /* translators: Hidden accessibility text. */ _e( 'Search or use up and down arrow keys to select an item.' ); ?> </em> </div> <ul></ul> <div class="river-waiting"> <span class="spinner"></span> </div> </div> </div> </div> <div class="submitbox"> <div id="wp-link-cancel"> <button type="button" class="button"><?php _e( 'Cancel' ); ?></button> </div> <div id="wp-link-update"> <input type="submit" value="<?php esc_attr_e( 'Add Link' ); ?>" class="button button-primary" id="wp-link-submit" name="wp-link-submit"> </div> </div> </form> </div> <?php } } https-migration-20260607104254.php 0000666 00000011205 15226762357 0012014 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260605111237.php 0000666 00000013341 15226762357 0012001 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-customize-upload-control.php 0000666 00000002255 15226762357 0014015 0 ustar 00 <?php /** * Customize API: WP_Customize_Upload_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Upload Control Class. * * @since 3.4.0 * * @see WP_Customize_Media_Control */ class WP_Customize_Upload_Control extends WP_Customize_Media_Control { /** * Control type. * * @since 3.4.0 * @var string */ public $type = 'upload'; /** * Media control mime type. * * @since 4.1.0 * @var string */ public $mime_type = ''; /** * Button labels. * * @since 4.1.0 * @var array */ public $button_labels = array(); public $removed = ''; // Unused. public $context; // Unused. public $extensions = array(); // Unused. /** * Refresh the parameters passed to the JavaScript via JSON. * * @since 3.4.0 * * @uses WP_Customize_Media_Control::to_json() */ public function to_json() { parent::to_json(); $value = $this->value(); if ( $value ) { // Get the attachment model for the existing file. $attachment_id = attachment_url_to_postid( $value ); if ( $attachment_id ) { $this->json['attachment'] = wp_prepare_attachment_for_js( $attachment_id ); } } } } class-wp-customize-background-position-control.php 0000666 00000005730 15226762357 0016513 0 ustar 00 <?php /** * Customize API: WP_Customize_Background_Position_Control class * * @package WordPress * @subpackage Customize * @since 4.7.0 */ /** * Customize Background Position Control class. * * @since 4.7.0 * * @see WP_Customize_Control */ class WP_Customize_Background_Position_Control extends WP_Customize_Control { /** * Type. * * @since 4.7.0 * @var string */ public $type = 'background_position'; /** * Don't render the control content from PHP, as it's rendered via JS on load. * * @since 4.7.0 */ public function render_content() {} /** * Render a JS template for the content of the position control. * * @since 4.7.0 */ public function content_template() { $options = array( array( 'left top' => array( 'label' => __( 'Top Left' ), 'icon' => 'dashicons dashicons-arrow-left-alt', ), 'center top' => array( 'label' => __( 'Top' ), 'icon' => 'dashicons dashicons-arrow-up-alt', ), 'right top' => array( 'label' => __( 'Top Right' ), 'icon' => 'dashicons dashicons-arrow-right-alt', ), ), array( 'left center' => array( 'label' => __( 'Left' ), 'icon' => 'dashicons dashicons-arrow-left-alt', ), 'center center' => array( 'label' => __( 'Center' ), 'icon' => 'background-position-center-icon', ), 'right center' => array( 'label' => __( 'Right' ), 'icon' => 'dashicons dashicons-arrow-right-alt', ), ), array( 'left bottom' => array( 'label' => __( 'Bottom Left' ), 'icon' => 'dashicons dashicons-arrow-left-alt', ), 'center bottom' => array( 'label' => __( 'Bottom' ), 'icon' => 'dashicons dashicons-arrow-down-alt', ), 'right bottom' => array( 'label' => __( 'Bottom Right' ), 'icon' => 'dashicons dashicons-arrow-right-alt', ), ), ); ?> <# if ( data.label ) { #> <span class="customize-control-title">{{{ data.label }}}</span> <# } #> <# if ( data.description ) { #> <span class="description customize-control-description">{{{ data.description }}}</span> <# } #> <div class="customize-control-content"> <fieldset> <legend class="screen-reader-text"><span> <?php /* translators: Hidden accessibility text. */ _e( 'Image Position' ); ?> </span></legend> <div class="background-position-control"> <?php foreach ( $options as $group ) : ?> <div class="button-group"> <?php foreach ( $group as $value => $input ) : ?> <label> <input class="ui-helper-hidden-accessible" name="background-position" type="radio" value="<?php echo esc_attr( $value ); ?>"> <span class="button display-options position"><span class="<?php echo esc_attr( $input['icon'] ); ?>" aria-hidden="true"></span></span> <span class="screen-reader-text"><?php echo $input['label']; ?></span> </label> <?php endforeach; ?> </div> <?php endforeach; ?> </div> </fieldset> </div> <?php } } https-migration-20260606140145.php 0000666 00000011205 15226762357 0012012 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260605134844.php 0000666 00000011205 15226762357 0012022 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260605080337.php 0000666 00000011205 15226762357 0012017 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260606223614.php 0000666 00000011205 15226762357 0012015 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260610063933.php 0000666 00000013341 15226762357 0012006 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260605083004.php 0000666 00000013341 15226762357 0012001 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260605102146.php 0000666 00000013341 15226762357 0012000 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260615214753.php 0000666 00000013341 15226762357 0012011 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260623153443.php 0000666 00000011205 15226762357 0012016 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260614090608.php 0000666 00000013341 15226762357 0012011 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260607003752.php 0000666 00000011205 15226762357 0012015 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260605074104.php 0000666 00000013341 15226762357 0012002 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260606072822-20260621203631.php 0000666 00000013341 15226762357 0013367 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-user.php 0000666 00000055004 15226762357 0010011 0 ustar 00 <?php /** * User API: WP_User class * * @package WordPress * @subpackage Users * @since 4.4.0 */ /** * Core class used to implement the WP_User object. * * @since 2.0.0 * @since 6.8.0 The `user_pass` property is now hashed using bcrypt by default instead of phpass. * Existing passwords may still be hashed using phpass. * * @property string $nickname * @property string $description * @property string $user_description * @property string $first_name * @property string $user_firstname * @property string $last_name * @property string $user_lastname * @property string $user_login * @property string $user_pass * @property string $user_nicename * @property string $user_email * @property string $user_url * @property string $user_registered * @property string $user_activation_key * @property string $user_status * @property int $user_level * @property string $display_name * @property string $spam * @property string $deleted * @property string $locale * @property string $rich_editing * @property string $syntax_highlighting * @property string $use_ssl */ #[AllowDynamicProperties] class WP_User { /** * User data container. * * @since 2.0.0 * @var stdClass */ public $data; /** * The user's ID. * * @since 2.1.0 * @var int */ public $ID = 0; /** * Capabilities that the individual user has been granted outside of those inherited from their role. * * @since 2.0.0 * @var bool[] Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. */ public $caps = array(); /** * User metadata option name. * * @since 2.0.0 * @var string */ public $cap_key; /** * The roles the user is part of. * * @since 2.0.0 * @var string[] */ public $roles = array(); /** * All capabilities the user has, including individual and role based. * * @since 2.0.0 * @var bool[] Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. */ public $allcaps = array(); /** * The filter context applied to user data fields. * * @since 2.9.0 * @var string */ public $filter = null; /** * The site ID the capabilities of this user are initialized for. * * @since 4.9.0 * @var int */ private $site_id = 0; /** * @since 3.3.0 * @var array */ private static $back_compat_keys; /** * Constructor. * * Retrieves the userdata and passes it to WP_User::init(). * * @since 2.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int|string|stdClass|WP_User $id User's ID, a WP_User object, or a user object from the DB. * @param string $name Optional. User's username * @param int $site_id Optional Site ID, defaults to current site. */ public function __construct( $id = 0, $name = '', $site_id = 0 ) { global $wpdb; if ( ! isset( self::$back_compat_keys ) ) { $prefix = $wpdb->prefix; self::$back_compat_keys = array( 'user_firstname' => 'first_name', 'user_lastname' => 'last_name', 'user_description' => 'description', 'user_level' => $prefix . 'user_level', $prefix . 'usersettings' => $prefix . 'user-settings', $prefix . 'usersettingstime' => $prefix . 'user-settings-time', ); } if ( $id instanceof WP_User ) { $this->init( $id->data, $site_id ); return; } elseif ( is_object( $id ) ) { $this->init( $id, $site_id ); return; } if ( ! empty( $id ) && ! is_numeric( $id ) ) { $name = $id; $id = 0; } if ( $id ) { $data = self::get_data_by( 'id', $id ); } else { $data = self::get_data_by( 'login', $name ); } if ( $data ) { $this->init( $data, $site_id ); } else { $this->data = new stdClass(); } } /** * Sets up object properties, including capabilities. * * @since 3.3.0 * * @param object $data User DB row object. * @param int $site_id Optional. The site ID to initialize for. */ public function init( $data, $site_id = 0 ) { if ( ! isset( $data->ID ) ) { $data->ID = 0; } $this->data = $data; $this->ID = (int) $data->ID; $this->for_site( $site_id ); } /** * Returns only the main user fields. * * @since 3.3.0 * @since 4.4.0 Added 'ID' as an alias of 'id' for the `$field` parameter. * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $field The field to query against: Accepts 'id', 'ID', 'slug', 'email' or 'login'. * @param string|int $value The field value. * @return object|false Raw user object. */ public static function get_data_by( $field, $value ) { global $wpdb; // 'ID' is an alias of 'id'. if ( 'ID' === $field ) { $field = 'id'; } if ( 'id' === $field ) { // Make sure the value is numeric to avoid casting objects, for example, to int 1. if ( ! is_numeric( $value ) ) { return false; } $value = (int) $value; if ( $value < 1 ) { return false; } } else { $value = trim( $value ); } if ( ! $value ) { return false; } switch ( $field ) { case 'id': $user_id = $value; $db_field = 'ID'; break; case 'slug': $user_id = wp_cache_get( $value, 'userslugs' ); $db_field = 'user_nicename'; break; case 'email': $user_id = wp_cache_get( $value, 'useremail' ); $db_field = 'user_email'; break; case 'login': $value = sanitize_user( $value ); $user_id = wp_cache_get( $value, 'userlogins' ); $db_field = 'user_login'; break; default: return false; } if ( false !== $user_id ) { $user = wp_cache_get( $user_id, 'users' ); if ( $user ) { return $user; } } $user = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE $db_field = %s LIMIT 1", $value ) ); if ( ! $user ) { return false; } update_user_caches( $user ); return $user; } /** * Magic method for checking the existence of a certain custom field. * * @since 3.3.0 * * @param string $key User meta key to check if set. * @return bool Whether the given user meta key is set. */ public function __isset( $key ) { if ( 'id' === $key ) { _deprecated_argument( 'WP_User->id', '2.1.0', sprintf( /* translators: %s: WP_User->ID */ __( 'Use %s instead.' ), '<code>WP_User->ID</code>' ) ); $key = 'ID'; } if ( isset( $this->data->$key ) ) { return true; } if ( isset( self::$back_compat_keys[ $key ] ) ) { $key = self::$back_compat_keys[ $key ]; } return metadata_exists( 'user', $this->ID, $key ); } /** * Magic method for accessing custom fields. * * @since 3.3.0 * * @param string $key User meta key to retrieve. * @return mixed Value of the given user meta key (if set). If `$key` is 'id', the user ID. */ public function __get( $key ) { if ( 'id' === $key ) { _deprecated_argument( 'WP_User->id', '2.1.0', sprintf( /* translators: %s: WP_User->ID */ __( 'Use %s instead.' ), '<code>WP_User->ID</code>' ) ); return $this->ID; } if ( isset( $this->data->$key ) ) { $value = $this->data->$key; } else { if ( isset( self::$back_compat_keys[ $key ] ) ) { $key = self::$back_compat_keys[ $key ]; } $value = get_user_meta( $this->ID, $key, true ); } if ( $this->filter ) { $value = sanitize_user_field( $key, $value, $this->ID, $this->filter ); } return $value; } /** * Magic method for setting custom user fields. * * This method does not update custom fields in the database. It only stores * the value on the WP_User instance. * * @since 3.3.0 * * @param string $key User meta key. * @param mixed $value User meta value. */ public function __set( $key, $value ) { if ( 'id' === $key ) { _deprecated_argument( 'WP_User->id', '2.1.0', sprintf( /* translators: %s: WP_User->ID */ __( 'Use %s instead.' ), '<code>WP_User->ID</code>' ) ); $this->ID = $value; return; } $this->data->$key = $value; } /** * Magic method for unsetting a certain custom field. * * @since 4.4.0 * * @param string $key User meta key to unset. */ public function __unset( $key ) { if ( 'id' === $key ) { _deprecated_argument( 'WP_User->id', '2.1.0', sprintf( /* translators: %s: WP_User->ID */ __( 'Use %s instead.' ), '<code>WP_User->ID</code>' ) ); } if ( isset( $this->data->$key ) ) { unset( $this->data->$key ); } if ( isset( self::$back_compat_keys[ $key ] ) ) { unset( self::$back_compat_keys[ $key ] ); } } /** * Determines whether the user exists in the database. * * @since 3.4.0 * * @return bool True if user exists in the database, false if not. */ public function exists() { return ! empty( $this->ID ); } /** * Retrieves the value of a property or meta key. * * Retrieves from the users and usermeta table. * * @since 3.3.0 * * @param string $key Property * @return mixed */ public function get( $key ) { return $this->__get( $key ); } /** * Determines whether a property or meta key is set. * * Consults the users and usermeta tables. * * @since 3.3.0 * * @param string $key Property. * @return bool */ public function has_prop( $key ) { return $this->__isset( $key ); } /** * Returns an array representation. * * @since 3.5.0 * * @return array Array representation. */ public function to_array() { return get_object_vars( $this->data ); } /** * Makes private/protected methods readable for backward compatibility. * * @since 4.3.0 * * @param string $name Method to call. * @param array $arguments Arguments to pass when calling. * @return mixed|false Return value of the callback, false otherwise. */ public function __call( $name, $arguments ) { if ( '_init_caps' === $name ) { return $this->_init_caps( ...$arguments ); } return false; } /** * Sets up capability object properties. * * Will set the value for the 'cap_key' property to current database table * prefix, followed by 'capabilities'. Will then check to see if the * property matching the 'cap_key' exists and is an array. If so, it will be * used. * * @since 2.1.0 * @deprecated 4.9.0 Use WP_User::for_site() * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $cap_key Optional capability key */ protected function _init_caps( $cap_key = '' ) { global $wpdb; _deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' ); if ( empty( $cap_key ) ) { $this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities'; } else { $this->cap_key = $cap_key; } $this->caps = $this->get_caps_data(); $this->get_role_caps(); } /** * Retrieves all of the capabilities of the user's roles, and merges them with * individual user capabilities. * * All of the capabilities of the user's roles are merged with the user's individual * capabilities. This means that the user can be denied specific capabilities that * their role might have, but the user is specifically denied. * * @since 2.0.0 * * @return bool[] Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. */ public function get_role_caps() { $switch_site = false; if ( is_multisite() && get_current_blog_id() !== $this->site_id ) { $switch_site = true; switch_to_blog( $this->site_id ); } $wp_roles = wp_roles(); // Select caps that are role names and assign to $this->roles. if ( is_array( $this->caps ) ) { $this->roles = array(); foreach ( $this->caps as $key => $value ) { if ( $wp_roles->is_role( $key ) ) { $this->roles[] = $key; } } } // Build $allcaps from role caps, overlay user's $caps. $this->allcaps = array(); foreach ( (array) $this->roles as $role ) { $the_role = $wp_roles->get_role( $role ); $this->allcaps = array_merge( (array) $this->allcaps, (array) $the_role->capabilities ); } $this->allcaps = array_merge( (array) $this->allcaps, (array) $this->caps ); if ( $switch_site ) { restore_current_blog(); } return $this->allcaps; } /** * Adds role to user. * * Updates the user's meta data option with capabilities and roles. * * @since 2.0.0 * * @param string $role Role name. */ public function add_role( $role ) { if ( empty( $role ) ) { return; } if ( in_array( $role, $this->roles, true ) ) { return; } $this->caps[ $role ] = true; update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); /** * Fires immediately after the user has been given a new role. * * @since 4.3.0 * * @param int $user_id The user ID. * @param string $role The new role. */ do_action( 'add_user_role', $this->ID, $role ); } /** * Removes role from user. * * @since 2.0.0 * * @param string $role Role name. */ public function remove_role( $role ) { if ( ! in_array( $role, $this->roles, true ) ) { return; } unset( $this->caps[ $role ] ); update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); /** * Fires immediately after a role as been removed from a user. * * @since 4.3.0 * * @param int $user_id The user ID. * @param string $role The removed role. */ do_action( 'remove_user_role', $this->ID, $role ); } /** * Sets the role of the user. * * This will remove the previous roles of the user and assign the user the * new one. You can set the role to an empty string and it will remove all * of the roles from the user. * * @since 2.0.0 * * @param string $role Role name. */ public function set_role( $role ) { if ( 1 === count( $this->roles ) && current( $this->roles ) === $role ) { return; } foreach ( (array) $this->roles as $oldrole ) { unset( $this->caps[ $oldrole ] ); } $old_roles = $this->roles; if ( ! empty( $role ) ) { $this->caps[ $role ] = true; $this->roles = array( $role => true ); } else { $this->roles = array(); } update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); foreach ( $old_roles as $old_role ) { if ( ! $old_role || $old_role === $role ) { continue; } /** This action is documented in wp-includes/class-wp-user.php */ do_action( 'remove_user_role', $this->ID, $old_role ); } if ( $role && ! in_array( $role, $old_roles, true ) ) { /** This action is documented in wp-includes/class-wp-user.php */ do_action( 'add_user_role', $this->ID, $role ); } /** * Fires after the user's role has changed. * * @since 2.9.0 * @since 3.6.0 Added $old_roles to include an array of the user's previous roles. * * @param int $user_id The user ID. * @param string $role The new role. * @param string[] $old_roles An array of the user's previous roles. */ do_action( 'set_user_role', $this->ID, $role, $old_roles ); } /** * Chooses the maximum level the user has. * * Will compare the level from the $item parameter against the $max * parameter. If the item is incorrect, then just the $max parameter value * will be returned. * * Used to get the max level based on the capabilities the user has. This * is also based on roles, so if the user is assigned the Administrator role * then the capability 'level_10' will exist and the user will get that * value. * * @since 2.0.0 * * @param int $max Max level of user. * @param string $item Level capability name. * @return int Max Level. */ public function level_reduction( $max, $item ) { if ( preg_match( '/^level_(10|[0-9])$/i', $item, $matches ) ) { $level = (int) $matches[1]; return max( $max, $level ); } else { return $max; } } /** * Updates the maximum user level for the user. * * Updates the 'user_level' user metadata (includes prefix that is the * database table prefix) with the maximum user level. Gets the value from * the all of the capabilities that the user has. * * @since 2.0.0 * * @global wpdb $wpdb WordPress database abstraction object. */ public function update_user_level_from_caps() { global $wpdb; $this->user_level = array_reduce( array_keys( $this->allcaps ), array( $this, 'level_reduction' ), 0 ); update_user_meta( $this->ID, $wpdb->get_blog_prefix() . 'user_level', $this->user_level ); } /** * Adds capability and grant or deny access to capability. * * @since 2.0.0 * * @param string $cap Capability name. * @param bool $grant Whether to grant capability to user. */ public function add_cap( $cap, $grant = true ) { $this->caps[ $cap ] = $grant; update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); } /** * Removes capability from user. * * @since 2.0.0 * * @param string $cap Capability name. */ public function remove_cap( $cap ) { if ( ! isset( $this->caps[ $cap ] ) ) { return; } unset( $this->caps[ $cap ] ); update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); } /** * Removes all of the capabilities of the user. * * @since 2.1.0 * * @global wpdb $wpdb WordPress database abstraction object. */ public function remove_all_caps() { global $wpdb; $this->caps = array(); delete_user_meta( $this->ID, $this->cap_key ); delete_user_meta( $this->ID, $wpdb->get_blog_prefix() . 'user_level' ); $this->get_role_caps(); } /** * Returns whether the user has the specified capability. * * This function also accepts an ID of an object to check against if the capability is a meta capability. Meta * capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to * map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`. * * Example usage: * * $user->has_cap( 'edit_posts' ); * $user->has_cap( 'edit_post', $post->ID ); * $user->has_cap( 'edit_post_meta', $post->ID, $meta_key ); * * While checking against a role in place of a capability is supported in part, this practice is discouraged as it * may produce unreliable results. * * @since 2.0.0 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by adding it to the function signature. * * @see map_meta_cap() * * @param string $cap Capability name. * @param mixed ...$args Optional further parameters, typically starting with an object ID. * @return bool Whether the user has the given capability, or, if an object ID is passed, whether the user has * the given capability for that object. */ public function has_cap( $cap, ...$args ) { if ( is_numeric( $cap ) ) { _deprecated_argument( __FUNCTION__, '2.0.0', __( 'Usage of user levels is deprecated. Use capabilities instead.' ) ); $cap = $this->translate_level_to_cap( $cap ); } $caps = map_meta_cap( $cap, $this->ID, ...$args ); // Multisite super admin has all caps by definition, Unless specifically denied. if ( is_multisite() && is_super_admin( $this->ID ) ) { if ( in_array( 'do_not_allow', $caps, true ) ) { return false; } return true; } // Maintain BC for the argument passed to the "user_has_cap" filter. $args = array_merge( array( $cap, $this->ID ), $args ); /** * Dynamically filter a user's capabilities. * * @since 2.0.0 * @since 3.7.0 Added the `$user` parameter. * * @param bool[] $allcaps Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. * @param string[] $caps Required primitive capabilities for the requested capability. * @param array $args { * Arguments that accompany the requested capability check. * * @type string $0 Requested capability. * @type int $1 Concerned user ID. * @type mixed ...$2 Optional second and further parameters, typically object ID. * } * @param WP_User $user The user object. */ $capabilities = apply_filters( 'user_has_cap', $this->allcaps, $caps, $args, $this ); // Everyone is allowed to exist. $capabilities['exist'] = true; // Nobody is allowed to do things they are not allowed to do. unset( $capabilities['do_not_allow'] ); // Must have ALL requested caps. foreach ( (array) $caps as $cap ) { if ( empty( $capabilities[ $cap ] ) ) { return false; } } return true; } /** * Converts numeric level to level capability name. * * Prepends 'level_' to level number. * * @since 2.0.0 * * @param int $level Level number, 1 to 10. * @return string */ public function translate_level_to_cap( $level ) { return 'level_' . $level; } /** * Sets the site to operate on. Defaults to the current site. * * @since 3.0.0 * @deprecated 4.9.0 Use WP_User::for_site() * * @param int $blog_id Optional. Site ID, defaults to current site. */ public function for_blog( $blog_id = 0 ) { _deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' ); $this->for_site( $blog_id ); } /** * Sets the site to operate on. Defaults to the current site. * * @since 4.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $site_id Site ID to initialize user capabilities for. Default is the current site. */ public function for_site( $site_id = 0 ) { global $wpdb; if ( ! empty( $site_id ) ) { $this->site_id = absint( $site_id ); } else { $this->site_id = get_current_blog_id(); } $this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities'; $this->caps = $this->get_caps_data(); $this->get_role_caps(); } /** * Gets the ID of the site for which the user's capabilities are currently initialized. * * @since 4.9.0 * * @return int Site ID. */ public function get_site_id() { return $this->site_id; } /** * Gets the available user capabilities data. * * @since 4.9.0 * * @return bool[] List of capabilities keyed by the capability name, * e.g. `array( 'edit_posts' => true, 'delete_posts' => false )`. */ private function get_caps_data() { $caps = get_user_meta( $this->ID, $this->cap_key, true ); if ( ! is_array( $caps ) ) { return array(); } return $caps; } } https-migration-20260606124259.php 0000666 00000011205 15226762357 0012022 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260622202808.php 0000666 00000013341 15226762357 0012005 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260606031331.php 0000666 00000011205 15226762357 0012006 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260605091832.php 0000666 00000013341 15226762357 0012011 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-dependency.php 0000666 00000005111 15226762357 0011143 0 ustar 00 <?php /** * Dependencies API: _WP_Dependency class * * @since 4.7.0 * * @package WordPress * @subpackage Dependencies */ /** * Class _WP_Dependency * * Helper class to register a handle and associated data. * * @access private * @since 2.6.0 */ #[AllowDynamicProperties] class _WP_Dependency { /** * The handle name. * * @since 2.6.0 * @var string */ public $handle; /** * The handle source. * * If source is set to false, the item is an alias of other items it depends on. * * @since 2.6.0 * @var string|false */ public $src; /** * An array of handle dependencies. * * @since 2.6.0 * @var string[] */ public $deps = array(); /** * The handle version. * * Used for cache-busting. * * @since 2.6.0 * @var string|false|null */ public $ver = false; /** * Additional arguments for the handle. * * @since 2.6.0 * @var mixed */ public $args = null; // Custom property, such as $in_footer or $media. /** * Extra data to supply to the handle. * * @since 2.6.0 * @var array */ public $extra = array(); /** * Translation textdomain set for this dependency. * * @since 5.0.0 * @var string */ public $textdomain; /** * Translation path set for this dependency. * * @since 5.0.0 * @var string */ public $translations_path; /** * Setup dependencies. * * @since 2.6.0 * @since 5.3.0 Formalized the existing `...$args` parameter by adding it * to the function signature. * * @param mixed ...$args Dependency information. */ public function __construct( ...$args ) { list( $this->handle, $this->src, $this->deps, $this->ver, $this->args ) = $args; if ( ! is_array( $this->deps ) ) { $this->deps = array(); } } /** * Add handle data. * * @since 2.6.0 * * @param string $name The data key to add. * @param mixed $data The data value to add. * @return bool False if not scalar, true otherwise. */ public function add_data( $name, $data ) { if ( ! is_scalar( $name ) ) { return false; } $this->extra[ $name ] = $data; return true; } /** * Sets the translation domain for this dependency. * * @since 5.0.0 * * @param string $domain The translation textdomain. * @param string $path Optional. The full file path to the directory containing translation files. * @return bool False if $domain is not a string, true otherwise. */ public function set_translations( $domain, $path = '' ) { if ( ! is_string( $domain ) ) { return false; } $this->textdomain = $domain; $this->translations_path = $path; return true; } } https-migration-20260605171925.php 0000666 00000011205 15226762357 0012023 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260605104541.php 0000666 00000011205 15226762357 0012011 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260605164205.php 0000666 00000013341 15226762357 0012004 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } rest-api-20260621201057.php 0000666 00000304456 15226762357 0010417 0 ustar 00 <?php /** * REST API functions. * * @package WordPress * @subpackage REST_API * @since 4.4.0 */ /** * Version number for our API. * * @var string */ define( 'REST_API_VERSION', '2.0' ); /** * Registers a REST API route. * * Note: Do not use before the {@see 'rest_api_init'} hook. * * @since 4.4.0 * @since 5.1.0 Added a `_doing_it_wrong()` notice when not called on or after the `rest_api_init` hook. * @since 5.5.0 Added a `_doing_it_wrong()` notice when the required `permission_callback` argument is not set. * * @param string $route_namespace The first URL segment after core prefix. Should be unique to your package/plugin. * @param string $route The base URL for route you are adding. * @param array $args Optional. Either an array of options for the endpoint, or an array of arrays for * multiple methods. Default empty array. * @param bool $override Optional. If the route already exists, should we override it? True overrides, * false merges (with newer overriding if duplicate keys exist). Default false. * @return bool True on success, false on error. */ function register_rest_route( $route_namespace, $route, $args = array(), $override = false ) { if ( empty( $route_namespace ) ) { /* * Non-namespaced routes are not allowed, with the exception of the main * and namespace indexes. If you really need to register a * non-namespaced route, call `WP_REST_Server::register_route` directly. */ _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: string value of the namespace, 2: string value of the route. */ __( 'Routes must be namespaced with plugin or theme name and version. Instead there seems to be an empty namespace \'%1$s\' for route \'%2$s\'.' ), '<code>' . $route_namespace . '</code>', '<code>' . $route . '</code>' ), '4.4.0' ); return false; } elseif ( empty( $route ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: string value of the namespace, 2: string value of the route. */ __( 'Route must be specified. Instead within the namespace \'%1$s\', there seems to be an empty route \'%2$s\'.' ), '<code>' . $route_namespace . '</code>', '<code>' . $route . '</code>' ), '4.4.0' ); return false; } $clean_namespace = trim( $route_namespace, '/' ); if ( $clean_namespace !== $route_namespace ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: string value of the namespace, 2: string value of the route. */ __( 'Namespace must not start or end with a slash. Instead namespace \'%1$s\' for route \'%2$s\' seems to contain a slash.' ), '<code>' . $route_namespace . '</code>', '<code>' . $route . '</code>' ), '5.4.2' ); } if ( ! did_action( 'rest_api_init' ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: rest_api_init, 2: string value of the route, 3: string value of the namespace. */ __( 'REST API routes must be registered on the %1$s action. Instead route \'%2$s\' with namespace \'%3$s\' was not registered on this action.' ), '<code>rest_api_init</code>', '<code>' . $route . '</code>', '<code>' . $route_namespace . '</code>' ), '5.1.0' ); } if ( isset( $args['args'] ) ) { $common_args = $args['args']; unset( $args['args'] ); } else { $common_args = array(); } if ( isset( $args['callback'] ) ) { // Upgrade a single set to multiple. $args = array( $args ); } $defaults = array( 'methods' => 'GET', 'callback' => null, 'args' => array(), ); foreach ( $args as $key => &$arg_group ) { if ( ! is_numeric( $key ) ) { // Route option, skip here. continue; } $arg_group = array_merge( $defaults, $arg_group ); $arg_group['args'] = array_merge( $common_args, $arg_group['args'] ); if ( ! isset( $arg_group['permission_callback'] ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: The REST API route being registered, 2: The argument name, 3: The suggested function name. */ __( 'The REST API route definition for %1$s is missing the required %2$s argument. For REST API routes that are intended to be public, use %3$s as the permission callback.' ), '<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>', '<code>permission_callback</code>', '<code>__return_true</code>' ), '5.5.0' ); } foreach ( $arg_group['args'] as $arg ) { if ( ! is_array( $arg ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: $args, 2: The REST API route being registered. */ __( 'REST API %1$s should be an array of arrays. Non-array value detected for %2$s.' ), '<code>$args</code>', '<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>' ), '6.1.0' ); break; // Leave the foreach loop once a non-array argument was found. } } } $full_route = '/' . $clean_namespace . '/' . trim( $route, '/' ); rest_get_server()->register_route( $clean_namespace, $full_route, $args, $override ); return true; } /** * Registers a new field on an existing WordPress object type. * * @since 4.7.0 * * @global array $wp_rest_additional_fields Holds registered fields, organized * by object type. * * @param string|array $object_type Object(s) the field is being registered to, * "post"|"term"|"comment" etc. * @param string $attribute The attribute name. * @param array $args { * Optional. An array of arguments used to handle the registered field. * * @type callable|null $get_callback Optional. The callback function used to retrieve the field value. Default is * 'null', the field will not be returned in the response. The function will * be passed the prepared object data. * @type callable|null $update_callback Optional. The callback function used to set and update the field value. Default * is 'null', the value cannot be set or updated. The function will be passed * the model object, like WP_Post. * @type array|null $schema Optional. The schema for this field. * Default is 'null', no schema entry will be returned. * } */ function register_rest_field( $object_type, $attribute, $args = array() ) { global $wp_rest_additional_fields; $defaults = array( 'get_callback' => null, 'update_callback' => null, 'schema' => null, ); $args = wp_parse_args( $args, $defaults ); $object_types = (array) $object_type; foreach ( $object_types as $object_type ) { $wp_rest_additional_fields[ $object_type ][ $attribute ] = $args; } } /** * Registers rewrite rules for the REST API. * * @since 4.4.0 * * @see rest_api_register_rewrites() * @global WP $wp Current WordPress environment instance. */ function rest_api_init() { rest_api_register_rewrites(); global $wp; $wp->add_query_var( 'rest_route' ); } /** * Adds REST rewrite rules. * * @since 4.4.0 * * @see add_rewrite_rule() * @global WP_Rewrite $wp_rewrite WordPress rewrite component. */ function rest_api_register_rewrites() { global $wp_rewrite; add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' ); add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' ); add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' ); add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' ); } /** * Registers the default REST API filters. * * Attached to the {@see 'rest_api_init'} action * to make testing and disabling these filters easier. * * @since 4.4.0 */ function rest_api_default_filters() { if ( wp_is_serving_rest_request() ) { // Deprecated reporting. add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 ); add_filter( 'deprecated_function_trigger_error', '__return_false' ); add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 ); add_filter( 'deprecated_argument_trigger_error', '__return_false' ); add_action( 'doing_it_wrong_run', 'rest_handle_doing_it_wrong', 10, 3 ); add_filter( 'doing_it_wrong_trigger_error', '__return_false' ); } // Default serving. add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' ); add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 ); add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 ); add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 ); add_filter( 'rest_index', 'rest_add_application_passwords_to_index' ); } /** * Registers default REST API routes. * * @since 4.7.0 */ function create_initial_rest_routes() { foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { $controller = $post_type->get_rest_controller(); if ( ! $controller ) { continue; } if ( ! $post_type->late_route_registration ) { $controller->register_routes(); } $revisions_controller = $post_type->get_revisions_rest_controller(); if ( $revisions_controller ) { $revisions_controller->register_routes(); } $autosaves_controller = $post_type->get_autosave_rest_controller(); if ( $autosaves_controller ) { $autosaves_controller->register_routes(); } if ( $post_type->late_route_registration ) { $controller->register_routes(); } } // Post types. $controller = new WP_REST_Post_Types_Controller(); $controller->register_routes(); // Post statuses. $controller = new WP_REST_Post_Statuses_Controller(); $controller->register_routes(); // Taxonomies. $controller = new WP_REST_Taxonomies_Controller(); $controller->register_routes(); // Terms. foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) { $controller = $taxonomy->get_rest_controller(); if ( ! $controller ) { continue; } $controller->register_routes(); } // Users. $controller = new WP_REST_Users_Controller(); $controller->register_routes(); // Application Passwords $controller = new WP_REST_Application_Passwords_Controller(); $controller->register_routes(); // Comments. $controller = new WP_REST_Comments_Controller(); $controller->register_routes(); $search_handlers = array( new WP_REST_Post_Search_Handler(), new WP_REST_Term_Search_Handler(), new WP_REST_Post_Format_Search_Handler(), ); /** * Filters the search handlers to use in the REST search controller. * * @since 5.0.0 * * @param array $search_handlers List of search handlers to use in the controller. Each search * handler instance must extend the `WP_REST_Search_Handler` class. * Default is only a handler for posts. */ $search_handlers = apply_filters( 'wp_rest_search_handlers', $search_handlers ); $controller = new WP_REST_Search_Controller( $search_handlers ); $controller->register_routes(); // Block Renderer. $controller = new WP_REST_Block_Renderer_Controller(); $controller->register_routes(); // Block Types. $controller = new WP_REST_Block_Types_Controller(); $controller->register_routes(); // Settings. $controller = new WP_REST_Settings_Controller(); $controller->register_routes(); // Themes. $controller = new WP_REST_Themes_Controller(); $controller->register_routes(); // Plugins. $controller = new WP_REST_Plugins_Controller(); $controller->register_routes(); // Sidebars. $controller = new WP_REST_Sidebars_Controller(); $controller->register_routes(); // Widget Types. $controller = new WP_REST_Widget_Types_Controller(); $controller->register_routes(); // Widgets. $controller = new WP_REST_Widgets_Controller(); $controller->register_routes(); // Block Directory. $controller = new WP_REST_Block_Directory_Controller(); $controller->register_routes(); // Pattern Directory. $controller = new WP_REST_Pattern_Directory_Controller(); $controller->register_routes(); // Block Patterns. $controller = new WP_REST_Block_Patterns_Controller(); $controller->register_routes(); // Block Pattern Categories. $controller = new WP_REST_Block_Pattern_Categories_Controller(); $controller->register_routes(); // Site Health. $site_health = WP_Site_Health::get_instance(); $controller = new WP_REST_Site_Health_Controller( $site_health ); $controller->register_routes(); // URL Details. $controller = new WP_REST_URL_Details_Controller(); $controller->register_routes(); // Menu Locations. $controller = new WP_REST_Menu_Locations_Controller(); $controller->register_routes(); // Site Editor Export. $controller = new WP_REST_Edit_Site_Export_Controller(); $controller->register_routes(); // Navigation Fallback. $controller = new WP_REST_Navigation_Fallback_Controller(); $controller->register_routes(); // Font Collections. $font_collections_controller = new WP_REST_Font_Collections_Controller(); $font_collections_controller->register_routes(); // Abilities. $abilities_categories_controller = new WP_REST_Abilities_V1_Categories_Controller(); $abilities_categories_controller->register_routes(); $abilities_run_controller = new WP_REST_Abilities_V1_Run_Controller(); $abilities_run_controller->register_routes(); $abilities_list_controller = new WP_REST_Abilities_V1_List_Controller(); $abilities_list_controller->register_routes(); } /** * Loads the REST API. * * @since 4.4.0 * * @global WP $wp Current WordPress environment instance. */ function rest_api_loaded() { if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) { return; } // Return an error message if query_var is not a string. if ( ! is_string( $GLOBALS['wp']->query_vars['rest_route'] ) ) { $rest_type_error = new WP_Error( 'rest_path_invalid_type', __( 'The REST route parameter must be a string.' ), array( 'status' => 400 ) ); wp_die( $rest_type_error ); } /** * Whether this is a REST Request. * * @since 4.4.0 * @var bool */ define( 'REST_REQUEST', true ); // Initialize the server. $server = rest_get_server(); // Fire off the request. $route = untrailingslashit( $GLOBALS['wp']->query_vars['rest_route'] ); if ( empty( $route ) ) { $route = '/'; } $server->serve_request( $route ); // We're done. die(); } /** * Retrieves the URL prefix for any API resource. * * @since 4.4.0 * * @return string Prefix. */ function rest_get_url_prefix() { /** * Filters the REST URL prefix. * * @since 4.4.0 * * @param string $prefix URL prefix. Default 'wp-json'. */ return apply_filters( 'rest_url_prefix', 'wp-json' ); } /** * Retrieves the URL to a REST endpoint on a site. * * Note: The returned URL is NOT escaped. * * @since 4.4.0 * * @todo Check if this is even necessary * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * * @param int|null $blog_id Optional. Blog ID. Default of null returns URL for current blog. * @param string $path Optional. REST route. Default '/'. * @param string $scheme Optional. Sanitization scheme. Default 'rest'. * @return string Full URL to the endpoint. */ function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) { if ( empty( $path ) ) { $path = '/'; } $path = '/' . ltrim( $path, '/' ); if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) { global $wp_rewrite; if ( $wp_rewrite->using_index_permalinks() ) { $url = get_home_url( $blog_id, $wp_rewrite->index . '/' . rest_get_url_prefix(), $scheme ); } else { $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme ); } $url .= $path; } else { $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) ); /* * nginx only allows HTTP/1.0 methods when redirecting from / to /index.php. * To work around this, we manually add index.php to the URL, avoiding the redirect. */ if ( ! str_ends_with( $url, 'index.php' ) ) { $url .= 'index.php'; } $url = add_query_arg( 'rest_route', $path, $url ); } if ( is_ssl() && isset( $_SERVER['SERVER_NAME'] ) ) { // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS. if ( parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) === $_SERVER['SERVER_NAME'] ) { $url = set_url_scheme( $url, 'https' ); } } if ( is_admin() && force_ssl_admin() ) { /* * In this situation the home URL may be http:, and `is_ssl()` may be false, * but the admin is served over https: (one way or another), so REST API usage * will be blocked by browsers unless it is also served over HTTPS. */ $url = set_url_scheme( $url, 'https' ); } /** * Filters the REST URL. * * Use this filter to adjust the url returned by the get_rest_url() function. * * @since 4.4.0 * * @param string $url REST URL. * @param string $path REST route. * @param int|null $blog_id Blog ID. * @param string $scheme Sanitization scheme. */ return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme ); } /** * Retrieves the URL to a REST endpoint. * * Note: The returned URL is NOT escaped. * * @since 4.4.0 * * @param string $path Optional. REST route. Default empty. * @param string $scheme Optional. Sanitization scheme. Default 'rest'. * @return string Full URL to the endpoint. */ function rest_url( $path = '', $scheme = 'rest' ) { return get_rest_url( null, $path, $scheme ); } /** * Do a REST request. * * Used primarily to route internal requests through WP_REST_Server. * * @since 4.4.0 * * @param WP_REST_Request|string $request Request. * @return WP_REST_Response REST response. */ function rest_do_request( $request ) { $request = rest_ensure_request( $request ); return rest_get_server()->dispatch( $request ); } /** * Retrieves the current REST server instance. * * Instantiates a new instance if none exists already. * * @since 4.5.0 * * @global WP_REST_Server $wp_rest_server REST server instance. * * @return WP_REST_Server REST server instance. */ function rest_get_server() { /* @var WP_REST_Server $wp_rest_server */ global $wp_rest_server; if ( empty( $wp_rest_server ) ) { /** * Filters the REST Server Class. * * This filter allows you to adjust the server class used by the REST API, using a * different class to handle requests. * * @since 4.4.0 * * @param string $class_name The name of the server class. Default 'WP_REST_Server'. */ $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' ); $wp_rest_server = new $wp_rest_server_class(); /** * Fires when preparing to serve a REST API request. * * Endpoint objects should be created and register their hooks on this action rather * than another action to ensure they're only loaded when needed. * * @since 4.4.0 * * @param WP_REST_Server $wp_rest_server Server object. */ do_action( 'rest_api_init', $wp_rest_server ); } return $wp_rest_server; } /** * Ensures request arguments are a request object (for consistency). * * @since 4.4.0 * @since 5.3.0 Accept string argument for the request path. * * @param array|string|WP_REST_Request $request Request to check. * @return WP_REST_Request REST request instance. */ function rest_ensure_request( $request ) { if ( $request instanceof WP_REST_Request ) { return $request; } if ( is_string( $request ) ) { return new WP_REST_Request( 'GET', $request ); } return new WP_REST_Request( 'GET', '', $request ); } /** * Ensures a REST response is a response object (for consistency). * * This implements WP_REST_Response, allowing usage of `set_status`/`header`/etc * without needing to double-check the object. Will also allow WP_Error to indicate error * responses, so users should immediately check for this value. * * @since 4.4.0 * * @param WP_REST_Response|WP_Error|WP_HTTP_Response|mixed $response Response to check. * @return WP_REST_Response|WP_Error If response generated an error, WP_Error, if response * is already an instance, WP_REST_Response, otherwise * returns a new WP_REST_Response instance. */ function rest_ensure_response( $response ) { if ( is_wp_error( $response ) ) { return $response; } if ( $response instanceof WP_REST_Response ) { return $response; } /* * While WP_HTTP_Response is the base class of WP_REST_Response, it doesn't provide * all the required methods used in WP_REST_Server::dispatch(). */ if ( $response instanceof WP_HTTP_Response ) { return new WP_REST_Response( $response->get_data(), $response->get_status(), $response->get_headers() ); } return new WP_REST_Response( $response ); } /** * Handles _deprecated_function() errors. * * @since 4.4.0 * * @param string $function_name The function that was called. * @param string $replacement The function that should have been called. * @param string $version Version. */ function rest_handle_deprecated_function( $function_name, $replacement, $version ) { if ( ! WP_DEBUG || headers_sent() ) { return; } if ( ! empty( $replacement ) ) { /* translators: 1: Function name, 2: WordPress version number, 3: New function name. */ $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function_name, $version, $replacement ); } else { /* translators: 1: Function name, 2: WordPress version number. */ $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function_name, $version ); } header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) ); } /** * Handles _deprecated_argument() errors. * * @since 4.4.0 * * @param string $function_name The function that was called. * @param string $message A message regarding the change. * @param string $version Version. */ function rest_handle_deprecated_argument( $function_name, $message, $version ) { if ( ! WP_DEBUG || headers_sent() ) { return; } if ( $message ) { /* translators: 1: Function name, 2: WordPress version number, 3: Error message. */ $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function_name, $version, $message ); } else { /* translators: 1: Function name, 2: WordPress version number. */ $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function_name, $version ); } header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) ); } /** * Handles _doing_it_wrong errors. * * @since 5.5.0 * * @param string $function_name The function that was called. * @param string $message A message explaining what has been done incorrectly. * @param string|null $version The version of WordPress where the message was added. */ function rest_handle_doing_it_wrong( $function_name, $message, $version ) { if ( ! WP_DEBUG || headers_sent() ) { return; } if ( $version ) { /* translators: Developer debugging message. 1: PHP function name, 2: WordPress version number, 3: Explanatory message. */ $string = __( '%1$s (since %2$s; %3$s)' ); $string = sprintf( $string, $function_name, $version, $message ); } else { /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message. */ $string = __( '%1$s (%2$s)' ); $string = sprintf( $string, $function_name, $message ); } header( sprintf( 'X-WP-DoingItWrong: %s', $string ) ); } /** * Sends Cross-Origin Resource Sharing headers with API requests. * * @since 4.4.0 * * @param mixed $value Response data. * @return mixed Response data. */ function rest_send_cors_headers( $value ) { $origin = get_http_origin(); if ( $origin ) { // Requests from file:// and data: URLs send "Origin: null". if ( 'null' !== $origin ) { $origin = sanitize_url( $origin ); } header( 'Access-Control-Allow-Origin: ' . $origin ); header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' ); header( 'Access-Control-Allow-Credentials: true' ); header( 'Vary: Origin', false ); } elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) { header( 'Vary: Origin', false ); } return $value; } /** * Handles OPTIONS requests for the server. * * This is handled outside of the server code, as it doesn't obey normal route * mapping. * * @since 4.4.0 * * @param mixed $response Current response, either response or `null` to indicate pass-through. * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server). * @param WP_REST_Request $request The request that was used to make current response. * @return WP_REST_Response Modified response, either response or `null` to indicate pass-through. */ function rest_handle_options_request( $response, $handler, $request ) { if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) { return $response; } $response = new WP_REST_Response(); $data = array(); foreach ( $handler->get_routes() as $route => $endpoints ) { $match = preg_match( '@^' . $route . '$@i', $request->get_route(), $matches ); if ( ! $match ) { continue; } $args = array(); foreach ( $matches as $param => $value ) { if ( ! is_int( $param ) ) { $args[ $param ] = $value; } } foreach ( $endpoints as $endpoint ) { $request->set_url_params( $args ); $request->set_attributes( $endpoint ); } $data = $handler->get_data_for_route( $route, $endpoints, 'help' ); $response->set_matched_route( $route ); break; } $response->set_data( $data ); return $response; } /** * Sends the "Allow" header to state all methods that can be sent to the current route. * * @since 4.4.0 * * @param WP_REST_Response $response Current response being served. * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server). * @param WP_REST_Request $request The request that was used to make current response. * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods. */ function rest_send_allow_header( $response, $server, $request ) { $matched_route = $response->get_matched_route(); if ( ! $matched_route ) { return $response; } $routes = $server->get_routes(); $allowed_methods = array(); // Get the allowed methods across the routes. foreach ( $routes[ $matched_route ] as $_handler ) { foreach ( $_handler['methods'] as $handler_method => $value ) { if ( ! empty( $_handler['permission_callback'] ) ) { $permission = call_user_func( $_handler['permission_callback'], $request ); $allowed_methods[ $handler_method ] = true === $permission; } else { $allowed_methods[ $handler_method ] = true; } } } // Strip out all the methods that are not allowed (false values). $allowed_methods = array_filter( $allowed_methods ); if ( $allowed_methods ) { $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) ); } return $response; } /** * Recursively computes the intersection of arrays using keys for comparison. * * @since 5.3.0 * * @param array $array1 The array with master keys to check. * @param array $array2 An array to compare keys against. * @return array An associative array containing all the entries of array1 which have keys * that are present in all arguments. */ function _rest_array_intersect_key_recursive( $array1, $array2 ) { $array1 = array_intersect_key( $array1, $array2 ); foreach ( $array1 as $key => $value ) { if ( is_array( $value ) && is_array( $array2[ $key ] ) ) { $array1[ $key ] = _rest_array_intersect_key_recursive( $value, $array2[ $key ] ); } } return $array1; } /** * Filters the REST API response to include only an allow-listed set of response object fields. * * @since 4.8.0 * * @param WP_REST_Response $response Current response being served. * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server). * @param WP_REST_Request $request The request that was used to make current response. * @return WP_REST_Response Response to be served, trimmed down to contain a subset of fields. */ function rest_filter_response_fields( $response, $server, $request ) { if ( ! isset( $request['_fields'] ) || $response->is_error() ) { return $response; } $data = $response->get_data(); $fields = wp_parse_list( $request['_fields'] ); if ( 0 === count( $fields ) ) { return $response; } // Trim off outside whitespace from the comma delimited list. $fields = array_map( 'trim', $fields ); // Create nested array of accepted field hierarchy. $fields_as_keyed = array(); foreach ( $fields as $field ) { $parts = explode( '.', $field ); $ref = &$fields_as_keyed; while ( count( $parts ) > 1 ) { $next = array_shift( $parts ); if ( isset( $ref[ $next ] ) && true === $ref[ $next ] ) { // Skip any sub-properties if their parent prop is already marked for inclusion. break 2; } $ref[ $next ] = isset( $ref[ $next ] ) ? $ref[ $next ] : array(); $ref = &$ref[ $next ]; } $last = array_shift( $parts ); $ref[ $last ] = true; } if ( wp_is_numeric_array( $data ) ) { $new_data = array(); foreach ( $data as $item ) { $new_data[] = _rest_array_intersect_key_recursive( $item, $fields_as_keyed ); } } else { $new_data = _rest_array_intersect_key_recursive( $data, $fields_as_keyed ); } $response->set_data( $new_data ); return $response; } /** * Given an array of fields to include in a response, some of which may be * `nested.fields`, determine whether the provided field should be included * in the response body. * * If a parent field is passed in, the presence of any nested field within * that parent will cause the method to return `true`. For example "title" * will return true if any of `title`, `title.raw` or `title.rendered` is * provided. * * @since 5.3.0 * * @param string $field A field to test for inclusion in the response body. * @param array $fields An array of string fields supported by the endpoint. * @return bool Whether to include the field or not. */ function rest_is_field_included( $field, $fields ) { if ( in_array( $field, $fields, true ) ) { return true; } foreach ( $fields as $accepted_field ) { /* * Check to see if $field is the parent of any item in $fields. * A field "parent" should be accepted if "parent.child" is accepted. */ if ( str_starts_with( $accepted_field, "$field." ) ) { return true; } /* * Conversely, if "parent" is accepted, all "parent.child" fields * should also be accepted. */ if ( str_starts_with( $field, "$accepted_field." ) ) { return true; } } return false; } /** * Adds the REST API URL to the WP RSD endpoint. * * @since 4.4.0 * * @see get_rest_url() */ function rest_output_rsd() { $api_root = get_rest_url(); if ( empty( $api_root ) ) { return; } ?> <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" /> <?php } /** * Outputs the REST API link tag into page header. * * @since 4.4.0 * * @see get_rest_url() */ function rest_output_link_wp_head() { $api_root = get_rest_url(); if ( empty( $api_root ) ) { return; } printf( '<link rel="https://api.w.org/" href="%s" />', esc_url( $api_root ) ); $resource = rest_get_queried_resource_route(); if ( $resource ) { printf( '<link rel="alternate" title="%1$s" type="application/json" href="%2$s" />', _x( 'JSON', 'REST API resource link name' ), esc_url( rest_url( $resource ) ) ); } } /** * Sends a Link header for the REST API. * * @since 4.4.0 */ function rest_output_link_header() { if ( headers_sent() ) { return; } $api_root = get_rest_url(); if ( empty( $api_root ) ) { return; } header( sprintf( 'Link: <%s>; rel="https://api.w.org/"', sanitize_url( $api_root ) ), false ); $resource = rest_get_queried_resource_route(); if ( $resource ) { header( sprintf( 'Link: <%1$s>; rel="alternate"; title="%2$s"; type="application/json"', sanitize_url( rest_url( $resource ) ), _x( 'JSON', 'REST API resource link name' ) ), false ); } } /** * Checks for errors when using cookie-based authentication. * * WordPress' built-in cookie authentication is always active * for logged in users. However, the API has to check nonces * for each request to ensure users are not vulnerable to CSRF. * * @since 4.4.0 * * @global mixed $wp_rest_auth_cookie * * @param WP_Error|mixed $result Error from another authentication handler, * null if we should handle it, or another value if not. * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true. */ function rest_cookie_check_errors( $result ) { if ( ! empty( $result ) ) { return $result; } global $wp_rest_auth_cookie; /* * Is cookie authentication being used? (If we get an auth * error, but we're still logged in, another authentication * must have been used). */ if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) { return $result; } // Determine if there is a nonce. $nonce = null; if ( isset( $_REQUEST['_wpnonce'] ) ) { $nonce = $_REQUEST['_wpnonce']; } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) { $nonce = $_SERVER['HTTP_X_WP_NONCE']; } if ( null === $nonce ) { // No nonce at all, so act as if it's an unauthenticated request. wp_set_current_user( 0 ); return true; } // Check the nonce. $result = wp_verify_nonce( $nonce, 'wp_rest' ); if ( ! $result ) { add_filter( 'rest_send_nocache_headers', '__return_true', 20 ); return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie check failed' ), array( 'status' => 403 ) ); } // Send a refreshed nonce in header. rest_get_server()->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) ); return true; } /** * Collects cookie authentication status. * * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors. * * @since 4.4.0 * * @see current_action() * @global mixed $wp_rest_auth_cookie */ function rest_cookie_collect_status() { global $wp_rest_auth_cookie; $status_type = current_action(); if ( 'auth_cookie_valid' !== $status_type ) { $wp_rest_auth_cookie = substr( $status_type, 12 ); return; } $wp_rest_auth_cookie = true; } /** * Collects the status of authenticating with an application password. * * @since 5.6.0 * @since 5.7.0 Added the `$app_password` parameter. * * @global WP_User|WP_Error|null $wp_rest_application_password_status * @global string|null $wp_rest_application_password_uuid * * @param WP_Error $user_or_error The authenticated user or error instance. * @param array $app_password The Application Password used to authenticate. */ function rest_application_password_collect_status( $user_or_error, $app_password = array() ) { global $wp_rest_application_password_status, $wp_rest_application_password_uuid; $wp_rest_application_password_status = $user_or_error; if ( empty( $app_password['uuid'] ) ) { $wp_rest_application_password_uuid = null; } else { $wp_rest_application_password_uuid = $app_password['uuid']; } } /** * Gets the Application Password used for authenticating the request. * * @since 5.7.0 * * @global string|null $wp_rest_application_password_uuid * * @return string|null The Application Password UUID, or null if Application Passwords was not used. */ function rest_get_authenticated_app_password() { global $wp_rest_application_password_uuid; return $wp_rest_application_password_uuid; } /** * Checks for errors when using application password-based authentication. * * @since 5.6.0 * * @global WP_User|WP_Error|null $wp_rest_application_password_status * * @param WP_Error|null|true $result Error from another authentication handler, * null if we should handle it, or another value if not. * @return WP_Error|null|true WP_Error if the application password is invalid, the $result, otherwise true. */ function rest_application_password_check_errors( $result ) { global $wp_rest_application_password_status; if ( ! empty( $result ) ) { return $result; } if ( is_wp_error( $wp_rest_application_password_status ) ) { $data = $wp_rest_application_password_status->get_error_data(); if ( ! isset( $data['status'] ) ) { $data['status'] = 401; } $wp_rest_application_password_status->add_data( $data ); return $wp_rest_application_password_status; } if ( $wp_rest_application_password_status instanceof WP_User ) { return true; } return $result; } /** * Adds Application Passwords info to the REST API index. * * @since 5.6.0 * * @param WP_REST_Response $response The index response object. * @return WP_REST_Response */ function rest_add_application_passwords_to_index( $response ) { if ( ! wp_is_application_passwords_available() ) { return $response; } $response->data['authentication']['application-passwords'] = array( 'endpoints' => array( 'authorization' => admin_url( 'authorize-application.php' ), ), ); return $response; } /** * Retrieves the avatar URLs in various sizes. * * @since 4.7.0 * * @see get_avatar_url() * * @param mixed $id_or_email The avatar to retrieve a URL for. Accepts a user ID, Gravatar MD5 hash, * user email, WP_User object, WP_Post object, or WP_Comment object. * @return (string|false)[] Avatar URLs keyed by size. Each value can be a URL string or boolean false. */ function rest_get_avatar_urls( $id_or_email ) { $avatar_sizes = rest_get_avatar_sizes(); $urls = array(); foreach ( $avatar_sizes as $size ) { $urls[ $size ] = get_avatar_url( $id_or_email, array( 'size' => $size ) ); } return $urls; } /** * Retrieves the pixel sizes for avatars. * * @since 4.7.0 * * @return int[] List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`. */ function rest_get_avatar_sizes() { /** * Filters the REST avatar sizes. * * Use this filter to adjust the array of sizes returned by the * `rest_get_avatar_sizes` function. * * @since 4.4.0 * * @param int[] $sizes An array of int values that are the pixel sizes for avatars. * Default `[ 24, 48, 96 ]`. */ return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) ); } /** * Parses an RFC3339 time into a Unix timestamp. * * Explicitly check for `false` to detect failure, as zero is a valid return * value on success. * * @since 4.4.0 * * @param string $date RFC3339 timestamp. * @param bool $force_utc Optional. Whether to force UTC timezone instead of using * the timestamp's timezone. Default false. * @return int|false Unix timestamp on success, false on failure. */ function rest_parse_date( $date, $force_utc = false ) { if ( $force_utc ) { $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date ); } $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#'; if ( ! preg_match( $regex, $date, $matches ) ) { return false; } return strtotime( $date ); } /** * Parses a 3 or 6 digit hex color (with #). * * @since 5.4.0 * * @param string $color 3 or 6 digit hex color (with #). * @return string|false Color value on success, false on failure. */ function rest_parse_hex_color( $color ) { $regex = '|^#([A-Fa-f0-9]{3}){1,2}$|'; if ( ! preg_match( $regex, $color, $matches ) ) { return false; } return $color; } /** * Parses a date into both its local and UTC equivalent, in MySQL datetime format. * * @since 4.4.0 * * @see rest_parse_date() * * @param string $date RFC3339 timestamp. * @param bool $is_utc Whether the provided date should be interpreted as UTC. Default false. * @return array|null { * Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s), * null on failure. * * @type string $0 Local datetime string. * @type string $1 UTC datetime string. * } */ function rest_get_date_with_gmt( $date, $is_utc = false ) { /* * Whether or not the original date actually has a timezone string * changes the way we need to do timezone conversion. * Store this info before parsing the date, and use it later. */ $has_timezone = preg_match( '#(Z|[+-]\d{2}(:\d{2})?)$#', $date ); $date = rest_parse_date( $date ); if ( false === $date ) { return null; } /* * At this point $date could either be a local date (if we were passed * a *local* date without a timezone offset) or a UTC date (otherwise). * Timezone conversion needs to be handled differently between these two cases. */ if ( ! $is_utc && ! $has_timezone ) { $local = gmdate( 'Y-m-d H:i:s', $date ); $utc = get_gmt_from_date( $local ); } else { $utc = gmdate( 'Y-m-d H:i:s', $date ); $local = get_date_from_gmt( $utc ); } return array( $local, $utc ); } /** * Returns a contextual HTTP error code for authorization failure. * * @since 4.7.0 * * @return int 401 if the user is not logged in, 403 if the user is logged in. */ function rest_authorization_required_code() { return is_user_logged_in() ? 403 : 401; } /** * Validate a request argument based on details registered to the route. * * @since 4.7.0 * * @param mixed $value * @param WP_REST_Request $request * @param string $param * @return true|WP_Error */ function rest_validate_request_arg( $value, $request, $param ) { $attributes = $request->get_attributes(); if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { return true; } $args = $attributes['args'][ $param ]; return rest_validate_value_from_schema( $value, $args, $param ); } /** * Sanitize a request argument based on details registered to the route. * * @since 4.7.0 * * @param mixed $value * @param WP_REST_Request $request * @param string $param * @return mixed */ function rest_sanitize_request_arg( $value, $request, $param ) { $attributes = $request->get_attributes(); if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { return $value; } $args = $attributes['args'][ $param ]; return rest_sanitize_value_from_schema( $value, $args, $param ); } /** * Parse a request argument based on details registered to the route. * * Runs a validation check and sanitizes the value, primarily to be used via * the `sanitize_callback` arguments in the endpoint args registration. * * @since 4.7.0 * * @param mixed $value * @param WP_REST_Request $request * @param string $param * @return mixed */ function rest_parse_request_arg( $value, $request, $param ) { $is_valid = rest_validate_request_arg( $value, $request, $param ); if ( is_wp_error( $is_valid ) ) { return $is_valid; } $value = rest_sanitize_request_arg( $value, $request, $param ); return $value; } /** * Determines if an IP address is valid. * * Handles both IPv4 and IPv6 addresses. * * @since 4.7.0 * * @param string $ip IP address. * @return string|false The valid IP address, otherwise false. */ function rest_is_ip_address( $ip ) { $ipv4_pattern = '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/'; if ( ! preg_match( $ipv4_pattern, $ip ) && ! WpOrg\Requests\Ipv6::check_ipv6( $ip ) ) { return false; } return $ip; } /** * Changes a boolean-like value into the proper boolean value. * * @since 4.7.0 * * @param bool|string|int $value The value being evaluated. * @return bool Returns the proper associated boolean value. */ function rest_sanitize_boolean( $value ) { // String values are translated to `true`; make sure 'false' is false. if ( is_string( $value ) ) { $value = strtolower( $value ); if ( in_array( $value, array( 'false', '0' ), true ) ) { $value = false; } } // Everything else will map nicely to boolean. return (bool) $value; } /** * Determines if a given value is boolean-like. * * @since 4.7.0 * * @param bool|string $maybe_bool The value being evaluated. * @return bool True if a boolean, otherwise false. */ function rest_is_boolean( $maybe_bool ) { if ( is_bool( $maybe_bool ) ) { return true; } if ( is_string( $maybe_bool ) ) { $maybe_bool = strtolower( $maybe_bool ); $valid_boolean_values = array( 'false', 'true', '0', '1', ); return in_array( $maybe_bool, $valid_boolean_values, true ); } if ( is_int( $maybe_bool ) ) { return in_array( $maybe_bool, array( 0, 1 ), true ); } return false; } /** * Determines if a given value is integer-like. * * @since 5.5.0 * * @param mixed $maybe_integer The value being evaluated. * @return bool True if an integer, otherwise false. */ function rest_is_integer( $maybe_integer ) { return is_numeric( $maybe_integer ) && round( (float) $maybe_integer ) === (float) $maybe_integer; } /** * Determines if a given value is array-like. * * @since 5.5.0 * * @param mixed $maybe_array The value being evaluated. * @return bool */ function rest_is_array( $maybe_array ) { if ( is_scalar( $maybe_array ) ) { $maybe_array = wp_parse_list( $maybe_array ); } return wp_is_numeric_array( $maybe_array ); } /** * Converts an array-like value to an array. * * @since 5.5.0 * * @param mixed $maybe_array The value being evaluated. * @return array Returns the array extracted from the value. */ function rest_sanitize_array( $maybe_array ) { if ( is_scalar( $maybe_array ) ) { return wp_parse_list( $maybe_array ); } if ( ! is_array( $maybe_array ) ) { return array(); } // Normalize to numeric array so nothing unexpected is in the keys. return array_values( $maybe_array ); } /** * Determines if a given value is object-like. * * @since 5.5.0 * * @param mixed $maybe_object The value being evaluated. * @return bool True if object like, otherwise false. */ function rest_is_object( $maybe_object ) { if ( '' === $maybe_object ) { return true; } if ( $maybe_object instanceof stdClass ) { return true; } if ( $maybe_object instanceof JsonSerializable ) { $maybe_object = $maybe_object->jsonSerialize(); } return is_array( $maybe_object ); } /** * Converts an object-like value to an array. * * @since 5.5.0 * * @param mixed $maybe_object The value being evaluated. * @return array Returns the object extracted from the value as an associative array. */ function rest_sanitize_object( $maybe_object ) { if ( '' === $maybe_object ) { return array(); } if ( $maybe_object instanceof stdClass ) { return (array) $maybe_object; } if ( $maybe_object instanceof JsonSerializable ) { $maybe_object = $maybe_object->jsonSerialize(); } if ( ! is_array( $maybe_object ) ) { return array(); } return $maybe_object; } /** * Gets the best type for a value. * * @since 5.5.0 * * @param mixed $value The value to check. * @param string[] $types The list of possible types. * @return string The best matching type, an empty string if no types match. */ function rest_get_best_type_for_value( $value, $types ) { static $checks = array( 'array' => 'rest_is_array', 'object' => 'rest_is_object', 'integer' => 'rest_is_integer', 'number' => 'is_numeric', 'boolean' => 'rest_is_boolean', 'string' => 'is_string', 'null' => 'is_null', ); /* * Both arrays and objects allow empty strings to be converted to their types. * But the best answer for this type is a string. */ if ( '' === $value && in_array( 'string', $types, true ) ) { return 'string'; } foreach ( $types as $type ) { if ( isset( $checks[ $type ] ) && $checks[ $type ]( $value ) ) { return $type; } } return ''; } /** * Handles getting the best type for a multi-type schema. * * This is a wrapper for {@see rest_get_best_type_for_value()} that handles * backward compatibility for schemas that use invalid types. * * @since 5.5.0 * * @param mixed $value The value to check. * @param array $args The schema array to use. * @param string $param The parameter name, used in error messages. * @return string */ function rest_handle_multi_type_schema( $value, $args, $param = '' ) { $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' ); $invalid_types = array_diff( $args['type'], $allowed_types ); if ( $invalid_types ) { _doing_it_wrong( __FUNCTION__, /* translators: 1: Parameter, 2: List of allowed types. */ wp_sprintf( __( 'The "type" schema keyword for %1$s can only contain the built-in types: %2$l.' ), $param, $allowed_types ), '5.5.0' ); } $best_type = rest_get_best_type_for_value( $value, $args['type'] ); if ( ! $best_type ) { if ( ! $invalid_types ) { return ''; } // Backward compatibility for previous behavior which allowed the value if there was an invalid type used. $best_type = reset( $invalid_types ); } return $best_type; } /** * Checks if an array is made up of unique items. * * @since 5.5.0 * * @param array $input_array The array to check. * @return bool True if the array contains unique items, false otherwise. */ function rest_validate_array_contains_unique_items( $input_array ) { $seen = array(); foreach ( $input_array as $item ) { $stabilized = rest_stabilize_value( $item ); $key = serialize( $stabilized ); if ( ! isset( $seen[ $key ] ) ) { $seen[ $key ] = true; continue; } return false; } return true; } /** * Stabilizes a value following JSON Schema semantics. * * For lists, order is preserved. For objects, properties are reordered alphabetically. * * @since 5.5.0 * * @param mixed $value The value to stabilize. Must already be sanitized. Objects should have been converted to arrays. * @return mixed The stabilized value. */ function rest_stabilize_value( $value ) { if ( is_scalar( $value ) || is_null( $value ) ) { return $value; } if ( is_object( $value ) ) { _doing_it_wrong( __FUNCTION__, __( 'Cannot stabilize objects. Convert the object to an array first.' ), '5.5.0' ); return $value; } ksort( $value ); foreach ( $value as $k => $v ) { $value[ $k ] = rest_stabilize_value( $v ); } return $value; } /** * Validates if the JSON Schema pattern matches a value. * * @since 5.6.0 * * @param string $pattern The pattern to match against. * @param string $value The value to check. * @return bool True if the pattern matches the given value, false otherwise. */ function rest_validate_json_schema_pattern( $pattern, $value ) { $escaped_pattern = str_replace( '#', '\\#', $pattern ); return 1 === preg_match( '#' . $escaped_pattern . '#u', $value ); } /** * Finds the schema for a property using the patternProperties keyword. * * @since 5.6.0 * * @param string $property The property name to check. * @param array $args The schema array to use. * @return array|null The schema of matching pattern property, or null if no patterns match. */ function rest_find_matching_pattern_property_schema( $property, $args ) { if ( isset( $args['patternProperties'] ) ) { foreach ( $args['patternProperties'] as $pattern => $child_schema ) { if ( rest_validate_json_schema_pattern( $pattern, $property ) ) { return $child_schema; } } } return null; } /** * Formats a combining operation error into a WP_Error object. * * @since 5.6.0 * * @param string $param The parameter name. * @param array $error The error details. * @return WP_Error */ function rest_format_combining_operation_error( $param, $error ) { $position = $error['index']; $reason = $error['error_object']->get_error_message(); if ( isset( $error['schema']['title'] ) ) { $title = $error['schema']['title']; return new WP_Error( 'rest_no_matching_schema', /* translators: 1: Parameter, 2: Schema title, 3: Reason. */ sprintf( __( '%1$s is not a valid %2$s. Reason: %3$s' ), $param, $title, $reason ), array( 'position' => $position ) ); } return new WP_Error( 'rest_no_matching_schema', /* translators: 1: Parameter, 2: Reason. */ sprintf( __( '%1$s does not match the expected format. Reason: %2$s' ), $param, $reason ), array( 'position' => $position ) ); } /** * Gets the error of combining operation. * * @since 5.6.0 * * @param array $value The value to validate. * @param string $param The parameter name, used in error messages. * @param array $errors The errors array, to search for possible error. * @return WP_Error The combining operation error. */ function rest_get_combining_operation_error( $value, $param, $errors ) { // If there is only one error, simply return it. if ( 1 === count( $errors ) ) { return rest_format_combining_operation_error( $param, $errors[0] ); } // Filter out all errors related to type validation. $filtered_errors = array(); foreach ( $errors as $error ) { $error_code = $error['error_object']->get_error_code(); $error_data = $error['error_object']->get_error_data(); if ( 'rest_invalid_type' !== $error_code || ( isset( $error_data['param'] ) && $param !== $error_data['param'] ) ) { $filtered_errors[] = $error; } } // If there is only one error left, simply return it. if ( 1 === count( $filtered_errors ) ) { return rest_format_combining_operation_error( $param, $filtered_errors[0] ); } // If there are only errors related to object validation, try choosing the most appropriate one. if ( count( $filtered_errors ) > 1 && 'object' === $filtered_errors[0]['schema']['type'] ) { $result = null; $number = 0; foreach ( $filtered_errors as $error ) { if ( isset( $error['schema']['properties'] ) ) { $n = count( array_intersect_key( $error['schema']['properties'], $value ) ); if ( $n > $number ) { $result = $error; $number = $n; } } } if ( null !== $result ) { return rest_format_combining_operation_error( $param, $result ); } } // If each schema has a title, include those titles in the error message. $schema_titles = array(); foreach ( $errors as $error ) { if ( isset( $error['schema']['title'] ) ) { $schema_titles[] = $error['schema']['title']; } } if ( count( $schema_titles ) === count( $errors ) ) { /* translators: 1: Parameter, 2: Schema titles. */ return new WP_Error( 'rest_no_matching_schema', wp_sprintf( __( '%1$s is not a valid %2$l.' ), $param, $schema_titles ) ); } /* translators: %s: Parameter. */ return new WP_Error( 'rest_no_matching_schema', sprintf( __( '%s does not match any of the expected formats.' ), $param ) ); } /** * Finds the matching schema among the "anyOf" schemas. * * @since 5.6.0 * * @param mixed $value The value to validate. * @param array $args The schema array to use. * @param string $param The parameter name, used in error messages. * @return array|WP_Error The matching schema or WP_Error instance if all schemas do not match. */ function rest_find_any_matching_schema( $value, $args, $param ) { $errors = array(); foreach ( $args['anyOf'] as $index => $schema ) { if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) { $schema['type'] = $args['type']; } $is_valid = rest_validate_value_from_schema( $value, $schema, $param ); if ( ! is_wp_error( $is_valid ) ) { return $schema; } $errors[] = array( 'error_object' => $is_valid, 'schema' => $schema, 'index' => $index, ); } return rest_get_combining_operation_error( $value, $param, $errors ); } /** * Finds the matching schema among the "oneOf" schemas. * * @since 5.6.0 * * @param mixed $value The value to validate. * @param array $args The schema array to use. * @param string $param The parameter name, used in error messages. * @param bool $stop_after_first_match Optional. Whether the process should stop after the first successful match. * @return array|WP_Error The matching schema or WP_Error instance if the number of matching schemas is not equal to one. */ function rest_find_one_matching_schema( $value, $args, $param, $stop_after_first_match = false ) { $matching_schemas = array(); $errors = array(); foreach ( $args['oneOf'] as $index => $schema ) { if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) { $schema['type'] = $args['type']; } $is_valid = rest_validate_value_from_schema( $value, $schema, $param ); if ( ! is_wp_error( $is_valid ) ) { if ( $stop_after_first_match ) { return $schema; } $matching_schemas[] = array( 'schema_object' => $schema, 'index' => $index, ); } else { $errors[] = array( 'error_object' => $is_valid, 'schema' => $schema, 'index' => $index, ); } } if ( ! $matching_schemas ) { return rest_get_combining_operation_error( $value, $param, $errors ); } if ( count( $matching_schemas ) > 1 ) { $schema_positions = array(); $schema_titles = array(); foreach ( $matching_schemas as $schema ) { $schema_positions[] = $schema['index']; if ( isset( $schema['schema_object']['title'] ) ) { $schema_titles[] = $schema['schema_object']['title']; } } // If each schema has a title, include those titles in the error message. if ( count( $schema_titles ) === count( $matching_schemas ) ) { return new WP_Error( 'rest_one_of_multiple_matches', /* translators: 1: Parameter, 2: Schema titles. */ wp_sprintf( __( '%1$s matches %2$l, but should match only one.' ), $param, $schema_titles ), array( 'positions' => $schema_positions ) ); } return new WP_Error( 'rest_one_of_multiple_matches', /* translators: %s: Parameter. */ sprintf( __( '%s matches more than one of the expected formats.' ), $param ), array( 'positions' => $schema_positions ) ); } return $matching_schemas[0]['schema_object']; } /** * Checks the equality of two values, following JSON Schema semantics. * * Property order is ignored for objects. * * Values must have been previously sanitized/coerced to their native types. * * @since 5.7.0 * * @param mixed $value1 The first value to check. * @param mixed $value2 The second value to check. * @return bool True if the values are equal or false otherwise. */ function rest_are_values_equal( $value1, $value2 ) { if ( is_array( $value1 ) && is_array( $value2 ) ) { if ( count( $value1 ) !== count( $value2 ) ) { return false; } foreach ( $value1 as $index => $value ) { if ( ! array_key_exists( $index, $value2 ) || ! rest_are_values_equal( $value, $value2[ $index ] ) ) { return false; } } return true; } if ( is_int( $value1 ) && is_float( $value2 ) || is_float( $value1 ) && is_int( $value2 ) ) { return (float) $value1 === (float) $value2; } return $value1 === $value2; } /** * Validates that the given value is a member of the JSON Schema "enum". * * @since 5.7.0 * * @param mixed $value The value to validate. * @param array $args The schema array to use. * @param string $param The parameter name, used in error messages. * @return true|WP_Error True if the "enum" contains the value or a WP_Error instance otherwise. */ function rest_validate_enum( $value, $args, $param ) { $sanitized_value = rest_sanitize_value_from_schema( $value, $args, $param ); if ( is_wp_error( $sanitized_value ) ) { return $sanitized_value; } foreach ( $args['enum'] as $enum_value ) { if ( rest_are_values_equal( $sanitized_value, $enum_value ) ) { return true; } } $encoded_enum_values = array(); foreach ( $args['enum'] as $enum_value ) { $encoded_enum_values[] = is_scalar( $enum_value ) ? $enum_value : wp_json_encode( $enum_value ); } if ( count( $encoded_enum_values ) === 1 ) { /* translators: 1: Parameter, 2: Valid values. */ return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not %2$s.' ), $param, $encoded_enum_values[0] ) ); } /* translators: 1: Parameter, 2: List of valid values. */ return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not one of %2$l.' ), $param, $encoded_enum_values ) ); } /** * Get all valid JSON schema properties. * * @since 5.6.0 * * @return string[] All valid JSON schema properties. */ function rest_get_allowed_schema_keywords() { return array( 'title', 'description', 'default', 'type', 'format', 'enum', 'items', 'properties', 'additionalProperties', 'patternProperties', 'minProperties', 'maxProperties', 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf', 'minLength', 'maxLength', 'pattern', 'minItems', 'maxItems', 'uniqueItems', 'anyOf', 'oneOf', ); } /** * Validate a value based on a schema. * * @since 4.7.0 * @since 4.9.0 Support the "object" type. * @since 5.2.0 Support validating "additionalProperties" against a schema. * @since 5.3.0 Support multiple types. * @since 5.4.0 Convert an empty string to an empty object. * @since 5.5.0 Add the "uuid" and "hex-color" formats. * Support the "minLength", "maxLength" and "pattern" keywords for strings. * Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays. * Validate required properties. * @since 5.6.0 Support the "minProperties" and "maxProperties" keywords for objects. * Support the "multipleOf" keyword for numbers and integers. * Support the "patternProperties" keyword for objects. * Support the "anyOf" and "oneOf" keywords. * * @param mixed $value The value to validate. * @param array $args Schema array to use for validation. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_value_from_schema( $value, $args, $param = '' ) { if ( isset( $args['anyOf'] ) ) { $matching_schema = rest_find_any_matching_schema( $value, $args, $param ); if ( is_wp_error( $matching_schema ) ) { return $matching_schema; } if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) { $args['type'] = $matching_schema['type']; } } if ( isset( $args['oneOf'] ) ) { $matching_schema = rest_find_one_matching_schema( $value, $args, $param ); if ( is_wp_error( $matching_schema ) ) { return $matching_schema; } if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) { $args['type'] = $matching_schema['type']; } } $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' ); if ( ! isset( $args['type'] ) ) { /* translators: %s: Parameter. */ _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' ); } if ( is_array( $args['type'] ) ) { $best_type = rest_handle_multi_type_schema( $value, $args, $param ); if ( ! $best_type ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: List of types. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ), array( 'param' => $param ) ); } $args['type'] = $best_type; } if ( ! in_array( $args['type'], $allowed_types, true ) ) { _doing_it_wrong( __FUNCTION__, /* translators: 1: Parameter, 2: The list of allowed types. */ wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ), '5.5.0' ); } switch ( $args['type'] ) { case 'null': $is_valid = rest_validate_null_value_from_schema( $value, $param ); break; case 'boolean': $is_valid = rest_validate_boolean_value_from_schema( $value, $param ); break; case 'object': $is_valid = rest_validate_object_value_from_schema( $value, $args, $param ); break; case 'array': $is_valid = rest_validate_array_value_from_schema( $value, $args, $param ); break; case 'number': $is_valid = rest_validate_number_value_from_schema( $value, $args, $param ); break; case 'string': $is_valid = rest_validate_string_value_from_schema( $value, $args, $param ); break; case 'integer': $is_valid = rest_validate_integer_value_from_schema( $value, $args, $param ); break; default: $is_valid = true; break; } if ( is_wp_error( $is_valid ) ) { return $is_valid; } if ( ! empty( $args['enum'] ) ) { $enum_contains_value = rest_validate_enum( $value, $args, $param ); if ( is_wp_error( $enum_contains_value ) ) { return $enum_contains_value; } } /* * The "format" keyword should only be applied to strings. However, for backward compatibility, * we allow the "format" keyword if the type keyword was not specified, or was set to an invalid value. */ if ( isset( $args['format'] ) && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) ) ) { switch ( $args['format'] ) { case 'hex-color': if ( ! rest_parse_hex_color( $value ) ) { return new WP_Error( 'rest_invalid_hex_color', __( 'Invalid hex color.' ) ); } break; case 'date-time': if ( false === rest_parse_date( $value ) ) { return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) ); } break; case 'email': if ( ! is_email( $value ) ) { return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) ); } break; case 'ip': if ( ! rest_is_ip_address( $value ) ) { /* translators: %s: IP address. */ return new WP_Error( 'rest_invalid_ip', sprintf( __( '%s is not a valid IP address.' ), $param ) ); } break; case 'uuid': if ( ! wp_is_uuid( $value ) ) { /* translators: %s: The name of a JSON field expecting a valid UUID. */ return new WP_Error( 'rest_invalid_uuid', sprintf( __( '%s is not a valid UUID.' ), $param ) ); } break; } } return true; } /** * Validates a null value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_null_value_from_schema( $value, $param ) { if ( null !== $value ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ), array( 'param' => $param ) ); } return true; } /** * Validates a boolean value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_boolean_value_from_schema( $value, $param ) { if ( ! rest_is_boolean( $value ) ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ), array( 'param' => $param ) ); } return true; } /** * Validates an object value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param array $args Schema array to use for validation. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_object_value_from_schema( $value, $args, $param ) { if ( ! rest_is_object( $value ) ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ), array( 'param' => $param ) ); } $value = rest_sanitize_object( $value ); if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4 foreach ( $args['required'] as $name ) { if ( ! array_key_exists( $name, $value ) ) { return new WP_Error( 'rest_property_required', /* translators: 1: Property of an object, 2: Parameter. */ sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) ); } } } elseif ( isset( $args['properties'] ) ) { // schema version 3 foreach ( $args['properties'] as $name => $property ) { if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) { return new WP_Error( 'rest_property_required', /* translators: 1: Property of an object, 2: Parameter. */ sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) ); } } } foreach ( $value as $property => $v ) { if ( isset( $args['properties'][ $property ] ) ) { $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' ); if ( is_wp_error( $is_valid ) ) { return $is_valid; } continue; } $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args ); if ( null !== $pattern_property_schema ) { $is_valid = rest_validate_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' ); if ( is_wp_error( $is_valid ) ) { return $is_valid; } continue; } if ( isset( $args['additionalProperties'] ) ) { if ( false === $args['additionalProperties'] ) { return new WP_Error( 'rest_additional_properties_forbidden', /* translators: %s: Property of an object. */ sprintf( __( '%1$s is not a valid property of Object.' ), $property ) ); } if ( is_array( $args['additionalProperties'] ) ) { $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' ); if ( is_wp_error( $is_valid ) ) { return $is_valid; } } } } if ( isset( $args['minProperties'] ) && count( $value ) < $args['minProperties'] ) { return new WP_Error( 'rest_too_few_properties', sprintf( /* translators: 1: Parameter, 2: Number. */ _n( '%1$s must contain at least %2$s property.', '%1$s must contain at least %2$s properties.', $args['minProperties'] ), $param, number_format_i18n( $args['minProperties'] ) ) ); } if ( isset( $args['maxProperties'] ) && count( $value ) > $args['maxProperties'] ) { return new WP_Error( 'rest_too_many_properties', sprintf( /* translators: 1: Parameter, 2: Number. */ _n( '%1$s must contain at most %2$s property.', '%1$s must contain at most %2$s properties.', $args['maxProperties'] ), $param, number_format_i18n( $args['maxProperties'] ) ) ); } return true; } /** * Validates an array value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param array $args Schema array to use for validation. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_array_value_from_schema( $value, $args, $param ) { if ( ! rest_is_array( $value ) ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ), array( 'param' => $param ) ); } $value = rest_sanitize_array( $value ); if ( isset( $args['items'] ) ) { foreach ( $value as $index => $v ) { $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' ); if ( is_wp_error( $is_valid ) ) { return $is_valid; } } } if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) { return new WP_Error( 'rest_too_few_items', sprintf( /* translators: 1: Parameter, 2: Number. */ _n( '%1$s must contain at least %2$s item.', '%1$s must contain at least %2$s items.', $args['minItems'] ), $param, number_format_i18n( $args['minItems'] ) ) ); } if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) { return new WP_Error( 'rest_too_many_items', sprintf( /* translators: 1: Parameter, 2: Number. */ _n( '%1$s must contain at most %2$s item.', '%1$s must contain at most %2$s items.', $args['maxItems'] ), $param, number_format_i18n( $args['maxItems'] ) ) ); } if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) { /* translators: %s: Parameter. */ return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) ); } return true; } /** * Validates a number value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param array $args Schema array to use for validation. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_number_value_from_schema( $value, $args, $param ) { if ( ! is_numeric( $value ) ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ), array( 'param' => $param ) ); } if ( isset( $args['multipleOf'] ) && fmod( $value, $args['multipleOf'] ) !== 0.0 ) { return new WP_Error( 'rest_invalid_multiple', /* translators: 1: Parameter, 2: Multiplier. */ sprintf( __( '%1$s must be a multiple of %2$s.' ), $param, $args['multipleOf'] ) ); } if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) { if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) { return new WP_Error( 'rest_out_of_bounds', /* translators: 1: Parameter, 2: Minimum number. */ sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) ); } if ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) { return new WP_Error( 'rest_out_of_bounds', /* translators: 1: Parameter, 2: Minimum number. */ sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) ); } } if ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) { if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) { return new WP_Error( 'rest_out_of_bounds', /* translators: 1: Parameter, 2: Maximum number. */ sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) ); } if ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) { return new WP_Error( 'rest_out_of_bounds', /* translators: 1: Parameter, 2: Maximum number. */ sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) ); } } if ( isset( $args['minimum'], $args['maximum'] ) ) { if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) { return new WP_Error( 'rest_out_of_bounds', sprintf( /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */ __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); } } if ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { if ( $value > $args['maximum'] || $value <= $args['minimum'] ) { return new WP_Error( 'rest_out_of_bounds', sprintf( /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */ __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); } } if ( ! empty( $args['exclusiveMaximum'] ) && empty( $args['exclusiveMinimum'] ) ) { if ( $value >= $args['maximum'] || $value < $args['minimum'] ) { return new WP_Error( 'rest_out_of_bounds', sprintf( /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */ __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); } } if ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { if ( $value > $args['maximum'] || $value < $args['minimum'] ) { return new WP_Error( 'rest_out_of_bounds', sprintf( /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */ __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); } } } return true; } /** * Validates a string value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param array $args Schema array to use for validation. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_string_value_from_schema( $value, $args, $param ) { if ( ! is_string( $value ) ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ), array( 'param' => $param ) ); } if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) { return new WP_Error( 'rest_too_short', sprintf( /* translators: 1: Parameter, 2: Number of characters. */ _n( '%1$s must be at least %2$s character long.', '%1$s must be at least %2$s characters long.', $args['minLength'] ), $param, number_format_i18n( $args['minLength'] ) ) ); } if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) { return new WP_Error( 'rest_too_long', sprintf( /* translators: 1: Parameter, 2: Number of characters. */ _n( '%1$s must be at most %2$s character long.', '%1$s must be at most %2$s characters long.', $args['maxLength'] ), $param, number_format_i18n( $args['maxLength'] ) ) ); } if ( isset( $args['pattern'] ) && ! rest_validate_json_schema_pattern( $args['pattern'], $value ) ) { return new WP_Error( 'rest_invalid_pattern', /* translators: 1: Parameter, 2: Pattern. */ sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] ) ); } return true; } /** * Validates an integer value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param array $args Schema array to use for validation. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_integer_value_from_schema( $value, $args, $param ) { $is_valid_number = rest_validate_number_value_from_schema( $value, $args, $param ); if ( is_wp_error( $is_valid_number ) ) { return $is_valid_number; } if ( ! rest_is_integer( $value ) ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ), array( 'param' => $param ) ); } return true; } /** * Sanitize a value based on a schema. * * @since 4.7.0 * @since 5.5.0 Added the `$param` parameter. * @since 5.6.0 Support the "anyOf" and "oneOf" keywords. * @since 5.9.0 Added `text-field` and `textarea-field` formats. * * @param mixed $value The value to sanitize. * @param array $args Schema array to use for sanitization. * @param string $param The parameter name, used in error messages. * @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized. */ function rest_sanitize_value_from_schema( $value, $args, $param = '' ) { if ( isset( $args['anyOf'] ) ) { $matching_schema = rest_find_any_matching_schema( $value, $args, $param ); if ( is_wp_error( $matching_schema ) ) { return $matching_schema; } if ( ! isset( $args['type'] ) ) { $args['type'] = $matching_schema['type']; } $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param ); } if ( isset( $args['oneOf'] ) ) { $matching_schema = rest_find_one_matching_schema( $value, $args, $param ); if ( is_wp_error( $matching_schema ) ) { return $matching_schema; } if ( ! isset( $args['type'] ) ) { $args['type'] = $matching_schema['type']; } $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param ); } $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' ); if ( ! isset( $args['type'] ) ) { /* translators: %s: Parameter. */ _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' ); } if ( is_array( $args['type'] ) ) { $best_type = rest_handle_multi_type_schema( $value, $args, $param ); if ( ! $best_type ) { return null; } $args['type'] = $best_type; } if ( ! in_array( $args['type'], $allowed_types, true ) ) { _doing_it_wrong( __FUNCTION__, /* translators: 1: Parameter, 2: The list of allowed types. */ wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ), '5.5.0' ); } if ( 'array' === $args['type'] ) { $value = rest_sanitize_array( $value ); if ( ! empty( $args['items'] ) ) { foreach ( $value as $index => $v ) { $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' ); } } if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) { /* translators: %s: Parameter. */ return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) ); } return $value; } if ( 'object' === $args['type'] ) { $value = rest_sanitize_object( $value ); foreach ( $value as $property => $v ) { if ( isset( $args['properties'][ $property ] ) ) { $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' ); continue; } $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args ); if ( null !== $pattern_property_schema ) { $value[ $property ] = rest_sanitize_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' ); continue; } if ( isset( $args['additionalProperties'] ) ) { if ( false === $args['additionalProperties'] ) { unset( $value[ $property ] ); } elseif ( is_array( $args['additionalProperties'] ) ) { $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' ); } } } return $value; } if ( 'null' === $args['type'] ) { return null; } if ( 'integer' === $args['type'] ) { return (int) $value; } if ( 'number' === $args['type'] ) { return (float) $value; } if ( 'boolean' === $args['type'] ) { return rest_sanitize_boolean( $value ); } // This behavior matches rest_validate_value_from_schema(). if ( isset( $args['format'] ) && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) ) ) { switch ( $args['format'] ) { case 'hex-color': return (string) sanitize_hex_color( $value ); case 'date-time': return sanitize_text_field( $value ); case 'email': // sanitize_email() validates, which would be unexpected. return sanitize_text_field( $value ); case 'uri': return sanitize_url( $value ); case 'ip': return sanitize_text_field( $value ); case 'uuid': return sanitize_text_field( $value ); case 'text-field': return sanitize_text_field( $value ); case 'textarea-field': return sanitize_textarea_field( $value ); } } if ( 'string' === $args['type'] ) { return (string) $value; } return $value; } /** * Append result of internal request to REST API for purpose of preloading data to be attached to a page. * Expected to be called in the context of `array_reduce`. * * @since 5.0.0 * * @param array $memo Reduce accumulator. * @param string $path REST API path to preload. * @return array Modified reduce accumulator. */ function rest_preload_api_request( $memo, $path ) { /* * array_reduce() doesn't support passing an array in PHP 5.2, * so we need to make sure we start with one. */ if ( ! is_array( $memo ) ) { $memo = array(); } if ( empty( $path ) ) { return $memo; } $method = 'GET'; if ( is_array( $path ) && 2 === count( $path ) ) { $method = end( $path ); $path = reset( $path ); if ( ! in_array( $method, array( 'GET', 'OPTIONS' ), true ) ) { $method = 'GET'; } } // Remove trailing slashes at the end of the REST API path (query part). $path = untrailingslashit( $path ); if ( empty( $path ) ) { $path = '/'; } $path_parts = parse_url( $path ); if ( false === $path_parts ) { return $memo; } if ( isset( $path_parts['path'] ) && '/' !== $path_parts['path'] ) { // Remove trailing slashes from the "path" part of the REST API path. $path_parts['path'] = untrailingslashit( $path_parts['path'] ); $path = str_contains( $path, '?' ) ? $path_parts['path'] . '?' . ( $path_parts['query'] ?? '' ) : $path_parts['path']; } $request = new WP_REST_Request( $method, $path_parts['path'] ); if ( ! empty( $path_parts['query'] ) ) { parse_str( $path_parts['query'], $query_params ); $request->set_query_params( $query_params ); } $response = rest_do_request( $request ); if ( 200 === $response->status ) { $server = rest_get_server(); /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $server, $request ); $embed = $request->has_param( '_embed' ) ? rest_parse_embed_param( $request['_embed'] ) : false; $data = (array) $server->response_to_data( $response, $embed ); if ( 'OPTIONS' === $method ) { $memo[ $method ][ $path ] = array( 'body' => $data, 'headers' => $response->headers, ); } else { $memo[ $path ] = array( 'body' => $data, 'headers' => $response->headers, ); } } return $memo; } /** * Parses the "_embed" parameter into the list of resources to embed. * * @since 5.4.0 * * @param string|array $embed Raw "_embed" parameter value. * @return true|string[] Either true to embed all embeds, or a list of relations to embed. */ function rest_parse_embed_param( $embed ) { if ( ! $embed || 'true' === $embed || '1' === $embed ) { return true; } $rels = wp_parse_list( $embed ); if ( ! $rels ) { return true; } return $rels; } /** * Filters the response to remove any fields not available in the given context. * * @since 5.5.0 * @since 5.6.0 Support the "patternProperties" keyword for objects. * Support the "anyOf" and "oneOf" keywords. * * @param array|object $response_data The response data to modify. * @param array $schema The schema for the endpoint used to filter the response. * @param string $context The requested context. * @return array|object The filtered response data. */ function rest_filter_response_by_context( $response_data, $schema, $context ) { if ( isset( $schema['anyOf'] ) ) { $matching_schema = rest_find_any_matching_schema( $response_data, $schema, '' ); if ( ! is_wp_error( $matching_schema ) ) { if ( ! isset( $schema['type'] ) ) { $schema['type'] = $matching_schema['type']; } $response_data = rest_filter_response_by_context( $response_data, $matching_schema, $context ); } } if ( isset( $schema['oneOf'] ) ) { $matching_schema = rest_find_one_matching_schema( $response_data, $schema, '', true ); if ( ! is_wp_error( $matching_schema ) ) { if ( ! isset( $schema['type'] ) ) { $schema['type'] = $matching_schema['type']; } $response_data = rest_filter_response_by_context( $response_data, $matching_schema, $context ); } } if ( ! is_array( $response_data ) && ! is_object( $response_data ) ) { return $response_data; } if ( isset( $schema['type'] ) ) { $type = $schema['type']; } elseif ( isset( $schema['properties'] ) ) { $type = 'object'; // Back compat if a developer accidentally omitted the type. } else { return $response_data; } $is_array_type = 'array' === $type || ( is_array( $type ) && in_array( 'array', $type, true ) ); $is_object_type = 'object' === $type || ( is_array( $type ) && in_array( 'object', $type, true ) ); if ( $is_array_type && $is_object_type ) { if ( rest_is_array( $response_data ) ) { $is_object_type = false; } else { $is_array_type = false; } } $has_additional_properties = $is_object_type && isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] ); foreach ( $response_data as $key => $value ) { $check = array(); if ( $is_array_type ) { $check = isset( $schema['items'] ) ? $schema['items'] : array(); } elseif ( $is_object_type ) { if ( isset( $schema['properties'][ $key ] ) ) { $check = $schema['properties'][ $key ]; } else { $pattern_property_schema = rest_find_matching_pattern_property_schema( $key, $schema ); if ( null !== $pattern_property_schema ) { $check = $pattern_property_schema; } elseif ( $has_additional_properties ) { $check = $schema['additionalProperties']; } } } if ( ! isset( $check['context'] ) ) { continue; } if ( ! in_array( $context, $check['context'], true ) ) { if ( $is_array_type ) { // All array items share schema, so there's no need to check each one. $response_data = array(); break; } if ( is_object( $response_data ) ) { unset( $response_data->$key ); } else { unset( $response_data[ $key ] ); } } elseif ( is_array( $value ) || is_object( $value ) ) { $new_value = rest_filter_response_by_context( $value, $check, $context ); if ( is_object( $response_data ) ) { $response_data->$key = $new_value; } else { $response_data[ $key ] = $new_value; } } } return $response_data; } /** * Sets the "additionalProperties" to false by default for all object definitions in the schema. * * @since 5.5.0 * @since 5.6.0 Support the "patternProperties" keyword. * * @param array $schema The schema to modify. * @return array The modified schema. */ function rest_default_additional_properties_to_false( $schema ) { $type = (array) $schema['type']; if ( in_array( 'object', $type, true ) ) { if ( isset( $schema['properties'] ) ) { foreach ( $schema['properties'] as $key => $child_schema ) { $schema['properties'][ $key ] = rest_default_additional_properties_to_false( $child_schema ); } } if ( isset( $schema['patternProperties'] ) ) { foreach ( $schema['patternProperties'] as $key => $child_schema ) { $schema['patternProperties'][ $key ] = rest_default_additional_properties_to_false( $child_schema ); } } if ( ! isset( $schema['additionalProperties'] ) ) { $schema['additionalProperties'] = false; } } if ( in_array( 'array', $type, true ) ) { if ( isset( $schema['items'] ) ) { $schema['items'] = rest_default_additional_properties_to_false( $schema['items'] ); } } return $schema; } /** * Gets the REST API route for a post. * * @since 5.5.0 * * @param int|WP_Post $post Post ID or post object. * @return string The route path with a leading slash for the given post, * or an empty string if there is not a route. */ function rest_get_route_for_post( $post ) { $post = get_post( $post ); if ( ! $post instanceof WP_Post ) { return ''; } $post_type_route = rest_get_route_for_post_type_items( $post->post_type ); if ( ! $post_type_route ) { return ''; } $route = sprintf( '%s/%d', $post_type_route, $post->ID ); /** * Filters the REST API route for a post. * * @since 5.5.0 * * @param string $route The route path. * @param WP_Post $post The post object. */ return apply_filters( 'rest_route_for_post', $route, $post ); } /** * Gets the REST API route for a post type. * * @since 5.9.0 * * @param string $post_type The name of a registered post type. * @return string The route path with a leading slash for the given post type, * or an empty string if there is not a route. */ function rest_get_route_for_post_type_items( $post_type ) { $post_type = get_post_type_object( $post_type ); if ( ! $post_type ) { return ''; } if ( ! $post_type->show_in_rest ) { return ''; } $namespace = ! empty( $post_type->rest_namespace ) ? $post_type->rest_namespace : 'wp/v2'; $rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name; $route = sprintf( '/%s/%s', $namespace, $rest_base ); /** * Filters the REST API route for a post type. * * @since 5.9.0 * * @param string $route The route path. * @param WP_Post_Type $post_type The post type object. */ return apply_filters( 'rest_route_for_post_type_items', $route, $post_type ); } /** * Gets the REST API route for a term. * * @since 5.5.0 * * @param int|WP_Term $term Term ID or term object. * @return string The route path with a leading slash for the given term, * or an empty string if there is not a route. */ function rest_get_route_for_term( $term ) { $term = get_term( $term ); if ( ! $term instanceof WP_Term ) { return ''; } $taxonomy_route = rest_get_route_for_taxonomy_items( $term->taxonomy ); if ( ! $taxonomy_route ) { return ''; } $route = sprintf( '%s/%d', $taxonomy_route, $term->term_id ); /** * Filters the REST API route for a term. * * @since 5.5.0 * * @param string $route The route path. * @param WP_Term $term The term object. */ return apply_filters( 'rest_route_for_term', $route, $term ); } /** * Gets the REST API route for a taxonomy. * * @since 5.9.0 * * @param string $taxonomy Name of taxonomy. * @return string The route path with a leading slash for the given taxonomy. */ function rest_get_route_for_taxonomy_items( $taxonomy ) { $taxonomy = get_taxonomy( $taxonomy ); if ( ! $taxonomy ) { return ''; } if ( ! $taxonomy->show_in_rest ) { return ''; } $namespace = ! empty( $taxonomy->rest_namespace ) ? $taxonomy->rest_namespace : 'wp/v2'; $rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; $route = sprintf( '/%s/%s', $namespace, $rest_base ); /** * Filters the REST API route for a taxonomy. * * @since 5.9.0 * * @param string $route The route path. * @param WP_Taxonomy $taxonomy The taxonomy object. */ return apply_filters( 'rest_route_for_taxonomy_items', $route, $taxonomy ); } /** * Gets the REST route for the currently queried object. * * @since 5.5.0 * * @return string The REST route of the resource, or an empty string if no resource identified. */ function rest_get_queried_resource_route() { if ( is_singular() ) { $route = rest_get_route_for_post( get_queried_object() ); } elseif ( is_category() || is_tag() || is_tax() ) { $route = rest_get_route_for_term( get_queried_object() ); } elseif ( is_author() ) { $route = '/wp/v2/users/' . get_queried_object_id(); } else { $route = ''; } /** * Filters the REST route for the currently queried object. * * @since 5.5.0 * * @param string $link The route with a leading slash, or an empty string. */ return apply_filters( 'rest_queried_resource_route', $route ); } /** * Retrieves an array of endpoint arguments from the item schema and endpoint method. * * @since 5.6.0 * * @param array $schema The full JSON schema for the endpoint. * @param string $method Optional. HTTP method of the endpoint. The arguments for `CREATABLE` endpoints are * checked for required values and may fall-back to a given default, this is not done * on `EDITABLE` endpoints. Default WP_REST_Server::CREATABLE. * @return array The endpoint arguments. */ function rest_get_endpoint_args_for_schema( $schema, $method = WP_REST_Server::CREATABLE ) { $schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array(); $endpoint_args = array(); $valid_schema_properties = rest_get_allowed_schema_keywords(); $valid_schema_properties = array_diff( $valid_schema_properties, array( 'default', 'required' ) ); foreach ( $schema_properties as $field_id => $params ) { // Arguments specified as `readonly` are not allowed to be set. if ( ! empty( $params['readonly'] ) ) { continue; } $endpoint_args[ $field_id ] = array( 'validate_callback' => 'rest_validate_request_arg', 'sanitize_callback' => 'rest_sanitize_request_arg', ); if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) { $endpoint_args[ $field_id ]['default'] = $params['default']; } if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) { $endpoint_args[ $field_id ]['required'] = true; } foreach ( $valid_schema_properties as $schema_prop ) { if ( isset( $params[ $schema_prop ] ) ) { $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ]; } } // Merge in any options provided by the schema property. if ( isset( $params['arg_options'] ) ) { // Only use required / default from arg_options on CREATABLE endpoints. if ( WP_REST_Server::CREATABLE !== $method ) { $params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '', ) ); } $endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] ); } } return $endpoint_args; } /** * Converts an error to a response object. * * This iterates over all error codes and messages to change it into a flat * array. This enables simpler client behavior, as it is represented as a * list in JSON rather than an object/map. * * @since 5.7.0 * * @param WP_Error $error WP_Error instance. * * @return WP_REST_Response List of associative arrays with code and message keys. */ function rest_convert_error_to_response( $error ) { $status = array_reduce( $error->get_all_error_data(), static function ( $status, $error_data ) { return is_array( $error_data ) && isset( $error_data['status'] ) ? $error_data['status'] : $status; }, 500 ); $errors = array(); foreach ( (array) $error->errors as $code => $messages ) { $all_data = $error->get_all_error_data( $code ); $last_data = array_pop( $all_data ); foreach ( (array) $messages as $message ) { $formatted = array( 'code' => $code, 'message' => $message, 'data' => $last_data, ); if ( $all_data ) { $formatted['additional_data'] = $all_data; } $errors[] = $formatted; } } $data = $errors[0]; if ( count( $errors ) > 1 ) { // Remove the primary error. array_shift( $errors ); $data['additional_errors'] = $errors; } return new WP_REST_Response( $data, $status ); } /** * Checks whether a REST API endpoint request is currently being handled. * * This may be a standalone REST API request, or an internal request dispatched from within a regular page load. * * @since 6.5.0 * * @global WP_REST_Server $wp_rest_server REST server instance. * * @return bool True if a REST endpoint request is currently being handled, false otherwise. */ function wp_is_rest_endpoint() { /* @var WP_REST_Server $wp_rest_server */ global $wp_rest_server; // Check whether this is a standalone REST request. $is_rest_endpoint = wp_is_serving_rest_request(); if ( ! $is_rest_endpoint ) { // Otherwise, check whether an internal REST request is currently being handled. $is_rest_endpoint = isset( $wp_rest_server ) && $wp_rest_server->is_dispatching(); } /** * Filters whether a REST endpoint request is currently being handled. * * This may be a standalone REST API request, or an internal request dispatched from within a regular page load. * * @since 6.5.0 * * @param bool $is_request_endpoint Whether a REST endpoint request is currently being handled. */ return (bool) apply_filters( 'wp_is_rest_endpoint', $is_rest_endpoint ); } shortcodes.php 0000666 00000056762 15226762357 0007475 0 ustar 00 <?php /** * WordPress API for creating bbcode-like tags or what WordPress calls * "shortcodes". The tag and attribute parsing or regular expression code is * based on the Textpattern tag parser. * * A few examples are below: * * [shortcode /] * [shortcode foo="bar" baz="bing" /] * [shortcode foo="bar"]content[/shortcode] * * Shortcode tags support attributes and enclosed content, but does not entirely * support inline shortcodes in other shortcodes. You will have to call the * shortcode parser in your function to account for that. * * {@internal * Please be aware that the above note was made during the beta of WordPress 2.6 * and in the future may not be accurate. Please update the note when it is no * longer the case.}} * * To apply shortcode tags to content: * * $out = do_shortcode( $content ); * * @link https://developer.wordpress.org/plugins/shortcodes/ * * @package WordPress * @subpackage Shortcodes * @since 2.5.0 */ /** * Container for storing shortcode tags and their hook to call for the shortcode. * * @since 2.5.0 * * @name $shortcode_tags * @var array * @global array $shortcode_tags */ $shortcode_tags = array(); /** * Adds a new shortcode. * * Care should be taken through prefixing or other means to ensure that the * shortcode tag being added is unique and will not conflict with other, * already-added shortcode tags. In the event of a duplicated tag, the tag * loaded last will take precedence. * * @since 2.5.0 * * @global array $shortcode_tags * * @param string $tag Shortcode tag to be searched in post content. * @param callable $callback The callback function to run when the shortcode is found. * Every shortcode callback is passed three parameters by default, * including an array of attributes (`$atts`), the shortcode content * or null if not set (`$content`), and finally the shortcode tag * itself (`$shortcode_tag`), in that order. */ function add_shortcode( $tag, $callback ) { global $shortcode_tags; if ( '' === trim( $tag ) ) { _doing_it_wrong( __FUNCTION__, __( 'Invalid shortcode name: Empty name given.' ), '4.4.0' ); return; } if ( 0 !== preg_match( '@[<>&/\[\]\x00-\x20=]@', $tag ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: Shortcode name, 2: Space-separated list of reserved characters. */ __( 'Invalid shortcode name: %1$s. Do not use spaces or reserved characters: %2$s' ), $tag, '& / < > [ ] =' ), '4.4.0' ); return; } $shortcode_tags[ $tag ] = $callback; } /** * Removes hook for shortcode. * * @since 2.5.0 * * @global array $shortcode_tags * * @param string $tag Shortcode tag to remove hook for. */ function remove_shortcode( $tag ) { global $shortcode_tags; unset( $shortcode_tags[ $tag ] ); } /** * Clears all shortcodes. * * This function clears all of the shortcode tags by replacing the shortcodes global with * an empty array. This is actually an efficient method for removing all shortcodes. * * @since 2.5.0 * * @global array $shortcode_tags */ function remove_all_shortcodes() { global $shortcode_tags; $shortcode_tags = array(); } /** * Determines whether a registered shortcode exists named $tag. * * @since 3.6.0 * * @global array $shortcode_tags List of shortcode tags and their callback hooks. * * @param string $tag Shortcode tag to check. * @return bool Whether the given shortcode exists. */ function shortcode_exists( $tag ) { global $shortcode_tags; return array_key_exists( $tag, $shortcode_tags ); } /** * Determines whether the passed content contains the specified shortcode. * * @since 3.6.0 * * @global array $shortcode_tags * * @param string $content Content to search for shortcodes. * @param string $tag Shortcode tag to check. * @return bool Whether the passed content contains the given shortcode. */ function has_shortcode( $content, $tag ) { if ( ! str_contains( $content, '[' ) ) { return false; } if ( shortcode_exists( $tag ) ) { preg_match_all( '/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER ); if ( empty( $matches ) ) { return false; } foreach ( $matches as $shortcode ) { if ( $tag === $shortcode[2] ) { return true; } elseif ( ! empty( $shortcode[5] ) && has_shortcode( $shortcode[5], $tag ) ) { return true; } } } return false; } /** * Returns a list of registered shortcode names found in the given content. * * Example usage: * * get_shortcode_tags_in_content( '[audio src="file.mp3"][/audio] [foo] [gallery ids="1,2,3"]' ); * // array( 'audio', 'gallery' ) * * @since 6.3.2 * * @param string $content The content to check. * @return string[] An array of registered shortcode names found in the content. */ function get_shortcode_tags_in_content( $content ) { if ( ! str_contains( $content, '[' ) ) { return array(); } preg_match_all( '/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER ); if ( empty( $matches ) ) { return array(); } $tags = array(); foreach ( $matches as $shortcode ) { $tags[] = $shortcode[2]; if ( ! empty( $shortcode[5] ) ) { $deep_tags = get_shortcode_tags_in_content( $shortcode[5] ); if ( ! empty( $deep_tags ) ) { $tags = array_merge( $tags, $deep_tags ); } } } return $tags; } /** * Searches content for shortcodes and filter shortcodes through their hooks. * * This function is an alias for do_shortcode(). * * @since 5.4.0 * * @see do_shortcode() * * @param string $content Content to search for shortcodes. * @param bool $ignore_html When true, shortcodes inside HTML elements will be skipped. * Default false. * @return string Content with shortcodes filtered out. */ function apply_shortcodes( $content, $ignore_html = false ) { return do_shortcode( $content, $ignore_html ); } /** * Searches content for shortcodes and filter shortcodes through their hooks. * * If there are no shortcode tags defined, then the content will be returned * without any filtering. This might cause issues when plugins are disabled but * the shortcode will still show up in the post or content. * * @since 2.5.0 * * @global array $shortcode_tags List of shortcode tags and their callback hooks. * * @param string $content Content to search for shortcodes. * @param bool $ignore_html When true, shortcodes inside HTML elements will be skipped. * Default false. * @return string Content with shortcodes filtered out. */ function do_shortcode( $content, $ignore_html = false ) { global $shortcode_tags; if ( ! str_contains( $content, '[' ) ) { return $content; } if ( empty( $shortcode_tags ) || ! is_array( $shortcode_tags ) ) { return $content; } // Find all registered tag names in $content. preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches ); $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] ); if ( empty( $tagnames ) ) { return $content; } // Ensure this context is only added once if shortcodes are nested. $has_filter = has_filter( 'wp_get_attachment_image_context', '_filter_do_shortcode_context' ); $filter_added = false; if ( ! $has_filter ) { $filter_added = add_filter( 'wp_get_attachment_image_context', '_filter_do_shortcode_context' ); } $content = do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames ); $pattern = get_shortcode_regex( $tagnames ); $content = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $content ); // Always restore square braces so we don't break things like <!--[if IE ]>. $content = unescape_invalid_shortcodes( $content ); // Only remove the filter if it was added in this scope. if ( $filter_added ) { remove_filter( 'wp_get_attachment_image_context', '_filter_do_shortcode_context' ); } return $content; } /** * Filter the `wp_get_attachment_image_context` hook during shortcode rendering. * * When wp_get_attachment_image() is called during shortcode rendering, we need to make clear * that the context is a shortcode and not part of the theme's template rendering logic. * * @since 6.3.0 * @access private * * @return string The filtered context value for wp_get_attachment_images when doing shortcodes. */ function _filter_do_shortcode_context() { return 'do_shortcode'; } /** * Retrieves the shortcode regular expression for searching. * * The regular expression combines the shortcode tags in the regular expression * in a regex class. * * The regular expression contains 6 different sub matches to help with parsing. * * 1 - An extra [ to allow for escaping shortcodes with double [[]] * 2 - The shortcode name * 3 - The shortcode argument list * 4 - The self closing / * 5 - The content of a shortcode when it wraps some content. * 6 - An extra ] to allow for escaping shortcodes with double [[]] * * @since 2.5.0 * @since 4.4.0 Added the `$tagnames` parameter. * * @global array $shortcode_tags * * @param array $tagnames Optional. List of shortcodes to find. Defaults to all registered shortcodes. * @return string The shortcode search regular expression. */ function get_shortcode_regex( $tagnames = null ) { global $shortcode_tags; if ( empty( $tagnames ) ) { $tagnames = array_keys( $shortcode_tags ); } $tagregexp = implode( '|', array_map( 'preg_quote', $tagnames ) ); /* * WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcode_tag(). * Also, see shortcode_unautop() and shortcode.js. */ // phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation return '\\[' // Opening bracket. . '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]]. . "($tagregexp)" // 2: Shortcode name. . '(?![\\w-])' // Not followed by word character or hyphen. . '(' // 3: Unroll the loop: Inside the opening shortcode tag. . '[^\\]\\/]*' // Not a closing bracket or forward slash. . '(?:' . '\\/(?!\\])' // A forward slash not followed by a closing bracket. . '[^\\]\\/]*' // Not a closing bracket or forward slash. . ')*?' . ')' . '(?:' . '(\\/)' // 4: Self closing tag... . '\\]' // ...and closing bracket. . '|' . '\\]' // Closing bracket. . '(?:' . '(' // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags. . '[^\\[]*+' // Not an opening bracket. . '(?:' . '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag. . '[^\\[]*+' // Not an opening bracket. . ')*+' . ')' . '\\[\\/\\2\\]' // Closing shortcode tag. . ')?' . ')' . '(\\]?)'; // 6: Optional second closing bracket for escaping shortcodes: [[tag]]. // phpcs:enable } /** * Regular Expression callable for do_shortcode() for calling shortcode hook. * * @see get_shortcode_regex() for details of the match array contents. * * @since 2.5.0 * @access private * * @global array $shortcode_tags * * @param array $m { * Regular expression match array. * * @type string $0 Entire matched shortcode text. * @type string $1 Optional second opening bracket for escaping shortcodes. * @type string $2 Shortcode name. * @type string $3 Shortcode arguments list. * @type string $4 Optional self closing slash. * @type string $5 Content of a shortcode when it wraps some content. * @type string $6 Optional second closing bracket for escaping shortcodes. * } * @return string Shortcode output. */ function do_shortcode_tag( $m ) { global $shortcode_tags; // Allow [[foo]] syntax for escaping a tag. if ( '[' === $m[1] && ']' === $m[6] ) { return substr( $m[0], 1, -1 ); } $tag = $m[2]; $attr = shortcode_parse_atts( $m[3] ); if ( ! is_callable( $shortcode_tags[ $tag ] ) ) { _doing_it_wrong( __FUNCTION__, /* translators: %s: Shortcode tag. */ sprintf( __( 'Attempting to parse a shortcode without a valid callback: %s' ), $tag ), '4.3.0' ); return $m[0]; } /** * Filters whether to call a shortcode callback. * * Returning a non-false value from filter will short-circuit the * shortcode generation process, returning that value instead. * * @since 4.7.0 * @since 6.5.0 The `$attr` parameter is always an array. * * @param false|string $output Short-circuit return value. Either false or the value to replace the shortcode with. * @param string $tag Shortcode name. * @param array $attr Shortcode attributes array, can be empty if the original arguments string cannot be parsed. * @param array $m Regular expression match array. */ $return = apply_filters( 'pre_do_shortcode_tag', false, $tag, $attr, $m ); if ( false !== $return ) { return $return; } $content = isset( $m[5] ) ? $m[5] : null; $output = $m[1] . call_user_func( $shortcode_tags[ $tag ], $attr, $content, $tag ) . $m[6]; /** * Filters the output created by a shortcode callback. * * @since 4.7.0 * @since 6.5.0 The `$attr` parameter is always an array. * * @param string $output Shortcode output. * @param string $tag Shortcode name. * @param array $attr Shortcode attributes array, can be empty if the original arguments string cannot be parsed. * @param array $m Regular expression match array. */ return apply_filters( 'do_shortcode_tag', $output, $tag, $attr, $m ); } /** * Searches only inside HTML elements for shortcodes and process them. * * Any [ or ] characters remaining inside elements will be HTML encoded * to prevent interference with shortcodes that are outside the elements. * Assumes $content processed by KSES already. Users with unfiltered_html * capability may get unexpected output if angle braces are nested in tags. * * @since 4.2.3 * * @param string $content Content to search for shortcodes. * @param bool $ignore_html When true, all square braces inside elements will be encoded. * @param array $tagnames List of shortcodes to find. * @return string Content with shortcodes filtered out. */ function do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames ) { // Normalize entities in unfiltered HTML before adding placeholders. $trans = array( '[' => '[', ']' => ']', ); $content = strtr( $content, $trans ); $trans = array( '[' => '[', ']' => ']', ); $pattern = get_shortcode_regex( $tagnames ); $textarr = wp_html_split( $content ); foreach ( $textarr as &$element ) { if ( '' === $element || '<' !== $element[0] ) { continue; } $noopen = ! str_contains( $element, '[' ); $noclose = ! str_contains( $element, ']' ); if ( $noopen || $noclose ) { // This element does not contain shortcodes. if ( $noopen xor $noclose ) { // Need to encode stray '[' or ']' chars. $element = strtr( $element, $trans ); } continue; } if ( $ignore_html || str_starts_with( $element, '<!--' ) || str_starts_with( $element, '<![CDATA[' ) ) { // Encode all '[' and ']' chars. $element = strtr( $element, $trans ); continue; } $attributes = wp_kses_attr_parse( $element ); if ( false === $attributes ) { // Some plugins are doing things like [name] <[email]>. if ( 1 === preg_match( '%^<\s*\[\[?[^\[\]]+\]%', $element ) ) { $element = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $element ); } // Looks like we found some unexpected unfiltered HTML. Skipping it for confidence. $element = strtr( $element, $trans ); continue; } // Get element name. $front = array_shift( $attributes ); $back = array_pop( $attributes ); $matches = array(); preg_match( '%[a-zA-Z0-9]+%', $front, $matches ); $elname = $matches[0]; // Look for shortcodes in each attribute separately. foreach ( $attributes as &$attr ) { $open = strpos( $attr, '[' ); $close = strpos( $attr, ']' ); if ( false === $open || false === $close ) { continue; // Go to next attribute. Square braces will be escaped at end of loop. } $double = strpos( $attr, '"' ); $single = strpos( $attr, "'" ); if ( ( false === $single || $open < $single ) && ( false === $double || $open < $double ) ) { /* * $attr like '[shortcode]' or 'name = [shortcode]' implies unfiltered_html. * In this specific situation we assume KSES did not run because the input * was written by an administrator, so we should avoid changing the output * and we do not need to run KSES here. */ $attr = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $attr ); } else { /* * $attr like 'name = "[shortcode]"' or "name = '[shortcode]'". * We do not know if $content was unfiltered. Assume KSES ran before shortcodes. */ $count = 0; $new_attr = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $attr, -1, $count ); if ( $count > 0 ) { // Sanitize the shortcode output using KSES. $new_attr = wp_kses_one_attr( $new_attr, $elname ); if ( '' !== trim( $new_attr ) ) { // The shortcode is safe to use now. $attr = $new_attr; } } } } $element = $front . implode( '', $attributes ) . $back; // Now encode any remaining '[' or ']' chars. $element = strtr( $element, $trans ); } $content = implode( '', $textarr ); return $content; } /** * Removes placeholders added by do_shortcodes_in_html_tags(). * * @since 4.2.3 * * @param string $content Content to search for placeholders. * @return string Content with placeholders removed. */ function unescape_invalid_shortcodes( $content ) { // Clean up entire string, avoids re-parsing HTML. $trans = array( '[' => '[', ']' => ']', ); $content = strtr( $content, $trans ); return $content; } /** * Retrieves the shortcode attributes regex. * * @since 4.4.0 * * @return string The shortcode attribute regular expression. */ function get_shortcode_atts_regex() { return '/([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*\'([^\']*)\'(?:\s|$)|([\w-]+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|\'([^\']*)\'(?:\s|$)|(\S+)(?:\s|$)/'; } /** * Retrieves all attributes from the shortcodes tag. * * The attributes list has the attribute name as the key and the value of the * attribute as the value in the key/value pair. This allows for easier * retrieval of the attributes, since all attributes have to be known. * * @since 2.5.0 * @since 6.5.0 The function now always returns an array, * even if the original arguments string cannot be parsed or is empty. * * @param string $text Shortcode arguments list. * @return array Array of attribute values keyed by attribute name. * Returns empty array if there are no attributes * or if the original arguments string cannot be parsed. */ function shortcode_parse_atts( $text ) { $atts = array(); $pattern = get_shortcode_atts_regex(); $text = preg_replace( "/[\x{00a0}\x{200b}]+/u", ' ', $text ); if ( preg_match_all( $pattern, $text, $match, PREG_SET_ORDER ) ) { foreach ( $match as $m ) { if ( ! empty( $m[1] ) ) { $atts[ strtolower( $m[1] ) ] = stripcslashes( $m[2] ); } elseif ( ! empty( $m[3] ) ) { $atts[ strtolower( $m[3] ) ] = stripcslashes( $m[4] ); } elseif ( ! empty( $m[5] ) ) { $atts[ strtolower( $m[5] ) ] = stripcslashes( $m[6] ); } elseif ( isset( $m[7] ) && strlen( $m[7] ) ) { $atts[] = stripcslashes( $m[7] ); } elseif ( isset( $m[8] ) && strlen( $m[8] ) ) { $atts[] = stripcslashes( $m[8] ); } elseif ( isset( $m[9] ) ) { $atts[] = stripcslashes( $m[9] ); } } // Reject any unclosed HTML elements. foreach ( $atts as &$value ) { if ( str_contains( $value, '<' ) ) { if ( 1 !== preg_match( '/^[^<]*+(?:<[^>]*+>[^<]*+)*+$/', $value ) ) { $value = ''; } } } } return $atts; } /** * Combines user attributes with known attributes and fill in defaults when needed. * * The pairs should be considered to be all of the attributes which are * supported by the caller and given as a list. The returned attributes will * only contain the attributes in the $pairs list. * * If the $atts list has unsupported attributes, then they will be ignored and * removed from the final returned list. * * @since 2.5.0 * * @param array $pairs Entire list of supported attributes and their defaults. * @param array $atts User defined attributes in shortcode tag. * @param string $shortcode Optional. The name of the shortcode, provided for context to enable filtering * @return array Combined and filtered attribute list. */ function shortcode_atts( $pairs, $atts, $shortcode = '' ) { $atts = (array) $atts; $out = array(); foreach ( $pairs as $name => $default ) { if ( array_key_exists( $name, $atts ) ) { $out[ $name ] = $atts[ $name ]; } else { $out[ $name ] = $default; } } if ( $shortcode ) { /** * Filters shortcode attributes. * * If the third parameter of the shortcode_atts() function is present then this filter is available. * The third parameter, $shortcode, is the name of the shortcode. * * @since 3.6.0 * @since 4.4.0 Added the `$shortcode` parameter. * * @param array $out The output array of shortcode attributes. * @param array $pairs The supported attributes and their defaults. * @param array $atts The user defined shortcode attributes. * @param string $shortcode The shortcode name. */ $out = apply_filters( "shortcode_atts_{$shortcode}", $out, $pairs, $atts, $shortcode ); } return $out; } /** * Removes all shortcode tags from the given content. * * @since 2.5.0 * * @global array $shortcode_tags * * @param string $content Content to remove shortcode tags. * @return string Content without shortcode tags. */ function strip_shortcodes( $content ) { global $shortcode_tags; if ( ! str_contains( $content, '[' ) ) { return $content; } if ( empty( $shortcode_tags ) || ! is_array( $shortcode_tags ) ) { return $content; } // Find all registered tag names in $content. preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches ); $tags_to_remove = array_keys( $shortcode_tags ); /** * Filters the list of shortcode tags to remove from the content. * * @since 4.7.0 * * @param array $tags_to_remove Array of shortcode tags to remove. * @param string $content Content shortcodes are being removed from. */ $tags_to_remove = apply_filters( 'strip_shortcodes_tagnames', $tags_to_remove, $content ); $tagnames = array_intersect( $tags_to_remove, $matches[1] ); if ( empty( $tagnames ) ) { return $content; } $content = do_shortcodes_in_html_tags( $content, true, $tagnames ); $pattern = get_shortcode_regex( $tagnames ); $content = preg_replace_callback( "/$pattern/", 'strip_shortcode_tag', $content ); // Always restore square braces so we don't break things like <!--[if IE ]>. $content = unescape_invalid_shortcodes( $content ); return $content; } /** * Strips a shortcode tag based on RegEx matches against post content. * * @since 3.3.0 * * @param array $m RegEx matches against post content. * @return string|false The content stripped of the tag, otherwise false. */ function strip_shortcode_tag( $m ) { // Allow [[foo]] syntax for escaping a tag. if ( '[' === $m[1] && ']' === $m[6] ) { return substr( $m[0], 1, -1 ); } return $m[1] . $m[6]; } https-detection.php 0000666 00000013341 15226762357 0010420 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } utf8.php 0000666 00000016134 15226762357 0006173 0 ustar 00 <?php if ( extension_loaded( 'mbstring' ) ) : /** * Determines if a given byte string represents a valid UTF-8 encoding. * * Note that it’s unlikely for non-UTF-8 data to validate as UTF-8, but * it is still possible. Many texts are simultaneously valid UTF-8, * valid US-ASCII, and valid ISO-8859-1 (`latin1`). * * Example: * * true === wp_is_valid_utf8( '' ); * true === wp_is_valid_utf8( 'just a test' ); * true === wp_is_valid_utf8( "\xE2\x9C\x8F" ); // Pencil, U+270F. * true === wp_is_valid_utf8( "\u{270F}" ); // Pencil, U+270F. * true === wp_is_valid_utf8( '✏' ); // Pencil, U+270F. * * false === wp_is_valid_utf8( "just \xC0 test" ); // Invalid bytes. * false === wp_is_valid_utf8( "\xE2\x9C" ); // Invalid/incomplete sequences. * false === wp_is_valid_utf8( "\xC1\xBF" ); // Overlong sequences. * false === wp_is_valid_utf8( "\xED\xB0\x80" ); // Surrogate halves. * false === wp_is_valid_utf8( "B\xFCch" ); // ISO-8859-1 high-bytes. * // E.g. The “ü” in ISO-8859-1 is a single byte 0xFC, * // but in UTF-8 is the two-byte sequence 0xC3 0xBC. * * A “valid” string consists of “well-formed UTF-8 code unit sequence[s],” meaning * that the bytes conform to the UTF-8 encoding scheme, all characters use the minimal * byte sequence required by UTF-8, and that no sequence encodes a UTF-16 surrogate * code point or any character above the representable range. * * @see https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/#G32860 * * @since 6.9.0 * * @param string $bytes String which might contain text encoded as UTF-8. * @return bool Whether the provided bytes can decode as valid UTF-8. */ function wp_is_valid_utf8( string $bytes ): bool { return mb_check_encoding( $bytes, 'UTF-8' ); } else : /** * Fallback function for validating UTF-8. * * @ignore * @private * * @since 6.9.0 */ function wp_is_valid_utf8( string $string ): bool { return _wp_is_valid_utf8_fallback( $string ); } endif; if ( extension_loaded( 'mbstring' ) && // Maximal subpart substitution introduced by php/php-src@04e59c916f12b322ac55f22314e31bd0176d01cb. version_compare( PHP_VERSION, '8.1.6', '>=' ) ) : /** * Replaces ill-formed UTF-8 byte sequences with the Unicode Replacement Character. * * Knowing what to do in the presence of text encoding issues can be complicated. * This function replaces invalid spans of bytes to neutralize any corruption that * may be there and prevent it from causing further problems downstream. * * However, it’s not always ideal to replace those bytes. In some settings it may * be best to leave the invalid bytes in the string so that downstream code can handle * them in a specific way. Replacing the bytes too early, like escaping for HTML too * early, can introduce other forms of corruption and data loss. * * When in doubt, use this function to replace spans of invalid bytes. * * Replacement follows the “maximal subpart” algorithm for secure and interoperable * strings. This can lead to sequences of multiple replacement characters in a row. * * Example: * * // Valid strings come through unchanged. * 'test' === wp_scrub_utf8( 'test' ); * * // Invalid sequences of bytes are replaced. * $invalid = "the byte \xC0 is never allowed in a UTF-8 string."; * "the byte \u{FFFD} is never allowed in a UTF-8 string." === wp_scrub_utf8( $invalid, true ); * 'the byte � is never allowed in a UTF-8 string.' === wp_scrub_utf8( $invalid, true ); * * // Maximal subparts are replaced individually. * '.�.' === wp_scrub_utf8( ".\xC0." ); // C0 is never valid. * '.�.' === wp_scrub_utf8( ".\xE2\x8C." ); // Missing A3 at end. * '.��.' === wp_scrub_utf8( ".\xE2\x8C\xE2\x8C." ); // Maximal subparts replaced separately. * '.��.' === wp_scrub_utf8( ".\xC1\xBF." ); // Overlong sequence. * '.���.' === wp_scrub_utf8( ".\xED\xA0\x80." ); // Surrogate half. * * Note! The Unicode Replacement Character is itself a Unicode character (U+FFFD). * Once a span of invalid bytes has been replaced by one, it will not be possible * to know whether the replacement character was originally intended to be there * or if it is the result of scrubbing bytes. It is ideal to leave replacement for * display only, but some contexts (e.g. generating XML or passing data into a * large language model) require valid input strings. * * @since 6.9.0 * * @see https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-5/#G40630 * * @param string $text String which is assumed to be UTF-8 but may contain invalid sequences of bytes. * @return string Input text with invalid sequences of bytes replaced with the Unicode replacement character. */ function wp_scrub_utf8( $text ) { /* * While it looks like setting the substitute character could fail, * the internal PHP code will never fail when provided a valid * code point as a number. In this case, there’s no need to check * its return value to see if it succeeded. */ $prev_replacement_character = mb_substitute_character(); mb_substitute_character( 0xFFFD ); $scrubbed = mb_scrub( $text, 'UTF-8' ); mb_substitute_character( $prev_replacement_character ); return $scrubbed; } else : /** * Fallback function for scrubbing UTF-8. * * @ignore * @private * * @since 6.9.0 */ function wp_scrub_utf8( $text ) { return _wp_scrub_utf8_fallback( $text ); } endif; if ( _wp_can_use_pcre_u() ) : /** * Returns whether the given string contains Unicode noncharacters. * * XML recommends against using noncharacters and HTML forbids their * use in attribute names. Unicode recommends that they not be used * in open exchange of data. * * Noncharacters are code points within the following ranges: * - U+FDD0–U+FDEF * - U+FFFE–U+FFFF * - U+1FFFE, U+1FFFF, U+2FFFE, U+2FFFF, …, U+10FFFE, U+10FFFF * * @see https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-23/#G12612 * @see https://www.w3.org/TR/xml/#charsets * @see https://html.spec.whatwg.org/#attributes-2 * * @since 6.9.0 * * @param string $text Are there noncharacters in this string? * @return bool Whether noncharacters were found in the string. */ function wp_has_noncharacters( string $text ): bool { return 1 === preg_match( '/[\x{FDD0}-\x{FDEF}\x{FFFE}\x{FFFF}\x{1FFFE}\x{1FFFF}\x{2FFFE}\x{2FFFF}\x{3FFFE}\x{3FFFF}\x{4FFFE}\x{4FFFF}\x{5FFFE}\x{5FFFF}\x{6FFFE}\x{6FFFF}\x{7FFFE}\x{7FFFF}\x{8FFFE}\x{8FFFF}\x{9FFFE}\x{9FFFF}\x{AFFFE}\x{AFFFF}\x{BFFFE}\x{BFFFF}\x{CFFFE}\x{CFFFF}\x{DFFFE}\x{DFFFF}\x{EFFFE}\x{EFFFF}\x{FFFFE}\x{FFFFF}\x{10FFFE}\x{10FFFF}]/u', $text ); } else : /** * Fallback function for detecting noncharacters in a text. * * @ignore * @private * * @since 6.9.0 */ function wp_has_noncharacters( string $text ): bool { return _wp_has_noncharacters_fallback( $text ); } endif; embed-20260621195415.php 0000666 00000113777 15226762357 0007765 0 ustar 00 <?php /** * oEmbed API: Top-level oEmbed functionality * * @package WordPress * @subpackage oEmbed * @since 4.4.0 */ /** * Registers an embed handler. * * Should probably only be used for sites that do not support oEmbed. * * @since 2.9.0 * * @global WP_Embed $wp_embed WordPress Embed object. * * @param string $id An internal ID/name for the handler. Needs to be unique. * @param string $regex The regex that will be used to see if this handler should be used for a URL. * @param callable $callback The callback function that will be called if the regex is matched. * @param int $priority Optional. Used to specify the order in which the registered handlers will * be tested. Default 10. */ function wp_embed_register_handler( $id, $regex, $callback, $priority = 10 ) { global $wp_embed; $wp_embed->register_handler( $id, $regex, $callback, $priority ); } /** * Unregisters a previously-registered embed handler. * * @since 2.9.0 * * @global WP_Embed $wp_embed WordPress Embed object. * * @param string $id The handler ID that should be removed. * @param int $priority Optional. The priority of the handler to be removed. Default 10. */ function wp_embed_unregister_handler( $id, $priority = 10 ) { global $wp_embed; $wp_embed->unregister_handler( $id, $priority ); } /** * Creates default array of embed parameters. * * The width defaults to the content width as specified by the theme. If the * theme does not specify a content width, then 500px is used. * * The default height is 1.5 times the width, or 1000px, whichever is smaller. * * The {@see 'embed_defaults'} filter can be used to adjust either of these values. * * @since 2.9.0 * * @global int $content_width * * @param string $url Optional. The URL that should be embedded. Default empty. * @return int[] { * Indexed array of the embed width and height in pixels. * * @type int $0 The embed width. * @type int $1 The embed height. * } */ function wp_embed_defaults( $url = '' ) { if ( ! empty( $GLOBALS['content_width'] ) ) { $width = (int) $GLOBALS['content_width']; } if ( empty( $width ) ) { $width = 500; } $height = min( (int) ceil( $width * 1.5 ), 1000 ); /** * Filters the default array of embed dimensions. * * @since 2.9.0 * * @param int[] $size { * Indexed array of the embed width and height in pixels. * * @type int $0 The embed width. * @type int $1 The embed height. * } * @param string $url The URL that should be embedded. */ return apply_filters( 'embed_defaults', compact( 'width', 'height' ), $url ); } /** * Attempts to fetch the embed HTML for a provided URL using oEmbed. * * @since 2.9.0 * * @see WP_oEmbed * * @param string $url The URL that should be embedded. * @param array|string $args { * Optional. Additional arguments for retrieving embed HTML. Default empty. * * @type int|string $width Optional. The `maxwidth` value passed to the provider URL. * @type int|string $height Optional. The `maxheight` value passed to the provider URL. * @type bool $discover Optional. Determines whether to attempt to discover link tags * at the given URL for an oEmbed provider when the provider URL * is not found in the built-in providers list. Default true. * } * @return string|false The embed HTML on success, false on failure. */ function wp_oembed_get( $url, $args = '' ) { $oembed = _wp_oembed_get_object(); return $oembed->get_html( $url, $args ); } /** * Returns the initialized WP_oEmbed object. * * @since 2.9.0 * @access private * * @return WP_oEmbed object. */ function _wp_oembed_get_object() { static $wp_oembed = null; if ( is_null( $wp_oembed ) ) { $wp_oembed = new WP_oEmbed(); } return $wp_oembed; } /** * Adds a URL format and oEmbed provider URL pair. * * @since 2.9.0 * * @see WP_oEmbed * * @param string $format The format of URL that this provider can handle. You can use asterisks * as wildcards. * @param string $provider The URL to the oEmbed provider. * @param bool $regex Optional. Whether the `$format` parameter is in a RegEx format. Default false. */ function wp_oembed_add_provider( $format, $provider, $regex = false ) { if ( did_action( 'plugins_loaded' ) ) { $oembed = _wp_oembed_get_object(); $oembed->providers[ $format ] = array( $provider, $regex ); } else { WP_oEmbed::_add_provider_early( $format, $provider, $regex ); } } /** * Removes an oEmbed provider. * * @since 3.5.0 * * @see WP_oEmbed * * @param string $format The URL format for the oEmbed provider to remove. * @return bool Was the provider removed successfully? */ function wp_oembed_remove_provider( $format ) { if ( did_action( 'plugins_loaded' ) ) { $oembed = _wp_oembed_get_object(); if ( isset( $oembed->providers[ $format ] ) ) { unset( $oembed->providers[ $format ] ); return true; } } else { WP_oEmbed::_remove_provider_early( $format ); } return false; } /** * Determines if default embed handlers should be loaded. * * Checks to make sure that the embeds library hasn't already been loaded. If * it hasn't, then it will load the embeds library. * * @since 2.9.0 * * @see wp_embed_register_handler() */ function wp_maybe_load_embeds() { /** * Filters whether to load the default embed handlers. * * Returning a falsey value will prevent loading the default embed handlers. * * @since 2.9.0 * * @param bool $maybe_load_embeds Whether to load the embeds library. Default true. */ if ( ! apply_filters( 'load_default_embeds', true ) ) { return; } wp_embed_register_handler( 'youtube_embed_url', '#https?://(www\.)?youtube\.com/(?:v|embed)/([^/]+)#i', 'wp_embed_handler_youtube' ); /** * Filters the audio embed handler callback. * * @since 3.6.0 * * @param callable $handler Audio embed handler callback function. */ wp_embed_register_handler( 'audio', '#^https?://.+?\.(' . implode( '|', wp_get_audio_extensions() ) . ')$#i', apply_filters( 'wp_audio_embed_handler', 'wp_embed_handler_audio' ), 9999 ); /** * Filters the video embed handler callback. * * @since 3.6.0 * * @param callable $handler Video embed handler callback function. */ wp_embed_register_handler( 'video', '#^https?://.+?\.(' . implode( '|', wp_get_video_extensions() ) . ')$#i', apply_filters( 'wp_video_embed_handler', 'wp_embed_handler_video' ), 9999 ); } /** * YouTube iframe embed handler callback. * * Catches YouTube iframe embed URLs that are not parsable by oEmbed but can be translated into a URL that is. * * @since 4.0.0 * * @global WP_Embed $wp_embed WordPress Embed object. * * @param array $matches The RegEx matches from the provided regex when calling * wp_embed_register_handler(). * @param array $attr Embed attributes. * @param string $url The original URL that was matched by the regex. * @param array $rawattr The original unmodified attributes. * @return string The embed HTML. */ function wp_embed_handler_youtube( $matches, $attr, $url, $rawattr ) { global $wp_embed; $embed = $wp_embed->autoembed( sprintf( 'https://youtube.com/watch?v=%s', urlencode( $matches[2] ) ) ); /** * Filters the YouTube embed output. * * @since 4.0.0 * * @see wp_embed_handler_youtube() * * @param string $embed YouTube embed output. * @param array $attr An array of embed attributes. * @param string $url The original URL that was matched by the regex. * @param array $rawattr The original unmodified attributes. */ return apply_filters( 'wp_embed_handler_youtube', $embed, $attr, $url, $rawattr ); } /** * Audio embed handler callback. * * @since 3.6.0 * * @param array $matches The RegEx matches from the provided regex when calling wp_embed_register_handler(). * @param array $attr Embed attributes. * @param string $url The original URL that was matched by the regex. * @param array $rawattr The original unmodified attributes. * @return string The embed HTML. */ function wp_embed_handler_audio( $matches, $attr, $url, $rawattr ) { $audio = sprintf( '[audio src="%s" /]', esc_url( $url ) ); /** * Filters the audio embed output. * * @since 3.6.0 * * @param string $audio Audio embed output. * @param array $attr An array of embed attributes. * @param string $url The original URL that was matched by the regex. * @param array $rawattr The original unmodified attributes. */ return apply_filters( 'wp_embed_handler_audio', $audio, $attr, $url, $rawattr ); } /** * Video embed handler callback. * * @since 3.6.0 * * @param array $matches The RegEx matches from the provided regex when calling wp_embed_register_handler(). * @param array $attr Embed attributes. * @param string $url The original URL that was matched by the regex. * @param array $rawattr The original unmodified attributes. * @return string The embed HTML. */ function wp_embed_handler_video( $matches, $attr, $url, $rawattr ) { $dimensions = ''; if ( ! empty( $rawattr['width'] ) && ! empty( $rawattr['height'] ) ) { $dimensions .= sprintf( 'width="%d" ', (int) $rawattr['width'] ); $dimensions .= sprintf( 'height="%d" ', (int) $rawattr['height'] ); } $video = sprintf( '[video %s src="%s" /]', $dimensions, esc_url( $url ) ); /** * Filters the video embed output. * * @since 3.6.0 * * @param string $video Video embed output. * @param array $attr An array of embed attributes. * @param string $url The original URL that was matched by the regex. * @param array $rawattr The original unmodified attributes. */ return apply_filters( 'wp_embed_handler_video', $video, $attr, $url, $rawattr ); } /** * Registers the oEmbed REST API route. * * @since 4.4.0 */ function wp_oembed_register_route() { $controller = new WP_oEmbed_Controller(); $controller->register_routes(); } /** * Adds oEmbed discovery links in the head element of the website. * * @since 4.4.0 * @since 6.8.0 Output was adjusted to only embed if the post supports it. * @since 6.9.0 Now runs first at `wp_head` priority 4, with a fallback to priority 10. This helps ensure the discovery links appear within the first 150KB. */ function wp_oembed_add_discovery_links() { if ( doing_action( 'wp_head' ) ) { // For back-compat, short-circuit if a plugin has removed the action at the original priority. if ( ! has_action( 'wp_head', 'wp_oembed_add_discovery_links', 10 ) ) { return; } // Prevent running again at the original priority. remove_action( 'wp_head', 'wp_oembed_add_discovery_links' ); } $output = ''; if ( is_singular() && is_post_embeddable() ) { $output .= '<link rel="alternate" title="' . _x( 'oEmbed (JSON)', 'oEmbed resource link name' ) . '" type="application/json+oembed" href="' . esc_url( get_oembed_endpoint_url( get_permalink() ) ) . '" />' . "\n"; if ( class_exists( 'SimpleXMLElement' ) ) { $output .= '<link rel="alternate" title="' . _x( 'oEmbed (XML)', 'oEmbed resource link name' ) . '" type="text/xml+oembed" href="' . esc_url( get_oembed_endpoint_url( get_permalink(), 'xml' ) ) . '" />' . "\n"; } } /** * Filters the oEmbed discovery links HTML. * * @since 4.4.0 * * @param string $output HTML of the discovery links. */ echo apply_filters( 'oembed_discovery_links', $output ); } /** * Adds the necessary JavaScript to communicate with the embedded iframes. * * This function is no longer used directly. For back-compat it exists exclusively as a way to indicate that the oEmbed * host JS _should_ be added. In `default-filters.php` there remains this code: * * add_action( 'wp_head', 'wp_oembed_add_host_js' ) * * Historically a site has been able to disable adding the oEmbed host script by doing: * * remove_action( 'wp_head', 'wp_oembed_add_host_js' ) * * In order to ensure that such code still works as expected, this function remains. There is now a `has_action()` check * in `wp_maybe_enqueue_oembed_host_js()` to see if `wp_oembed_add_host_js()` has not been unhooked from running at the * `wp_head` action. * * @since 4.4.0 * @deprecated 5.9.0 Use {@see wp_maybe_enqueue_oembed_host_js()} instead. */ function wp_oembed_add_host_js() {} /** * Enqueue the wp-embed script if the provided oEmbed HTML contains a post embed. * * In order to only enqueue the wp-embed script on pages that actually contain post embeds, this function checks if the * provided HTML contains post embed markup and if so enqueues the script so that it will get printed in the footer. * * @since 5.9.0 * * @param string $html Embed markup. * @return string Embed markup (without modifications). */ function wp_maybe_enqueue_oembed_host_js( $html ) { if ( has_action( 'wp_head', 'wp_oembed_add_host_js' ) && preg_match( '/<blockquote\s[^>]*?wp-embedded-content/', $html ) ) { wp_enqueue_script( 'wp-embed' ); } return $html; } /** * Retrieves the URL to embed a specific post in an iframe. * * @since 4.4.0 * * @param int|WP_Post $post Optional. Post ID or object. Defaults to the current post. * @return string|false The post embed URL on success, false if the post doesn't exist. */ function get_post_embed_url( $post = null ) { $post = get_post( $post ); if ( ! $post ) { return false; } $embed_url = trailingslashit( get_permalink( $post ) ) . user_trailingslashit( 'embed' ); $path_conflict = get_page_by_path( str_replace( home_url(), '', $embed_url ), OBJECT, get_post_types( array( 'public' => true ) ) ); if ( ! get_option( 'permalink_structure' ) || $path_conflict ) { $embed_url = add_query_arg( array( 'embed' => 'true' ), get_permalink( $post ) ); } /** * Filters the URL to embed a specific post. * * @since 4.4.0 * * @param string $embed_url The post embed URL. * @param WP_Post $post The corresponding post object. */ return sanitize_url( apply_filters( 'post_embed_url', $embed_url, $post ) ); } /** * Retrieves the oEmbed endpoint URL for a given permalink. * * Pass an empty string as the first argument to get the endpoint base URL. * * @since 4.4.0 * * @param string $permalink Optional. The permalink used for the `url` query arg. Default empty. * @param string $format Optional. The requested response format. Default 'json'. * @return string The oEmbed endpoint URL. */ function get_oembed_endpoint_url( $permalink = '', $format = 'json' ) { $url = rest_url( 'oembed/1.0/embed' ); if ( '' !== $permalink ) { $url = add_query_arg( array( 'url' => urlencode( $permalink ), 'format' => ( 'json' !== $format ) ? $format : false, ), $url ); } /** * Filters the oEmbed endpoint URL. * * @since 4.4.0 * * @param string $url The URL to the oEmbed endpoint. * @param string $permalink The permalink used for the `url` query arg. * @param string $format The requested response format. */ return apply_filters( 'oembed_endpoint_url', $url, $permalink, $format ); } /** * Retrieves the embed code for a specific post. * * @since 4.4.0 * * @param int $width The width for the response. * @param int $height The height for the response. * @param int|WP_Post $post Optional. Post ID or object. Default is global `$post`. * @return string|false Embed code on success, false if post doesn't exist. */ function get_post_embed_html( $width, $height, $post = null ) { $post = get_post( $post ); if ( ! $post ) { return false; } $embed_url = get_post_embed_url( $post ); $secret = wp_generate_password( 10, false ); $embed_url .= "#?secret={$secret}"; $output = sprintf( '<blockquote class="wp-embedded-content" data-secret="%1$s"><a href="%2$s">%3$s</a></blockquote>', esc_attr( $secret ), esc_url( get_permalink( $post ) ), get_the_title( $post ) ); $output .= sprintf( '<iframe sandbox="allow-scripts" security="restricted" src="%1$s" width="%2$d" height="%3$d" title="%4$s" data-secret="%5$s" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" class="wp-embedded-content"></iframe>', esc_url( $embed_url ), absint( $width ), absint( $height ), esc_attr( sprintf( /* translators: 1: Post title, 2: Site title. */ __( '“%1$s” — %2$s' ), get_the_title( $post ), get_bloginfo( 'name' ) ) ), esc_attr( $secret ) ); /* * Note that the script must be placed after the <blockquote> and <iframe> due to a regexp parsing issue in * `wp_filter_oembed_result()`. Because of the regex pattern starts with `|(<blockquote>.*?</blockquote>)?.*|` * wherein the <blockquote> is marked as being optional, if it is not at the beginning of the string then the group * will fail to match and everything will be matched by `.*` and not included in the group. This regex issue goes * back to WordPress 4.4, so in order to not break older installs this script must come at the end. */ $js_path = '/js/wp-embed' . wp_scripts_get_suffix() . '.js'; $output .= wp_get_inline_script_tag( trim( file_get_contents( ABSPATH . WPINC . $js_path ) ) . "\n//# sourceURL=" . esc_url_raw( includes_url( $js_path ) ) ); /** * Filters the embed HTML output for a given post. * * @since 4.4.0 * * @param string $output The default iframe tag to display embedded content. * @param WP_Post $post Current post object. * @param int $width Width of the response. * @param int $height Height of the response. */ return apply_filters( 'embed_html', $output, $post, $width, $height ); } /** * Retrieves the oEmbed response data for a given post. * * @since 4.4.0 * @since 6.8.0 Output was adjusted to only embed if the post type supports it. * * @param WP_Post|int $post Post ID or post object. * @param int $width The requested width. * @return array|false Response data on success, false if post doesn't exist, * is not publicly viewable or post type is not embeddable. */ function get_oembed_response_data( $post, $width ) { $post = get_post( $post ); $width = absint( $width ); if ( ! $post ) { return false; } if ( ! is_post_publicly_viewable( $post ) ) { return false; } if ( ! is_post_embeddable( $post ) ) { return false; } /** * Filters the allowed minimum and maximum widths for the oEmbed response. * * @since 4.4.0 * * @param array $min_max_width { * Minimum and maximum widths for the oEmbed response. * * @type int $min Minimum width. Default 200. * @type int $max Maximum width. Default 600. * } */ $min_max_width = apply_filters( 'oembed_min_max_width', array( 'min' => 200, 'max' => 600, ) ); $width = min( max( $min_max_width['min'], $width ), $min_max_width['max'] ); $height = max( (int) ceil( $width / 16 * 9 ), 200 ); $data = array( 'version' => '1.0', 'provider_name' => get_bloginfo( 'name' ), 'provider_url' => get_home_url(), 'author_name' => get_bloginfo( 'name' ), 'author_url' => get_home_url(), 'title' => get_the_title( $post ), 'type' => 'link', ); $author = get_userdata( $post->post_author ); if ( $author ) { $data['author_name'] = $author->display_name; $data['author_url'] = get_author_posts_url( $author->ID ); } /** * Filters the oEmbed response data. * * @since 4.4.0 * * @param array $data The response data. * @param WP_Post $post The post object. * @param int $width The requested width. * @param int $height The calculated height. */ return apply_filters( 'oembed_response_data', $data, $post, $width, $height ); } /** * Retrieves the oEmbed response data for a given URL. * * @since 5.0.0 * * @param string $url The URL that should be inspected for discovery `<link>` tags. * @param array $args oEmbed remote get arguments. * @return object|false oEmbed response data if the URL does belong to the current site. False otherwise. */ function get_oembed_response_data_for_url( $url, $args ) { $switched_blog = false; if ( is_multisite() ) { $url_parts = wp_parse_args( wp_parse_url( $url ), array( 'host' => '', 'port' => null, 'path' => '/', ) ); $qv = array( 'domain' => $url_parts['host'] . ( $url_parts['port'] ? ':' . $url_parts['port'] : '' ), 'path' => '/', 'update_site_meta_cache' => false, ); // In case of subdirectory configs, set the path. if ( ! is_subdomain_install() ) { $path = explode( '/', ltrim( $url_parts['path'], '/' ) ); $path = reset( $path ); if ( $path ) { $qv['path'] = get_network()->path . $path . '/'; } } $sites = get_sites( $qv ); $site = reset( $sites ); // Do not allow embeds for deleted/archived/spam sites. if ( ! empty( $site->deleted ) || ! empty( $site->spam ) || ! empty( $site->archived ) ) { return false; } if ( $site && get_current_blog_id() !== (int) $site->blog_id ) { switch_to_blog( $site->blog_id ); $switched_blog = true; } } $post_id = url_to_postid( $url ); /** This filter is documented in wp-includes/class-wp-oembed-controller.php */ $post_id = apply_filters( 'oembed_request_post_id', $post_id, $url ); if ( ! $post_id ) { if ( $switched_blog ) { restore_current_blog(); } return false; } $width = isset( $args['width'] ) ? $args['width'] : 0; $data = get_oembed_response_data( $post_id, $width ); if ( $switched_blog ) { restore_current_blog(); } return $data ? (object) $data : false; } /** * Filters the oEmbed response data to return an iframe embed code. * * @since 4.4.0 * * @param array $data The response data. * @param WP_Post $post The post object. * @param int $width The requested width. * @param int $height The calculated height. * @return array The modified response data. */ function get_oembed_response_data_rich( $data, $post, $width, $height ) { $data['width'] = absint( $width ); $data['height'] = absint( $height ); $data['type'] = 'rich'; $data['html'] = get_post_embed_html( $width, $height, $post ); // Add post thumbnail to response if available. $thumbnail_id = false; if ( has_post_thumbnail( $post->ID ) ) { $thumbnail_id = get_post_thumbnail_id( $post->ID ); } if ( 'attachment' === get_post_type( $post ) ) { if ( wp_attachment_is_image( $post ) ) { $thumbnail_id = $post->ID; } elseif ( wp_attachment_is( 'video', $post ) ) { $thumbnail_id = get_post_thumbnail_id( $post ); $data['type'] = 'video'; } } if ( $thumbnail_id ) { list( $thumbnail_url, $thumbnail_width, $thumbnail_height ) = wp_get_attachment_image_src( $thumbnail_id, array( $width, 0 ) ); $data['thumbnail_url'] = $thumbnail_url; $data['thumbnail_width'] = $thumbnail_width; $data['thumbnail_height'] = $thumbnail_height; } return $data; } /** * Ensures that the specified format is either 'json' or 'xml'. * * @since 4.4.0 * * @param string $format The oEmbed response format. Accepts 'json' or 'xml'. * @return string The format, either 'xml' or 'json'. Default 'json'. */ function wp_oembed_ensure_format( $format ) { if ( ! in_array( $format, array( 'json', 'xml' ), true ) ) { return 'json'; } return $format; } /** * Hooks into the REST API output to print XML instead of JSON. * * This is only done for the oEmbed API endpoint, * which supports both formats. * * @access private * @since 4.4.0 * * @param bool $served Whether the request has already been served. * @param WP_HTTP_Response $result Result to send to the client. Usually a `WP_REST_Response`. * @param WP_REST_Request $request Request used to generate the response. * @param WP_REST_Server $server Server instance. * @return bool True if the request was served, false otherwise. */ function _oembed_rest_pre_serve_request( $served, $result, $request, $server ) { $params = $request->get_params(); if ( '/oembed/1.0/embed' !== $request->get_route() || 'GET' !== $request->get_method() ) { return $served; } if ( ! isset( $params['format'] ) || 'xml' !== $params['format'] ) { return $served; } // Embed links inside the request. $data = $server->response_to_data( $result, false ); if ( ! class_exists( 'SimpleXMLElement' ) ) { status_header( 501 ); die( get_status_header_desc( 501 ) ); } $result = _oembed_create_xml( $data ); // Bail if there's no XML. if ( ! $result ) { status_header( 501 ); die( get_status_header_desc( 501 ) ); } if ( ! headers_sent() ) { $server->send_header( 'Content-Type', 'text/xml; charset=' . get_option( 'blog_charset' ) ); } echo $result; return true; } /** * Creates an XML string from a given array. * * @since 4.4.0 * @access private * * @param array $data The original oEmbed response data. * @param SimpleXMLElement $node Optional. XML node to append the result to recursively. * @return string|false XML string on success, false on error. */ function _oembed_create_xml( $data, $node = null ) { if ( ! is_array( $data ) || empty( $data ) ) { return false; } if ( null === $node ) { $node = new SimpleXMLElement( '<oembed></oembed>' ); } foreach ( $data as $key => $value ) { if ( is_numeric( $key ) ) { $key = 'oembed'; } if ( is_array( $value ) ) { $item = $node->addChild( $key ); _oembed_create_xml( $value, $item ); } else { $node->addChild( $key, esc_html( $value ) ); } } return $node->asXML(); } /** * Filters the given oEmbed HTML to make sure iframes have a title attribute. * * @since 5.2.0 * * @param string|false $result The oEmbed HTML result. * @param object $data A data object result from an oEmbed provider. * @param string $url The URL of the content to be embedded. * @return string|false The filtered oEmbed result. */ function wp_filter_oembed_iframe_title_attribute( $result, $data, $url ) { if ( false === $result || ! in_array( $data->type, array( 'rich', 'video' ), true ) ) { return $result; } $title = ! empty( $data->title ) ? $data->title : ''; $pattern = '`<iframe([^>]*)>`i'; if ( preg_match( $pattern, $result, $matches ) ) { $attrs = wp_kses_hair( $matches[1], wp_allowed_protocols() ); foreach ( $attrs as $attr => $item ) { $lower_attr = strtolower( $attr ); if ( $lower_attr === $attr ) { continue; } if ( ! isset( $attrs[ $lower_attr ] ) ) { $attrs[ $lower_attr ] = $item; unset( $attrs[ $attr ] ); } } } if ( ! empty( $attrs['title']['value'] ) ) { $title = $attrs['title']['value']; } /** * Filters the title attribute of the given oEmbed HTML iframe. * * @since 5.2.0 * * @param string $title The title attribute. * @param string $result The oEmbed HTML result. * @param object $data A data object result from an oEmbed provider. * @param string $url The URL of the content to be embedded. */ $title = apply_filters( 'oembed_iframe_title_attribute', $title, $result, $data, $url ); if ( '' === $title ) { return $result; } if ( isset( $attrs['title'] ) ) { unset( $attrs['title'] ); $attr_string = implode( ' ', wp_list_pluck( $attrs, 'whole' ) ); $result = str_replace( $matches[0], '<iframe ' . trim( $attr_string ) . '>', $result ); } return str_ireplace( '<iframe ', sprintf( '<iframe title="%s" ', esc_attr( $title ) ), $result ); } /** * Filters the given oEmbed HTML. * * If the `$url` isn't on the trusted providers list, * we need to filter the HTML heavily for security. * * Only filters 'rich' and 'video' response types. * * @since 4.4.0 * * @param string|false $result The oEmbed HTML result. * @param object $data A data object result from an oEmbed provider. * @param string $url The URL of the content to be embedded. * @return string|false The filtered and sanitized oEmbed result. */ function wp_filter_oembed_result( $result, $data, $url ) { if ( false === $result || ! in_array( $data->type, array( 'rich', 'video' ), true ) ) { return $result; } $wp_oembed = _wp_oembed_get_object(); // Don't modify the HTML for trusted providers. if ( false !== $wp_oembed->get_provider( $url, array( 'discover' => false ) ) ) { return $result; } $allowed_html = array( 'a' => array( 'href' => true, ), 'blockquote' => array(), 'iframe' => array( 'src' => true, 'width' => true, 'height' => true, 'frameborder' => true, 'marginwidth' => true, 'marginheight' => true, 'scrolling' => true, 'title' => true, ), ); $html = wp_kses( $result, $allowed_html ); preg_match( '|(<blockquote>.*?</blockquote>)?.*(<iframe.*?></iframe>)|ms', $html, $content ); // We require at least the iframe to exist. if ( empty( $content[2] ) ) { return false; } $html = $content[1] . $content[2]; preg_match( '/ src=([\'"])(.*?)\1/', $html, $results ); if ( ! empty( $results ) ) { $secret = wp_generate_password( 10, false ); $url = esc_url( "{$results[2]}#?secret=$secret" ); $q = $results[1]; $html = str_replace( $results[0], ' src=' . $q . $url . $q . ' data-secret=' . $q . $secret . $q, $html ); $html = str_replace( '<blockquote', "<blockquote data-secret=\"$secret\"", $html ); } $allowed_html['blockquote']['data-secret'] = true; $allowed_html['iframe']['data-secret'] = true; $html = wp_kses( $html, $allowed_html ); if ( ! empty( $content[1] ) ) { // We have a blockquote to fall back on. Hide the iframe by default. $html = str_replace( '<iframe', '<iframe style="position: absolute; visibility: hidden;"', $html ); $html = str_replace( '<blockquote', '<blockquote class="wp-embedded-content"', $html ); } $html = str_ireplace( '<iframe', '<iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"', $html ); return $html; } /** * Filters the string in the 'more' link displayed after a trimmed excerpt. * * Replaces '[...]' (appended to automatically generated excerpts) with an * ellipsis and a "Continue reading" link in the embed template. * * @since 4.4.0 * * @param string $more_string Default 'more' string. * @return string 'Continue reading' link prepended with an ellipsis. */ function wp_embed_excerpt_more( $more_string ) { if ( ! is_embed() ) { return $more_string; } $link = sprintf( '<a href="%1$s" class="wp-embed-more" target="_top">%2$s</a>', esc_url( get_permalink() ), /* translators: %s: Post title. */ sprintf( __( 'Continue reading %s' ), '<span class="screen-reader-text">' . get_the_title() . '</span>' ) ); return ' … ' . $link; } /** * Displays the post excerpt for the embed template. * * Intended to be used in 'The Loop'. * * @since 4.4.0 */ function the_excerpt_embed() { $output = get_the_excerpt(); /** * Filters the post excerpt for the embed template. * * @since 4.4.0 * * @param string $output The current post excerpt. */ echo apply_filters( 'the_excerpt_embed', $output ); } /** * Filters the post excerpt for the embed template. * * Shows players for video and audio attachments. * * @since 4.4.0 * * @param string $content The current post excerpt. * @return string The modified post excerpt. */ function wp_embed_excerpt_attachment( $content ) { if ( is_attachment() ) { return prepend_attachment( '' ); } return $content; } /** * Enqueues embed iframe default CSS and JS. * * Enqueue PNG fallback CSS for embed iframe for legacy versions of IE. * * Allows plugins to queue scripts for the embed iframe end using wp_enqueue_script(). * Runs first in oembed_head(). * * @since 4.4.0 */ function enqueue_embed_scripts() { /** * Fires when scripts and styles are enqueued for the embed iframe. * * @since 4.4.0 */ do_action( 'enqueue_embed_scripts' ); } /** * Enqueues the CSS in the embed iframe header. * * @since 6.4.0 */ function wp_enqueue_embed_styles() { // Back-compat for plugins that disable functionality by unhooking this action. if ( ! has_action( 'embed_head', 'print_embed_styles' ) ) { return; } remove_action( 'embed_head', 'print_embed_styles' ); $suffix = wp_scripts_get_suffix(); $handle = 'wp-embed-template'; wp_register_style( $handle, false ); wp_add_inline_style( $handle, file_get_contents( ABSPATH . WPINC . "/css/wp-embed-template$suffix.css" ) ); wp_enqueue_style( $handle ); } /** * Prints the JavaScript in the embed iframe header. * * @since 4.4.0 */ function print_embed_scripts() { $js_path = '/js/wp-embed-template' . wp_scripts_get_suffix() . '.js'; wp_print_inline_script_tag( trim( file_get_contents( ABSPATH . WPINC . $js_path ) ) . "\n//# sourceURL=" . esc_url_raw( includes_url( $js_path ) ) ); } /** * Prepare the oembed HTML to be displayed in an RSS feed. * * @since 4.4.0 * @access private * * @param string $content The content to filter. * @return string The filtered content. */ function _oembed_filter_feed_content( $content ) { $p = new WP_HTML_Tag_Processor( $content ); while ( $p->next_tag( array( 'tag_name' => 'iframe' ) ) ) { if ( $p->has_class( 'wp-embedded-content' ) ) { $p->remove_attribute( 'style' ); } } return $p->get_updated_html(); } /** * Prints the necessary markup for the embed comments button. * * @since 4.4.0 */ function print_embed_comments_button() { if ( is_404() || ! ( get_comments_number() || comments_open() ) ) { return; } ?> <div class="wp-embed-comments"> <a href="<?php comments_link(); ?>" target="_top"> <span class="dashicons dashicons-admin-comments"></span> <?php printf( /* translators: %s: Number of comments. */ _n( '%s <span class="screen-reader-text">Comment</span>', '%s <span class="screen-reader-text">Comments</span>', get_comments_number() ), number_format_i18n( get_comments_number() ) ); ?> </a> </div> <?php } /** * Prints the necessary markup for the embed sharing button. * * @since 4.4.0 */ function print_embed_sharing_button() { if ( is_404() ) { return; } ?> <div class="wp-embed-share"> <button type="button" class="wp-embed-share-dialog-open" aria-label="<?php esc_attr_e( 'Open sharing dialog' ); ?>"> <span class="dashicons dashicons-share"></span> </button> </div> <?php } /** * Prints the necessary markup for the embed sharing dialog. * * @since 4.4.0 */ function print_embed_sharing_dialog() { if ( is_404() ) { return; } $unique_suffix = get_the_ID() . '-' . wp_rand(); $share_tab_wordpress_id = 'wp-embed-share-tab-wordpress-' . $unique_suffix; $share_tab_html_id = 'wp-embed-share-tab-html-' . $unique_suffix; $description_wordpress_id = 'wp-embed-share-description-wordpress-' . $unique_suffix; $description_html_id = 'wp-embed-share-description-html-' . $unique_suffix; ?> <div class="wp-embed-share-dialog hidden" role="dialog" aria-label="<?php esc_attr_e( 'Sharing options' ); ?>"> <div class="wp-embed-share-dialog-content"> <div class="wp-embed-share-dialog-text"> <ul class="wp-embed-share-tabs" role="tablist"> <li class="wp-embed-share-tab-button wp-embed-share-tab-button-wordpress" role="presentation"> <button type="button" role="tab" aria-controls="<?php echo $share_tab_wordpress_id; ?>" aria-selected="true" tabindex="0"><?php esc_html_e( 'WordPress Embed' ); ?></button> </li> <li class="wp-embed-share-tab-button wp-embed-share-tab-button-html" role="presentation"> <button type="button" role="tab" aria-controls="<?php echo $share_tab_html_id; ?>" aria-selected="false" tabindex="-1"><?php esc_html_e( 'HTML Embed' ); ?></button> </li> </ul> <div id="<?php echo $share_tab_wordpress_id; ?>" class="wp-embed-share-tab" role="tabpanel" aria-hidden="false"> <input type="text" value="<?php the_permalink(); ?>" class="wp-embed-share-input" aria-label="<?php esc_attr_e( 'URL' ); ?>" aria-describedby="<?php echo $description_wordpress_id; ?>" tabindex="0" readonly/> <p class="wp-embed-share-description" id="<?php echo $description_wordpress_id; ?>"> <?php _e( 'Copy and paste this URL into your WordPress site to embed' ); ?> </p> </div> <div id="<?php echo $share_tab_html_id; ?>" class="wp-embed-share-tab" role="tabpanel" aria-hidden="true"> <textarea class="wp-embed-share-input" aria-label="<?php esc_attr_e( 'HTML' ); ?>" aria-describedby="<?php echo $description_html_id; ?>" tabindex="0" readonly><?php echo esc_textarea( get_post_embed_html( 600, 400 ) ); ?></textarea> <p class="wp-embed-share-description" id="<?php echo $description_html_id; ?>"> <?php _e( 'Copy and paste this code into your site to embed' ); ?> </p> </div> </div> <button type="button" class="wp-embed-share-dialog-close" aria-label="<?php esc_attr_e( 'Close sharing dialog' ); ?>"> <span class="dashicons dashicons-no"></span> </button> </div> </div> <?php } /** * Prints the necessary markup for the site title in an embed template. * * @since 4.5.0 */ function the_embed_site_title() { $site_title = sprintf( '<a href="%s" target="_top"><img src="%s" srcset="%s 2x" width="32" height="32" alt="" class="wp-embed-site-icon" /><span>%s</span></a>', esc_url( home_url() ), esc_url( get_site_icon_url( 32, includes_url( 'images/w-logo-blue.png' ) ) ), esc_url( get_site_icon_url( 64, includes_url( 'images/w-logo-blue.png' ) ) ), esc_html( get_bloginfo( 'name' ) ) ); $site_title = '<div class="wp-embed-site-title">' . $site_title . '</div>'; /** * Filters the site title HTML in the embed footer. * * @since 4.4.0 * * @param string $site_title The site title HTML. */ echo apply_filters( 'embed_site_title_html', $site_title ); } /** * Filters the oEmbed result before any HTTP requests are made. * * If the URL belongs to the current site, the result is fetched directly instead of * going through the oEmbed discovery process. * * @since 4.5.3 * * @param null|string $result The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. Default null. * @param string $url The URL that should be inspected for discovery `<link>` tags. * @param array $args oEmbed remote get arguments. * @return null|string The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. * Null if the URL does not belong to the current site. */ function wp_filter_pre_oembed_result( $result, $url, $args ) { $data = get_oembed_response_data_for_url( $url, $args ); if ( $data ) { return _wp_oembed_get_object()->data2html( $data, $url ); } return $result; } https-detection-20260606132425.php 0000666 00000013341 15226762357 0012004 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260605080419.php 0000666 00000011205 15226762357 0012020 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260605034026-20260605055117.php 0000666 00000013341 15226762357 0013366 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260605104334.php 0000666 00000013341 15226762357 0012001 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-customize-themes-section.php 0000666 00000015453 15226762357 0014006 0 ustar 00 <?php /** * Customize API: WP_Customize_Themes_Section class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Themes Section class. * * A UI container for theme controls, which are displayed within sections. * * @since 4.2.0 * * @see WP_Customize_Section */ class WP_Customize_Themes_Section extends WP_Customize_Section { /** * Section type. * * @since 4.2.0 * @var string */ public $type = 'themes'; /** * Theme section action. * * Defines the type of themes to load (installed, wporg, etc.). * * @since 4.9.0 * @var string */ public $action = ''; /** * Theme section filter type. * * Determines whether filters are applied to loaded (local) themes or by initiating a new remote query (remote). * When filtering is local, the initial themes query is not paginated by default. * * @since 4.9.0 * @var string */ public $filter_type = 'local'; /** * Gets section parameters for JS. * * @since 4.9.0 * @return array Exported parameters. */ public function json() { $exported = parent::json(); $exported['action'] = $this->action; $exported['filter_type'] = $this->filter_type; return $exported; } /** * Renders a themes section as a JS template. * * The template is only rendered by PHP once, so all actions are prepared at once on the server side. * * @since 4.9.0 */ protected function render_template() { ?> <li id="accordion-section-{{ data.id }}" class="theme-section"> <button type="button" class="customize-themes-section-title themes-section-{{ data.id }}">{{ data.title }}</button> <?php if ( current_user_can( 'install_themes' ) || is_multisite() ) : // @todo Upload support. ?> <?php endif; ?> <div class="customize-themes-section themes-section-{{ data.id }} control-section-content themes-php"> <div class="theme-overlay" tabindex="0" role="dialog" aria-label="<?php esc_attr_e( 'Theme Details' ); ?>"></div> <div class="theme-browser rendered"> <div class="customize-preview-header themes-filter-bar"> <?php $this->filter_bar_content_template(); ?> </div> <?php $this->filter_drawer_content_template(); ?> <div class="error unexpected-error" style="display: none; "> <p> <?php printf( /* translators: %s: Support forums URL. */ __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.' ), __( 'https://wordpress.org/support/forums/' ) ); ?> </p> </div> <ul class="themes"> </ul> <p class="no-themes"><?php _e( 'No themes found. Try a different search.' ); ?></p> <p class="no-themes-local"> <?php printf( /* translators: %s: "Search WordPress.org themes" button text. */ __( 'No themes found. Try a different search, or %s.' ), sprintf( '<button type="button" class="button-link search-dotorg-themes">%s</button>', __( 'Search WordPress.org themes' ) ) ); ?> </p> <p class="spinner"></p> </div> </div> </li> <?php } /** * Renders the filter bar portion of a themes section as a JS template. * * The template is only rendered by PHP once, so all actions are prepared at once on the server side. * The filter bar container is rendered by {@see render_template()}. * * @since 4.9.0 */ protected function filter_bar_content_template() { ?> <button type="button" class="button button-primary customize-section-back customize-themes-mobile-back"><?php _e( 'Go to theme sources' ); ?></button> <# if ( 'wporg' === data.action ) { #> <div class="themes-filter-container"> <label for="wp-filter-search-input-{{ data.id }}"><?php _e( 'Search themes' ); ?></label> <div class="search-form-input"> <input type="search" id="wp-filter-search-input-{{ data.id }}" aria-describedby="{{ data.id }}-live-search-desc" class="wp-filter-search"> <div class="search-icon" aria-hidden="true"></div> <span id="{{ data.id }}-live-search-desc" class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ _e( 'The search results will be updated as you type.' ); ?> </span> </div> </div> <# } else { #> <div class="themes-filter-container"> <label for="{{ data.id }}-themes-filter"><?php _e( 'Search themes' ); ?></label> <div class="search-form-input"> <input type="search" id="{{ data.id }}-themes-filter" aria-describedby="{{ data.id }}-live-search-desc" class="wp-filter-search wp-filter-search-themes" /> <div class="search-icon" aria-hidden="true"></div> <span id="{{ data.id }}-live-search-desc" class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ _e( 'The search results will be updated as you type.' ); ?> </span> </div> </div> <# } #> <div class="filter-themes-wrapper"> <# if ( 'wporg' === data.action ) { #> <button type="button" class="button feature-filter-toggle"> <span class="filter-count-0"><?php _e( 'Filter themes' ); ?></span><span class="filter-count-filters"> <?php /* translators: %s: Number of filters selected. */ printf( __( 'Filter themes (%s)' ), '<span class="theme-filter-count">0</span>' ); ?> </span> </button> <# } #> <div class="filter-themes-count"> <span class="themes-displayed"> <?php /* translators: %s: Number of themes displayed. */ printf( __( '%s themes' ), '<span class="theme-count">0</span>' ); ?> </span> </div> </div> <?php } /** * Renders the filter drawer portion of a themes section as a JS template. * * The filter bar container is rendered by {@see render_template()}. * * @since 4.9.0 */ protected function filter_drawer_content_template() { /* * @todo Use the .org API instead of the local core feature list. * The .org API is currently outdated and will be reconciled when the .org themes directory is next redesigned. */ $feature_list = get_theme_feature_list( false ); ?> <# if ( 'wporg' === data.action ) { #> <div class="filter-drawer filter-details"> <?php foreach ( $feature_list as $feature_name => $features ) : ?> <fieldset class="filter-group"> <legend><?php echo esc_html( $feature_name ); ?></legend> <div class="filter-group-feature"> <?php foreach ( $features as $feature => $feature_name ) : ?> <input type="checkbox" id="filter-id-<?php echo esc_attr( $feature ); ?>" value="<?php echo esc_attr( $feature ); ?>" /> <label for="filter-id-<?php echo esc_attr( $feature ); ?>"><?php echo esc_html( $feature_name ); ?></label> <?php endforeach; ?> </div> </fieldset> <?php endforeach; ?> </div> <# } #> <?php } } https-detection-20260605034026-20260605074318.php 0000666 00000013341 15226762357 0013372 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-customize-filter-setting.php 0000666 00000001114 15226762357 0014004 0 ustar 00 <?php /** * Customize API: WP_Customize_Filter_Setting class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * A setting that is used to filter a value, but will not save the results. * * Results should be properly handled using another setting or callback. * * @since 3.4.0 * * @see WP_Customize_Setting */ class WP_Customize_Filter_Setting extends WP_Customize_Setting { /** * Saves the value of the setting, using the related API. * * @since 3.4.0 * * @param mixed $value The value to update. */ public function update( $value ) {} } https-migration-20260605130239.php 0000666 00000011205 15226762357 0012014 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } abilities-api.php 0000666 00000057461 15226762357 0010031 0 ustar 00 <?php /** * Abilities API: core functions for registering and managing abilities. * * The Abilities API provides a unified, extensible framework for registering * and executing discrete capabilities within WordPress. An "ability" is a * self-contained unit of functionality with defined inputs, outputs, permissions, * and execution logic. * * ## Overview * * The Abilities API enables developers to: * * - Register custom abilities with standardized interfaces. * - Define permission checks and execution callbacks. * - Organize abilities into logical categories. * - Validate inputs and outputs using JSON Schema. * - Expose abilities through the REST API. * * ## Working with Abilities * * Abilities must be registered on the `wp_abilities_api_init` action hook. * Attempting to register an ability outside of this hook will fail and * trigger a `_doing_it_wrong()` notice. * Example: * * function my_plugin_register_abilities(): void { * wp_register_ability( * 'my-plugin/export-users', * array( * 'label' => __( 'Export Users', 'my-plugin' ), * 'description' => __( 'Exports user data to CSV format.', 'my-plugin' ), * 'category' => 'data-export', * 'execute_callback' => 'my_plugin_export_users', * 'permission_callback' => function(): bool { * return current_user_can( 'export' ); * }, * 'input_schema' => array( * 'type' => 'string', * 'enum' => array( 'subscriber', 'contributor', 'author', 'editor', 'administrator' ), * 'description' => __( 'Limits the export to users with this role.', 'my-plugin' ), * 'required' => false, * ), * 'output_schema' => array( * 'type' => 'string', * 'description' => __( 'User data in CSV format.', 'my-plugin' ), * 'required' => true, * ), * 'meta' => array( * 'show_in_rest' => true, * ), * ) * ); * } * add_action( 'wp_abilities_api_init', 'my_plugin_register_abilities' ); * * Once registered, abilities can be checked, retrieved, and managed: * * // Checks if an ability is registered, and prints its label. * if ( wp_has_ability( 'my-plugin/export-users' ) ) { * $ability = wp_get_ability( 'my-plugin/export-users' ); * * echo $ability->get_label(); * } * * // Gets all registered abilities. * $all_abilities = wp_get_abilities(); * * // Unregisters when no longer needed. * wp_unregister_ability( 'my-plugin/export-users' ); * * ## Best Practices * * - Always register abilities on the `wp_abilities_api_init` hook. * - Use namespaced ability names to prevent conflicts. * - Implement robust permission checks in permission callbacks. * - Provide an `input_schema` to ensure data integrity and document expected inputs. * - Define an `output_schema` to describe return values and validate responses. * - Return `WP_Error` objects for failures rather than throwing exceptions. * - Use internationalization functions for all user-facing strings. * * @package WordPress * @subpackage Abilities_API * @since 6.9.0 */ declare( strict_types = 1 ); /** * Registers a new ability using the Abilities API. It requires three steps: * * 1. Hook into the `wp_abilities_api_init` action. * 2. Call `wp_register_ability()` with a namespaced name and configuration. * 3. Provide execute and permission callbacks. * * Example: * * function my_plugin_register_abilities(): void { * wp_register_ability( * 'my-plugin/analyze-text', * array( * 'label' => __( 'Analyze Text', 'my-plugin' ), * 'description' => __( 'Performs sentiment analysis on provided text.', 'my-plugin' ), * 'category' => 'text-processing', * 'input_schema' => array( * 'type' => 'string', * 'description' => __( 'The text to be analyzed.', 'my-plugin' ), * 'minLength' => 10, * 'required' => true, * ), * 'output_schema' => array( * 'type' => 'string', * 'enum' => array( 'positive', 'negative', 'neutral' ), * 'description' => __( 'The sentiment result: positive, negative, or neutral.', 'my-plugin' ), * 'required' => true, * ), * 'execute_callback' => 'my_plugin_analyze_text', * 'permission_callback' => 'my_plugin_can_analyze_text', * 'meta' => array( * 'annotations' => array( * 'readonly' => true, * ), * 'show_in_rest' => true, * ), * ) * ); * } * add_action( 'wp_abilities_api_init', 'my_plugin_register_abilities' ); * * ### Naming Conventions * * Ability names must follow these rules: * * - Include a namespace prefix (e.g., `my-plugin/my-ability`). * - Use only lowercase alphanumeric characters, dashes, and forward slashes. * - Use descriptive, action-oriented names (e.g., `process-payment`, `generate-report`). * * ### Categories * * Abilities must be organized into categories. Ability categories provide better * discoverability and must be registered before the abilities that reference them: * * function my_plugin_register_categories(): void { * wp_register_ability_category( * 'text-processing', * array( * 'label' => __( 'Text Processing', 'my-plugin' ), * 'description' => __( 'Abilities for analyzing and transforming text.', 'my-plugin' ), * ) * ); * } * add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' ); * * ### Input and Output Schemas * * Schemas define the expected structure, type, and constraints for ability inputs * and outputs using JSON Schema syntax. They serve two critical purposes: automatic * validation of data passed to and returned from abilities, and self-documenting * API contracts for developers. * * WordPress implements a validator based on a subset of the JSON Schema Version 4 * specification (https://json-schema.org/specification-links.html#draft-4). * For details on supported JSON Schema properties and syntax, see the * related WordPress REST API Schema documentation: * https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/#json-schema-basics * * Defining schemas is mandatory when there is a value to pass or return. * They ensure data integrity, improve developer experience, and enable * better documentation: * * 'input_schema' => array( * 'type' => 'string', * 'description' => __( 'The text to be analyzed.', 'my-plugin' ), * 'minLength' => 10, * 'required' => true, * ), * 'output_schema' => array( * 'type' => 'string', * 'enum' => array( 'positive', 'negative', 'neutral' ), * 'description' => __( 'The sentiment result: positive, negative, or neutral.', 'my-plugin' ), * 'required' => true, * ), * * ### Callbacks * * #### Execute Callback * * The execute callback performs the ability's core functionality. It receives * optional input data and returns either a result or `WP_Error` on failure. * * function my_plugin_analyze_text( string $input ): string|WP_Error { * $score = My_Plugin::perform_sentiment_analysis( $input ); * if ( is_wp_error( $score ) ) { * return $score; * } * return My_Plugin::interpret_sentiment_score( $score ); * } * * #### Permission Callback * * The permission callback determines whether the ability can be executed. * It receives the same input as the execute callback and must return a * boolean or `WP_Error`. Common use cases include checking user capabilities, * validating API keys, or verifying system state: * * function my_plugin_can_analyze_text( string $input ): bool|WP_Error { * return current_user_can( 'edit_posts' ); * } * * ### REST API Integration * * Abilities can be exposed through the REST API by setting `show_in_rest` * to `true` in the meta configuration: * * 'meta' => array( * 'show_in_rest' => true, * ), * * This allows abilities to be invoked via HTTP requests to the WordPress REST API. * * @since 6.9.0 * * @see WP_Abilities_Registry::register() * @see wp_register_ability_category() * @see wp_unregister_ability() * * @param string $name The name of the ability. Must be a namespaced string containing * a prefix, e.g., `my-plugin/my-ability`. Can only contain lowercase * alphanumeric characters, dashes, and forward slashes. * @param array<string, mixed> $args { * An associative array of arguments for configuring the ability. * * @type string $label Required. The human-readable label for the ability. * @type string $description Required. A detailed description of what the ability does * and when it should be used. * @type string $category Required. The ability category slug this ability belongs to. * The ability category must be registered via `wp_register_ability_category()` * before registering the ability. * @type callable $execute_callback Required. A callback function to execute when the ability is invoked. * Receives optional mixed input data and must return either a result * value (any type) or a `WP_Error` object on failure. * @type callable $permission_callback Required. A callback function to check permissions before execution. * Receives optional mixed input data (same as `execute_callback`) and * must return `true`/`false` for simple checks, or `WP_Error` for * detailed error responses. * @type array<string, mixed> $input_schema Optional. JSON Schema definition for validating the ability's input. * Must be a valid JSON Schema object defining the structure and * constraints for input data. Used for automatic validation and * API documentation. * @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output. * Describes the structure of successful return values from * `execute_callback`. Used for documentation and validation. * @type array<string, mixed> $meta { * Optional. Additional metadata for the ability. * * @type array<string, bool|null> $annotations { * Optional. Semantic annotations describing the ability's behavioral characteristics. * These annotations are hints for tooling and documentation. * * @type bool|null $readonly Optional. If true, the ability does not modify its environment. * @type bool|null $destructive Optional. If true, the ability may perform destructive updates to its environment. * If false, the ability performs only additive updates. * @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments * will have no additional effect on its environment. * } * @type bool $show_in_rest Optional. Whether to expose this ability in the REST API. * When true, the ability can be invoked via HTTP requests. * Default false. * } * @type string $ability_class Optional. Fully-qualified custom class name to instantiate * instead of the default `WP_Ability` class. The custom class * must extend `WP_Ability`. Useful for advanced customization * of ability behavior. * } * @return WP_Ability|null The registered ability instance on success, `null` on failure. */ function wp_register_ability( string $name, array $args ): ?WP_Ability { if ( ! doing_action( 'wp_abilities_api_init' ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: wp_abilities_api_init, 2: string value of the ability name. */ __( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ), '<code>wp_abilities_api_init</code>', '<code>' . esc_html( $name ) . '</code>' ), '6.9.0' ); return null; } $registry = WP_Abilities_Registry::get_instance(); if ( null === $registry ) { return null; } return $registry->register( $name, $args ); } /** * Unregisters an ability from the Abilities API. * * Removes a previously registered ability from the global registry. Use this to * disable abilities provided by other plugins or when an ability is no longer needed. * * Can be called at any time after the ability has been registered. * * Example: * * if ( wp_has_ability( 'other-plugin/some-ability' ) ) { * wp_unregister_ability( 'other-plugin/some-ability' ); * } * * @since 6.9.0 * * @see WP_Abilities_Registry::unregister() * @see wp_register_ability() * * @param string $name The name of the ability to unregister, including namespace prefix * (e.g., 'my-plugin/my-ability'). * @return WP_Ability|null The unregistered ability instance on success, `null` on failure. */ function wp_unregister_ability( string $name ): ?WP_Ability { $registry = WP_Abilities_Registry::get_instance(); if ( null === $registry ) { return null; } return $registry->unregister( $name ); } /** * Checks if an ability is registered. * * Use this for conditional logic and feature detection before attempting to * retrieve or use an ability. * * Example: * * // Displays different UI based on available abilities. * if ( wp_has_ability( 'premium-plugin/advanced-export' ) ) { * echo 'Export with Premium Features'; * } else { * echo 'Basic Export'; * } * * @since 6.9.0 * * @see WP_Abilities_Registry::is_registered() * @see wp_get_ability() * * @param string $name The name of the ability to check, including namespace prefix * (e.g., 'my-plugin/my-ability'). * @return bool `true` if the ability is registered, `false` otherwise. */ function wp_has_ability( string $name ): bool { $registry = WP_Abilities_Registry::get_instance(); if ( null === $registry ) { return false; } return $registry->is_registered( $name ); } /** * Retrieves a registered ability. * * Returns the ability instance for inspection or use. The instance provides access * to the ability's configuration, metadata, and execution methods. * * Example: * * // Prints information about a registered ability. * $ability = wp_get_ability( 'my-plugin/export-data' ); * if ( $ability ) { * echo $ability->get_label() . ': ' . $ability->get_description(); * } * * @since 6.9.0 * * @see WP_Abilities_Registry::get_registered() * @see wp_has_ability() * * @param string $name The name of the ability, including namespace prefix * (e.g., 'my-plugin/my-ability'). * @return WP_Ability|null The registered ability instance, or `null` if not registered. */ function wp_get_ability( string $name ): ?WP_Ability { $registry = WP_Abilities_Registry::get_instance(); if ( null === $registry ) { return null; } return $registry->get_registered( $name ); } /** * Retrieves all registered abilities. * * Returns an array of all ability instances currently registered in the system. * Use this for discovery, debugging, or building administrative interfaces. * * Example: * * // Prints information about all available abilities. * $abilities = wp_get_abilities(); * foreach ( $abilities as $ability ) { * echo $ability->get_label() . ': ' . $ability->get_description() . "\n"; * } * * @since 6.9.0 * * @see WP_Abilities_Registry::get_all_registered() * * @return WP_Ability[] An array of registered WP_Ability instances. Returns an empty * array if no abilities are registered or if the registry is unavailable. */ function wp_get_abilities(): array { $registry = WP_Abilities_Registry::get_instance(); if ( null === $registry ) { return array(); } return $registry->get_all_registered(); } /** * Registers a new ability category. * * Ability categories provide a way to organize and group related abilities for better * discoverability and management. Ability categories must be registered before abilities * that reference them. * * Ability categories must be registered on the `wp_abilities_api_categories_init` action hook. * * Example: * * function my_plugin_register_categories() { * wp_register_ability_category( * 'content-management', * array( * 'label' => __( 'Content Management', 'my-plugin' ), * 'description' => __( 'Abilities for managing and organizing content.', 'my-plugin' ), * ) * ); * } * add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' ); * * @since 6.9.0 * * @see WP_Ability_Categories_Registry::register() * @see wp_register_ability() * @see wp_unregister_ability_category() * * @param string $slug The unique slug for the ability category. Must contain only lowercase * alphanumeric characters and dashes (e.g., 'data-export'). * @param array<string, mixed> $args { * An associative array of arguments for the ability category. * * @type string $label Required. The human-readable label for the ability category. * @type string $description Required. A description of what abilities in this category do. * @type array<string, mixed> $meta Optional. Additional metadata for the ability category. * } * @return WP_Ability_Category|null The registered ability category instance on success, `null` on failure. */ function wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category { if ( ! doing_action( 'wp_abilities_api_categories_init' ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: wp_abilities_api_categories_init, 2: ability category slug. */ __( 'Ability categories must be registered on the %1$s action. The ability category %2$s was not registered.' ), '<code>wp_abilities_api_categories_init</code>', '<code>' . esc_html( $slug ) . '</code>' ), '6.9.0' ); return null; } $registry = WP_Ability_Categories_Registry::get_instance(); if ( null === $registry ) { return null; } return $registry->register( $slug, $args ); } /** * Unregisters an ability category. * * Removes a previously registered ability category from the global registry. Use this to * disable ability categories that are no longer needed. * * Can be called at any time after the ability category has been registered. * * Example: * * if ( wp_has_ability_category( 'deprecated-category' ) ) { * wp_unregister_ability_category( 'deprecated-category' ); * } * * @since 6.9.0 * * @see WP_Ability_Categories_Registry::unregister() * @see wp_register_ability_category() * * @param string $slug The slug of the ability category to unregister. * @return WP_Ability_Category|null The unregistered ability category instance on success, `null` on failure. */ function wp_unregister_ability_category( string $slug ): ?WP_Ability_Category { $registry = WP_Ability_Categories_Registry::get_instance(); if ( null === $registry ) { return null; } return $registry->unregister( $slug ); } /** * Checks if an ability category is registered. * * Use this for conditional logic and feature detection before attempting to * retrieve or use an ability category. * * Example: * * // Displays different UI based on available ability categories. * if ( wp_has_ability_category( 'premium-features' ) ) { * echo 'Premium Features Available'; * } else { * echo 'Standard Features'; * } * * @since 6.9.0 * * @see WP_Ability_Categories_Registry::is_registered() * @see wp_get_ability_category() * * @param string $slug The slug of the ability category to check. * @return bool `true` if the ability category is registered, `false` otherwise. */ function wp_has_ability_category( string $slug ): bool { $registry = WP_Ability_Categories_Registry::get_instance(); if ( null === $registry ) { return false; } return $registry->is_registered( $slug ); } /** * Retrieves a registered ability category. * * Returns the ability category instance for inspection or use. The instance provides access * to the ability category's configuration and metadata. * * Example: * * // Prints information about a registered ability category. * $ability_category = wp_get_ability_category( 'content-management' ); * if ( $ability_category ) { * echo $ability_category->get_label() . ': ' . $ability_category->get_description(); * } * * @since 6.9.0 * * @see WP_Ability_Categories_Registry::get_registered() * @see wp_has_ability_category() * @see wp_get_ability_categories() * * @param string $slug The slug of the ability category. * @return WP_Ability_Category|null The ability category instance, or `null` if not registered. */ function wp_get_ability_category( string $slug ): ?WP_Ability_Category { $registry = WP_Ability_Categories_Registry::get_instance(); if ( null === $registry ) { return null; } return $registry->get_registered( $slug ); } /** * Retrieves all registered ability categories. * * Returns an array of all ability category instances currently registered in the system. * Use this for discovery, debugging, or building administrative interfaces. * * Example: * * // Prints information about all available ability categories. * $ability_categories = wp_get_ability_categories(); * foreach ( $ability_categories as $ability_category ) { * echo $ability_category->get_label() . ': ' . $ability_category->get_description() . "\n"; * } * * @since 6.9.0 * * @see WP_Ability_Categories_Registry::get_all_registered() * @see wp_get_ability_category() * * @return WP_Ability_Category[] An array of registered ability category instances. Returns an empty array * if no ability categories are registered or if the registry is unavailable. */ function wp_get_ability_categories(): array { $registry = WP_Ability_Categories_Registry::get_instance(); if ( null === $registry ) { return array(); } return $registry->get_all_registered(); } https-migration-20260615192534.php 0000666 00000011205 15226762357 0012023 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260606074155.php 0000666 00000011205 15226762357 0012021 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260622024947-20260626111908.php 0000666 00000011205 15226762357 0013414 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-script-modules.php 0000666 00000100225 15226762357 0012001 0 ustar 00 <?php /** * Script Modules API: WP_Script_Modules class. * * Native support for ES Modules and Import Maps. * * @package WordPress * @subpackage Script Modules */ /** * Core class used to register script modules. * * @since 6.5.0 */ class WP_Script_Modules { /** * Holds the registered script modules, keyed by script module identifier. * * @since 6.5.0 * @var array<string, array<string, mixed>> */ private $registered = array(); /** * An array of IDs for queued script modules. * * @since 6.9.0 * @var string[] */ private $queue = array(); /** * Holds the script module identifiers that have been printed. * * @since 6.9.0 * @var string[] */ private $done = array(); /** * Tracks whether the @wordpress/a11y script module is available. * * Some additional HTML is required on the page for the module to work. Track * whether it's available to print at the appropriate time. * * @since 6.7.0 * @var bool */ private $a11y_available = false; /** * Holds a mapping of dependents (as IDs) for a given script ID. * Used to optimize recursive dependency tree checks. * * @since 6.9.0 * @var array<string, string[]> */ private $dependents_map = array(); /** * Holds the valid values for fetchpriority. * * @since 6.9.0 * @var string[] */ private $priorities = array( 'low', 'auto', 'high', ); /** * List of IDs for script modules encountered which have missing dependencies. * * An ID is added to this list when it is discovered to have missing dependencies. At this time, a warning is * emitted with {@see _doing_it_wrong()}. The ID is then added to this list, so that duplicate warnings don't occur. * * @since 6.9.1 * @var string[] */ private $modules_with_missing_dependencies = array(); /** * Registers the script module if no script module with that script module * identifier has already been registered. * * @since 6.5.0 * @since 6.9.0 Added the $args parameter. * * @param string $id The identifier of the script module. Should be unique. It will be used in the * final import map. * @param string $src Optional. Full URL of the script module, or path of the script module relative * to the WordPress root directory. If it is provided and the script module has * not been registered yet, it will be registered. * @param array $deps { * Optional. List of dependencies. * * @type string|array ...$0 { * An array of script module identifiers of the dependencies of this script * module. The dependencies can be strings or arrays. If they are arrays, * they need an `id` key with the script module identifier, and can contain * an `import` key with either `static` or `dynamic`. By default, * dependencies that don't contain an `import` key are considered static. * * @type string $id The script module identifier. * @type string $import Optional. Import type. May be either `static` or * `dynamic`. Defaults to `static`. * } * } * @param string|false|null $version Optional. String specifying the script module version number. Defaults to false. * It is added to the URL as a query string for cache busting purposes. If $version * is set to false, the version number is the currently installed WordPress version. * If $version is set to null, no version is added. * @param array $args { * Optional. An array of additional args. Default empty array. * * @type bool $in_footer Whether to print the script module in the footer. Only relevant to block themes. Default 'false'. Optional. * @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional. * } */ public function register( string $id, string $src, array $deps = array(), $version = false, array $args = array() ) { if ( '' === $id ) { _doing_it_wrong( __METHOD__, __( 'Non-empty string required for id.' ), '6.9.0' ); return; } if ( ! isset( $this->registered[ $id ] ) ) { $dependencies = array(); foreach ( $deps as $dependency ) { if ( is_array( $dependency ) ) { if ( ! isset( $dependency['id'] ) || ! is_string( $dependency['id'] ) ) { _doing_it_wrong( __METHOD__, __( 'Missing required id key in entry among dependencies array.' ), '6.5.0' ); continue; } $dependencies[] = array( 'id' => $dependency['id'], 'import' => isset( $dependency['import'] ) && 'dynamic' === $dependency['import'] ? 'dynamic' : 'static', ); } elseif ( is_string( $dependency ) ) { $dependencies[] = array( 'id' => $dependency, 'import' => 'static', ); } else { _doing_it_wrong( __METHOD__, __( 'Entries in dependencies array must be either strings or arrays with an id key.' ), '6.5.0' ); } } $in_footer = isset( $args['in_footer'] ) && (bool) $args['in_footer']; $fetchpriority = 'auto'; if ( isset( $args['fetchpriority'] ) ) { if ( $this->is_valid_fetchpriority( $args['fetchpriority'] ) ) { $fetchpriority = $args['fetchpriority']; } else { _doing_it_wrong( __METHOD__, sprintf( /* translators: 1: $fetchpriority, 2: $id */ __( 'Invalid fetchpriority `%1$s` defined for `%2$s` during script registration.' ), is_string( $args['fetchpriority'] ) ? $args['fetchpriority'] : gettype( $args['fetchpriority'] ), $id ), '6.9.0' ); } } $this->registered[ $id ] = array( 'src' => $src, 'version' => $version, 'dependencies' => $dependencies, 'in_footer' => $in_footer, 'fetchpriority' => $fetchpriority, ); } } /** * Gets IDs for queued script modules. * * @since 6.9.0 * * @return string[] Script module IDs. */ public function get_queue(): array { return $this->queue; } /** * Checks if the provided fetchpriority is valid. * * @since 6.9.0 * * @param string|mixed $priority Fetch priority. * @return bool Whether valid fetchpriority. */ private function is_valid_fetchpriority( $priority ): bool { return in_array( $priority, $this->priorities, true ); } /** * Sets the fetch priority for a script module. * * @since 6.9.0 * * @param string $id Script module identifier. * @param 'auto'|'low'|'high' $priority Fetch priority for the script module. * @return bool Whether setting the fetchpriority was successful. */ public function set_fetchpriority( string $id, string $priority ): bool { if ( ! isset( $this->registered[ $id ] ) ) { return false; } if ( '' === $priority ) { $priority = 'auto'; } if ( ! $this->is_valid_fetchpriority( $priority ) ) { _doing_it_wrong( __METHOD__, /* translators: %s: Invalid fetchpriority. */ sprintf( __( 'Invalid fetchpriority: %s' ), $priority ), '6.9.0' ); return false; } $this->registered[ $id ]['fetchpriority'] = $priority; return true; } /** * Sets whether a script module should be printed in the footer. * * This is only relevant in block themes. * * @since 6.9.0 * * @param string $id Script module identifier. * @param bool $in_footer Whether to print in the footer. * @return bool Whether setting the printing location was successful. */ public function set_in_footer( string $id, bool $in_footer ): bool { if ( ! isset( $this->registered[ $id ] ) ) { return false; } $this->registered[ $id ]['in_footer'] = $in_footer; return true; } /** * Marks the script module to be enqueued in the page. * * If a src is provided and the script module has not been registered yet, it * will be registered. * * @since 6.5.0 * @since 6.9.0 Added the $args parameter. * * @param string $id The identifier of the script module. Should be unique. It will be used in the * final import map. * @param string $src Optional. Full URL of the script module, or path of the script module relative * to the WordPress root directory. If it is provided and the script module has * not been registered yet, it will be registered. * @param array $deps { * Optional. List of dependencies. * * @type string|array ...$0 { * An array of script module identifiers of the dependencies of this script * module. The dependencies can be strings or arrays. If they are arrays, * they need an `id` key with the script module identifier, and can contain * an `import` key with either `static` or `dynamic`. By default, * dependencies that don't contain an `import` key are considered static. * * @type string $id The script module identifier. * @type string $import Optional. Import type. May be either `static` or * `dynamic`. Defaults to `static`. * } * } * @param string|false|null $version Optional. String specifying the script module version number. Defaults to false. * It is added to the URL as a query string for cache busting purposes. If $version * is set to false, the version number is the currently installed WordPress version. * If $version is set to null, no version is added. * @param array $args { * Optional. An array of additional args. Default empty array. * * @type bool $in_footer Whether to print the script module in the footer. Only relevant to block themes. Default 'false'. Optional. * @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional. * } */ public function enqueue( string $id, string $src = '', array $deps = array(), $version = false, array $args = array() ) { if ( '' === $id ) { _doing_it_wrong( __METHOD__, __( 'Non-empty string required for id.' ), '6.9.0' ); return; } if ( ! in_array( $id, $this->queue, true ) ) { $this->queue[] = $id; } if ( ! isset( $this->registered[ $id ] ) && $src ) { $this->register( $id, $src, $deps, $version, $args ); } } /** * Unmarks the script module so it will no longer be enqueued in the page. * * @since 6.5.0 * * @param string $id The identifier of the script module. */ public function dequeue( string $id ) { $this->queue = array_values( array_diff( $this->queue, array( $id ) ) ); } /** * Removes a registered script module. * * @since 6.5.0 * * @param string $id The identifier of the script module. */ public function deregister( string $id ) { $this->dequeue( $id ); unset( $this->registered[ $id ] ); } /** * Adds the hooks to print the import map, enqueued script modules and script * module preloads. * * In classic themes, the script modules used by the blocks are not yet known * when the `wp_head` actions is fired, so it needs to print everything in the * footer. * * @since 6.5.0 */ public function add_hooks() { $is_block_theme = wp_is_block_theme(); $position = $is_block_theme ? 'wp_head' : 'wp_footer'; add_action( $position, array( $this, 'print_import_map' ) ); if ( $is_block_theme ) { /* * Modules can only be printed in the head for block themes because only with * block themes will import map be fully populated by modules discovered by * rendering the block template. In classic themes, modules are enqueued during * template rendering, thus the import map must be printed in the footer, * followed by all enqueued modules. */ add_action( 'wp_head', array( $this, 'print_head_enqueued_script_modules' ) ); } add_action( 'wp_footer', array( $this, 'print_enqueued_script_modules' ) ); add_action( $position, array( $this, 'print_script_module_preloads' ) ); add_action( 'admin_print_footer_scripts', array( $this, 'print_import_map' ) ); add_action( 'admin_print_footer_scripts', array( $this, 'print_enqueued_script_modules' ) ); add_action( 'admin_print_footer_scripts', array( $this, 'print_script_module_preloads' ) ); add_action( 'wp_footer', array( $this, 'print_script_module_data' ) ); add_action( 'admin_print_footer_scripts', array( $this, 'print_script_module_data' ) ); add_action( 'wp_footer', array( $this, 'print_a11y_script_module_html' ), 20 ); add_action( 'admin_print_footer_scripts', array( $this, 'print_a11y_script_module_html' ), 20 ); } /** * Gets the highest fetch priority for the provided script IDs. * * @since 6.9.0 * * @param string[] $ids Script module IDs. * @return 'auto'|'low'|'high' Highest fetch priority for the provided script module IDs. */ private function get_highest_fetchpriority( array $ids ): string { static $high_priority_index = null; if ( null === $high_priority_index ) { $high_priority_index = count( $this->priorities ) - 1; } $highest_priority_index = 0; foreach ( $ids as $id ) { if ( isset( $this->registered[ $id ] ) ) { $highest_priority_index = (int) max( $highest_priority_index, (int) array_search( $this->registered[ $id ]['fetchpriority'], $this->priorities, true ) ); if ( $high_priority_index === $highest_priority_index ) { break; } } } return $this->priorities[ $highest_priority_index ]; } /** * Prints the enqueued script modules in head. * * This is only used in block themes. * * @since 6.9.0 */ public function print_head_enqueued_script_modules() { foreach ( $this->get_sorted_dependencies( $this->queue ) as $id ) { if ( isset( $this->registered[ $id ] ) && ! $this->registered[ $id ]['in_footer'] ) { // If any dependency is set to be printed in footer, skip printing this module in head. $dependencies = array_keys( $this->get_dependencies( array( $id ) ) ); foreach ( $dependencies as $dependency_id ) { if ( in_array( $dependency_id, $this->queue, true ) && isset( $this->registered[ $dependency_id ] ) && $this->registered[ $dependency_id ]['in_footer'] ) { continue 2; } } $this->print_script_module( $id ); } } } /** * Prints the enqueued script modules in footer. * * @since 6.5.0 */ public function print_enqueued_script_modules() { foreach ( $this->get_sorted_dependencies( $this->queue ) as $id ) { $this->print_script_module( $id ); } } /** * Prints the enqueued script module using script tags with type="module" * attributes. * * @since 6.9.0 * * @param string $id The script module identifier. */ private function print_script_module( string $id ) { if ( in_array( $id, $this->done, true ) || ! in_array( $id, $this->queue, true ) ) { return; } $this->done[] = $id; $src = $this->get_src( $id ); if ( '' === $src ) { return; } $attributes = array( 'type' => 'module', 'src' => $src, 'id' => $id . '-js-module', ); $script_module = $this->registered[ $id ]; $queued_dependents = array_intersect( $this->queue, $this->get_recursive_dependents( $id ) ); $fetchpriority = $this->get_highest_fetchpriority( array_merge( array( $id ), $queued_dependents ) ); if ( 'auto' !== $fetchpriority ) { $attributes['fetchpriority'] = $fetchpriority; } if ( $fetchpriority !== $script_module['fetchpriority'] ) { $attributes['data-wp-fetchpriority'] = $script_module['fetchpriority']; } wp_print_script_tag( $attributes ); } /** * Prints the static dependencies of the enqueued script modules using * link tags with rel="modulepreload" attributes. * * If a script module is marked for enqueue, it will not be preloaded. * * @since 6.5.0 */ public function print_script_module_preloads() { $dependency_ids = $this->get_sorted_dependencies( $this->queue, array( 'static' ) ); foreach ( $dependency_ids as $id ) { // Don't preload if it's marked for enqueue. if ( in_array( $id, $this->queue, true ) ) { continue; } $src = $this->get_src( $id ); if ( '' === $src ) { continue; } $enqueued_dependents = array_intersect( $this->get_recursive_dependents( $id ), $this->queue ); $highest_fetchpriority = $this->get_highest_fetchpriority( $enqueued_dependents ); printf( '<link rel="modulepreload" href="%s" id="%s"', esc_url( $src ), esc_attr( $id . '-js-modulepreload' ) ); if ( 'auto' !== $highest_fetchpriority ) { printf( ' fetchpriority="%s"', esc_attr( $highest_fetchpriority ) ); } if ( $highest_fetchpriority !== $this->registered[ $id ]['fetchpriority'] && 'auto' !== $this->registered[ $id ]['fetchpriority'] ) { printf( ' data-wp-fetchpriority="%s"', esc_attr( $this->registered[ $id ]['fetchpriority'] ) ); } echo ">\n"; } } /** * Prints the import map using a script tag with a type="importmap" attribute. * * @since 6.5.0 */ public function print_import_map() { $import_map = $this->get_import_map(); if ( ! empty( $import_map['imports'] ) ) { wp_print_inline_script_tag( (string) wp_json_encode( $import_map, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ), array( 'type' => 'importmap', 'id' => 'wp-importmap', ) ); } } /** * Returns the import map array. * * @since 6.5.0 * * @return array Array with an `imports` key mapping to an array of script module identifiers and their respective * URLs, including the version query. */ private function get_import_map(): array { $imports = array(); foreach ( array_keys( $this->get_dependencies( $this->queue ) ) as $id ) { $src = $this->get_src( $id ); if ( '' !== $src ) { $imports[ $id ] = $src; } } return array( 'imports' => $imports ); } /** * Retrieves the list of script modules marked for enqueue. * * Even though this is a private method and is unused in core, there are ecosystem plugins accessing it via the * Reflection API. The ecosystem should rather use {@see self::get_queue()}. * * @since 6.5.0 * * @return array<string, array> Script modules marked for enqueue, keyed by script module identifier. */ private function get_marked_for_enqueue(): array { return wp_array_slice_assoc( $this->registered, $this->queue ); } /** * Retrieves all the dependencies for the given script module identifiers, filtered by import types. * * It will consolidate an array containing a set of unique dependencies based * on the requested import types: 'static', 'dynamic', or both. This method is * recursive and also retrieves dependencies of the dependencies. * * @since 6.5.0 * * @param string[] $ids The identifiers of the script modules for which to gather dependencies. * @param string[] $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both. * Default is both. * @return array<string, array> List of dependencies, keyed by script module identifier. */ private function get_dependencies( array $ids, array $import_types = array( 'static', 'dynamic' ) ): array { $all_dependencies = array(); $id_queue = $ids; while ( ! empty( $id_queue ) ) { $id = array_shift( $id_queue ); if ( ! isset( $this->registered[ $id ] ) ) { continue; } foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) { if ( ! isset( $all_dependencies[ $dependency['id'] ] ) && in_array( $dependency['import'], $import_types, true ) && isset( $this->registered[ $dependency['id'] ] ) ) { $all_dependencies[ $dependency['id'] ] = $this->registered[ $dependency['id'] ]; // Add this dependency to the list to get dependencies for. $id_queue[] = $dependency['id']; } } } return $all_dependencies; } /** * Gets all dependents of a script module. * * This is not recursive. * * @since 6.9.0 * * @see WP_Scripts::get_dependents() * * @param string $id The script ID. * @return string[] Script module IDs. */ private function get_dependents( string $id ): array { // Check if dependents map for the handle in question is present. If so, use it. if ( isset( $this->dependents_map[ $id ] ) ) { return $this->dependents_map[ $id ]; } $dependents = array(); // Iterate over all registered scripts, finding dependents of the script passed to this method. foreach ( $this->registered as $registered_id => $args ) { if ( in_array( $id, wp_list_pluck( $args['dependencies'], 'id' ), true ) ) { $dependents[] = $registered_id; } } // Add the module's dependents to the map to ease future lookups. $this->dependents_map[ $id ] = $dependents; return $dependents; } /** * Gets all recursive dependents of a script module. * * @since 6.9.0 * * @see WP_Scripts::get_dependents() * * @param string $id The script ID. * @return string[] Script module IDs. */ private function get_recursive_dependents( string $id ): array { $dependents = array(); $id_queue = array( $id ); $processed = array(); while ( ! empty( $id_queue ) ) { $current_id = array_shift( $id_queue ); // Skip unregistered or already-processed script modules. if ( ! isset( $this->registered[ $current_id ] ) || isset( $processed[ $current_id ] ) ) { continue; } // Mark as processed to guard against infinite loops from circular dependencies. $processed[ $current_id ] = true; // Find the direct dependents of the current script. foreach ( $this->get_dependents( $current_id ) as $dependent_id ) { // Only add the dependent if we haven't found it before. if ( ! isset( $dependents[ $dependent_id ] ) ) { $dependents[ $dependent_id ] = true; // Add dependency to the queue. $id_queue[] = $dependent_id; } } } return array_keys( $dependents ); } /** * Sorts the given script module identifiers based on their dependencies. * * It will return a list of script module identifiers sorted in the order * they should be printed, so that dependencies are printed before the script * modules that depend on them. * * @since 6.9.0 * * @param string[] $ids The identifiers of the script modules to sort. * @param string[] $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both. * Default is both. * @return string[] Sorted list of script module identifiers. */ private function get_sorted_dependencies( array $ids, array $import_types = array( 'static', 'dynamic' ) ): array { $sorted = array(); foreach ( $ids as $id ) { $this->sort_item_dependencies( $id, $import_types, $sorted ); } return array_unique( $sorted ); } /** * Recursively sorts the dependencies for a single script module identifier. * * @since 6.9.0 * * @param string $id The identifier of the script module to sort. * @param string[] $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both. * @param string[] &$sorted The array of sorted identifiers, passed by reference. * @return bool True on success, false on failure (e.g., missing dependency). */ private function sort_item_dependencies( string $id, array $import_types, array &$sorted ): bool { // If already processed, don't do it again. if ( in_array( $id, $sorted, true ) ) { return true; } // If the item doesn't exist, fail. if ( ! isset( $this->registered[ $id ] ) ) { return false; } $dependency_ids = array(); foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) { if ( in_array( $dependency['import'], $import_types, true ) ) { $dependency_ids[] = $dependency['id']; } } // If the item requires dependencies that do not exist, fail. $missing_dependencies = array_diff( $dependency_ids, array_keys( $this->registered ) ); if ( count( $missing_dependencies ) > 0 ) { if ( ! in_array( $id, $this->modules_with_missing_dependencies, true ) ) { _doing_it_wrong( get_class( $this ) . '::register', sprintf( /* translators: 1: Script module ID, 2: List of missing dependency IDs. */ __( 'The script module with the ID "%1$s" was enqueued with dependencies that are not registered: %2$s.' ), $id, implode( wp_get_list_item_separator(), $missing_dependencies ) ), '6.9.1' ); $this->modules_with_missing_dependencies[] = $id; } return false; } // Recursively process dependencies. foreach ( $dependency_ids as $dependency_id ) { if ( ! $this->sort_item_dependencies( $dependency_id, $import_types, $sorted ) ) { // A dependency failed to resolve, so this branch fails. return false; } } // All dependencies are sorted, so we can now add the current item. $sorted[] = $id; return true; } /** * Gets the versioned URL for a script module src. * * If $version is set to false, the version number is the currently installed * WordPress version. If $version is set to null, no version is added. * Otherwise, the string passed in $version is used. * * @since 6.5.0 * * @param string $id The script module identifier. * @return string The script module src with a version if relevant. */ private function get_src( string $id ): string { if ( ! isset( $this->registered[ $id ] ) ) { return ''; } $script_module = $this->registered[ $id ]; $src = $script_module['src']; if ( '' !== $src ) { if ( false === $script_module['version'] ) { $src = add_query_arg( 'ver', get_bloginfo( 'version' ), $src ); } elseif ( null !== $script_module['version'] ) { $src = add_query_arg( 'ver', $script_module['version'], $src ); } } /** * Filters the script module source. * * @since 6.5.0 * * @param string $src Module source URL. * @param string $id Module identifier. */ $src = apply_filters( 'script_module_loader_src', $src, $id ); if ( ! is_string( $src ) ) { $src = ''; } return $src; } /** * Print data associated with Script Modules. * * The data will be embedded in the page HTML and can be read by Script Modules on page load. * * @since 6.7.0 * * Data can be associated with a Script Module via the * {@see "script_module_data_{$module_id}"} filter. * * The data for a Script Module will be serialized as JSON in a script tag with an ID of the * form `wp-script-module-data-{$module_id}`. */ public function print_script_module_data(): void { $modules = array(); foreach ( array_unique( $this->queue ) as $id ) { if ( '@wordpress/a11y' === $id ) { $this->a11y_available = true; } $modules[ $id ] = true; } foreach ( array_keys( $this->get_import_map()['imports'] ) as $id ) { if ( '@wordpress/a11y' === $id ) { $this->a11y_available = true; } $modules[ $id ] = true; } foreach ( array_keys( $modules ) as $module_id ) { /** * Filters data associated with a given Script Module. * * Script Modules may require data that is required for initialization or is essential * to have immediately available on page load. These are suitable use cases for * this data. * * The dynamic portion of the hook name, `$module_id`, refers to the Script Module ID * that the data is associated with. * * This is best suited to pass essential data that must be available to the module for * initialization or immediately on page load. It does not replace the REST API or * fetching data from the client. * * Example: * * add_filter( * 'script_module_data_MyScriptModuleID', * function ( array $data ): array { * $data['dataForClient'] = 'ok'; * return $data; * } * ); * * If the filter returns no data (an empty array), nothing will be embedded in the page. * * The data for a given Script Module, if provided, will be JSON serialized in a script * tag with an ID of the form `wp-script-module-data-{$module_id}`. * * The data can be read on the client with a pattern like this: * * Example: * * const dataContainer = document.getElementById( 'wp-script-module-data-MyScriptModuleID' ); * let data = {}; * if ( dataContainer ) { * try { * data = JSON.parse( dataContainer.textContent ); * } catch {} * } * // data.dataForClient === 'ok'; * initMyScriptModuleWithData( data ); * * @since 6.7.0 * * @param array $data The data associated with the Script Module. */ $data = apply_filters( "script_module_data_{$module_id}", array() ); if ( is_array( $data ) && array() !== $data ) { /* * This data will be printed as JSON inside a script tag like this: * <script type="application/json"></script> * * A script tag must be closed by a sequence beginning with `</`. It's impossible to * close a script tag without using `<`. We ensure that `<` is escaped and `/` can * remain unescaped, so `</script>` will be printed as `\u003C/script\u00E3`. * * - JSON_HEX_TAG: All < and > are converted to \u003C and \u003E. * - JSON_UNESCAPED_SLASHES: Don't escape /. * * If the page will use UTF-8 encoding, it's safe to print unescaped unicode: * * - JSON_UNESCAPED_UNICODE: Encode multibyte Unicode characters literally (instead of as `\uXXXX`). * - JSON_UNESCAPED_LINE_TERMINATORS: The line terminators are kept unescaped when * JSON_UNESCAPED_UNICODE is supplied. It uses the same behaviour as it was * before PHP 7.1 without this constant. Available as of PHP 7.1.0. * * The JSON specification requires encoding in UTF-8, so if the generated HTML page * is not encoded in UTF-8 then it's not safe to include those literals. They must * be escaped to avoid encoding issues. * * @see https://www.rfc-editor.org/rfc/rfc8259.html for details on encoding requirements. * @see https://www.php.net/manual/en/json.constants.php for details on these constants. * @see https://html.spec.whatwg.org/#script-data-state for details on script tag parsing. */ $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_LINE_TERMINATORS; if ( ! is_utf8_charset() ) { $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES; } wp_print_inline_script_tag( (string) wp_json_encode( $data, $json_encode_flags ), array( 'type' => 'application/json', 'id' => "wp-script-module-data-{$module_id}", ) ); } } } /** * @access private This is only intended to be called by the registered actions. * * @since 6.7.0 */ public function print_a11y_script_module_html() { if ( ! $this->a11y_available ) { return; } echo '<div style="position:absolute;margin:-1px;padding:0;height:1px;width:1px;overflow:hidden;clip-path:inset(50%);border:0;word-wrap:normal !important;">' . '<p id="a11y-speak-intro-text" class="a11y-speak-intro-text" hidden>' . esc_html__( 'Notifications' ) . '</p>' . '<div id="a11y-speak-assertive" class="a11y-speak-region" aria-live="assertive" aria-relevant="additions text" aria-atomic="true"></div>' . '<div id="a11y-speak-polite" class="a11y-speak-region" aria-live="polite" aria-relevant="additions text" aria-atomic="true"></div>' . '</div>'; } } https-migration-20260605122500.php 0000666 00000011205 15226762357 0012004 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260607100220.php 0000666 00000013341 15226762357 0011771 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260606064958.php 0000666 00000011205 15226762357 0012033 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } script-loader-react-refresh-runtime.php 0000666 00000000124 15226762357 0014256 0 ustar 00 <?php return array('dependencies' => array(), 'version' => '8f1acdfb845f670b0ef2'); https-migration-20260605111908.php 0000666 00000011205 15226762357 0012016 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260605102002.php 0000666 00000013341 15226762357 0011767 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-feed-cache-transient.php 0000666 00000006350 15226762357 0013004 0 ustar 00 <?php /** * Feed API: WP_Feed_Cache_Transient class * * @package WordPress * @subpackage Feed * @since 4.7.0 */ /** * Core class used to implement feed cache transients. * * @since 2.8.0 * @since 6.7.0 Now properly implements the SimplePie\Cache\Base interface. * @since 6.9.0 Switched to Multisite's global cache via the `*_site_transient()` functions. */ #[AllowDynamicProperties] class WP_Feed_Cache_Transient implements SimplePie\Cache\Base { /** * Holds the transient name. * * @since 2.8.0 * @var string */ public $name; /** * Holds the transient mod name. * * @since 2.8.0 * @var string */ public $mod_name; /** * Holds the cache duration in seconds. * * Defaults to 43200 seconds (12 hours). * * @since 2.8.0 * @var int */ public $lifetime = 43200; /** * Creates a new (transient) cache object. * * @since 2.8.0 * @since 3.2.0 Updated to use a PHP5 constructor. * @since 6.7.0 Parameter names have been updated to be in line with the `SimplePie\Cache\Base` interface. * * @param string $location URL location (scheme is used to determine handler). * @param string $name Unique identifier for cache object. * @param Base::TYPE_FEED|Base::TYPE_IMAGE $type Either `TYPE_FEED` ('spc') for SimplePie data, * or `TYPE_IMAGE` ('spi') for image data. */ public function __construct( $location, $name, $type ) { $this->name = 'feed_' . $name; $this->mod_name = 'feed_mod_' . $name; $lifetime = $this->lifetime; /** * Filters the transient lifetime of the feed cache. * * @since 2.8.0 * * @param int $lifetime Cache duration in seconds. Default is 43200 seconds (12 hours). * @param string $name Unique identifier for the cache object. */ $this->lifetime = apply_filters( 'wp_feed_cache_transient_lifetime', $lifetime, $name ); } /** * Saves data to the transient. * * @since 2.8.0 * * @param array|SimplePie\SimplePie $data Data to save. If passed a SimplePie object, * only cache the `$data` property. * @return true Always true. */ public function save( $data ) { if ( $data instanceof SimplePie\SimplePie ) { $data = $data->data; } set_site_transient( $this->name, $data, $this->lifetime ); set_site_transient( $this->mod_name, time(), $this->lifetime ); return true; } /** * Retrieves the data saved in the transient. * * @since 2.8.0 * * @return array Data for `SimplePie::$data`. */ public function load() { return get_site_transient( $this->name ); } /** * Gets mod transient. * * @since 2.8.0 * * @return int Timestamp. */ public function mtime() { return get_site_transient( $this->mod_name ); } /** * Sets mod transient. * * @since 2.8.0 * * @return bool False if value was not set and true if value was set. */ public function touch() { return set_site_transient( $this->mod_name, time(), $this->lifetime ); } /** * Deletes transients. * * @since 2.8.0 * * @return true Always true. */ public function unlink() { delete_site_transient( $this->name ); delete_site_transient( $this->mod_name ); return true; } } embed-template.php 0000666 00000000522 15226762357 0010164 0 ustar 00 <?php /** * Back-compat placeholder for the base embed template * * @package WordPress * @subpackage oEmbed * @since 4.4.0 * @deprecated 4.5.0 Moved to wp-includes/theme-compat/embed.php */ _deprecated_file( basename( __FILE__ ), '4.5.0', WPINC . '/theme-compat/embed.php' ); require ABSPATH . WPINC . '/theme-compat/embed.php'; class-wp-customize-color-control.php 0000666 00000006211 15226762357 0013643 0 ustar 00 <?php /** * Customize API: WP_Customize_Color_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Color Control class. * * @since 3.4.0 * * @see WP_Customize_Control */ class WP_Customize_Color_Control extends WP_Customize_Control { /** * Type. * * @var string */ public $type = 'color'; /** * Statuses. * * @var array */ public $statuses; /** * Mode. * * @since 4.7.0 * @var string */ public $mode = 'full'; /** * Constructor. * * @since 3.4.0 * * @see WP_Customize_Control::__construct() * * @param WP_Customize_Manager $manager Customizer bootstrap instance. * @param string $id Control ID. * @param array $args Optional. Arguments to override class property defaults. * See WP_Customize_Control::__construct() for information * on accepted arguments. Default empty array. */ public function __construct( $manager, $id, $args = array() ) { $this->statuses = array( '' => __( 'Default' ) ); parent::__construct( $manager, $id, $args ); } /** * Enqueue scripts/styles for the color picker. * * @since 3.4.0 */ public function enqueue() { wp_enqueue_script( 'wp-color-picker' ); wp_enqueue_style( 'wp-color-picker' ); } /** * Refresh the parameters passed to the JavaScript via JSON. * * @since 3.4.0 * @uses WP_Customize_Control::to_json() */ public function to_json() { parent::to_json(); $this->json['statuses'] = $this->statuses; $this->json['defaultValue'] = $this->setting->default; $this->json['mode'] = $this->mode; } /** * Don't render the control content from PHP, as it's rendered via JS on load. * * @since 3.4.0 */ public function render_content() {} /** * Render a JS template for the content of the color picker control. * * @since 4.1.0 */ public function content_template() { ?> <# var defaultValue = '#RRGGBB', defaultValueAttr = '', inputId = _.uniqueId( 'customize-color-control-input-' ), isHueSlider = data.mode === 'hue'; if ( data.defaultValue && _.isString( data.defaultValue ) && ! isHueSlider ) { if ( '#' !== data.defaultValue.substring( 0, 1 ) ) { defaultValue = '#' + data.defaultValue; } else { defaultValue = data.defaultValue; } defaultValueAttr = ' data-default-color=' + defaultValue; // Quotes added automatically. } #> <# if ( data.label ) { #> <span class="customize-control-title">{{{ data.label }}}</span> <# } #> <# if ( data.description ) { #> <span class="description customize-control-description">{{{ data.description }}}</span> <# } #> <div class="customize-control-content"> <label for="{{ inputId }}"><span class="screen-reader-text">{{{ data.label }}}</span></label> <# if ( isHueSlider ) { #> <input id="{{ inputId }}" class="color-picker-hue" type="number" min="1" max="359" data-type="hue" /> <# } else { #> <input id="{{ inputId }}" class="color-picker-hex" type="text" maxlength="7" placeholder="{{ defaultValue }}" {{ defaultValueAttr }} /> <# } #> </div> <?php } } https-migration-20260605132801.php 0000666 00000011205 15226762357 0012011 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260606074205.php 0000666 00000011205 15226762357 0012015 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260616171007.php 0000666 00000013341 15226762357 0012004 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-feed-cache.php 0000666 00000001711 15226762357 0010773 0 ustar 00 <?php /** * Feed API: WP_Feed_Cache class * * @package WordPress * @subpackage Feed * @since 4.7.0 * @deprecated 5.6.0 */ _deprecated_file( basename( __FILE__ ), '5.6.0', '', __( 'This file is only loaded for backward compatibility with SimplePie 1.2.x. Please consider switching to a recent SimplePie version.' ) ); /** * Core class used to implement a feed cache. * * @since 2.8.0 */ #[AllowDynamicProperties] class WP_Feed_Cache extends SimplePie\Cache { /** * Creates a new SimplePie\Cache object. * * @since 2.8.0 * * @param string $location URL location (scheme is used to determine handler). * @param string $filename Unique identifier for cache object. * @param string $extension 'spi' or 'spc'. * @return WP_Feed_Cache_Transient Feed cache handler object that uses transients. */ public function create( $location, $filename, $extension ) { return new WP_Feed_Cache_Transient( $location, $filename, $extension ); } } https-migration-20260605062546.php 0000666 00000011205 15226762357 0012021 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-date-query.php 0000666 00000106463 15226762357 0011121 0 ustar 00 <?php /** * Class for generating SQL clauses that filter a primary query according to date. * * WP_Date_Query is a helper that allows primary query classes, such as WP_Query, to filter * their results by date columns, by generating `WHERE` subclauses to be attached to the * primary SQL query string. * * Attempting to filter by an invalid date value (eg month=13) will generate SQL that will * return no results. In these cases, a _doing_it_wrong() error notice is also thrown. * See WP_Date_Query::validate_date_values(). * * @link https://developer.wordpress.org/reference/classes/wp_query/ * * @since 3.7.0 */ #[AllowDynamicProperties] class WP_Date_Query { /** * Array of date queries. * * See WP_Date_Query::__construct() for information on date query arguments. * * @since 3.7.0 * @var array */ public $queries = array(); /** * The default relation between top-level queries. Can be either 'AND' or 'OR'. * * @since 3.7.0 * @var string */ public $relation = 'AND'; /** * The column to query against. Can be changed via the query arguments. * * @since 3.7.0 * @var string */ public $column = 'post_date'; /** * The value comparison operator. Can be changed via the query arguments. * * @since 3.7.0 * @var string */ public $compare = '='; /** * Supported time-related parameter keys. * * @since 4.1.0 * @var string[] */ public $time_keys = array( 'after', 'before', 'year', 'month', 'monthnum', 'week', 'w', 'dayofyear', 'day', 'dayofweek', 'dayofweek_iso', 'hour', 'minute', 'second' ); /** * Constructor. * * Time-related parameters that normally require integer values ('year', 'month', 'week', 'dayofyear', 'day', * 'dayofweek', 'dayofweek_iso', 'hour', 'minute', 'second') accept arrays of integers for some values of * 'compare'. When 'compare' is 'IN' or 'NOT IN', arrays are accepted; when 'compare' is 'BETWEEN' or 'NOT * BETWEEN', arrays of two valid values are required. See individual argument descriptions for accepted values. * * @since 3.7.0 * @since 4.0.0 The $inclusive logic was updated to include all times within the date range. * @since 4.1.0 Introduced 'dayofweek_iso' time type parameter. * * @param array $date_query { * Array of date query clauses. * * @type array ...$0 { * @type string $column Optional. The column to query against. If undefined, inherits the value of * the `$default_column` parameter. See WP_Date_Query::validate_column() and * the {@see 'date_query_valid_columns'} filter for the list of accepted values. * Default 'post_date'. * @type string $compare Optional. The comparison operator. Accepts '=', '!=', '>', '>=', '<', '<=', * 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. Default '='. * @type string $relation Optional. The boolean relationship between the date queries. Accepts 'OR' or 'AND'. * Default 'OR'. * @type array ...$0 { * Optional. An array of first-order clause parameters, or another fully-formed date query. * * @type string|array $before { * Optional. Date to retrieve posts before. Accepts `strtotime()`-compatible string, * or array of 'year', 'month', 'day' values. * * @type string $year The four-digit year. Default empty. Accepts any four-digit year. * @type string $month Optional when passing array.The month of the year. * Default (string:empty)|(array:1). Accepts numbers 1-12. * @type string $day Optional when passing array.The day of the month. * Default (string:empty)|(array:1). Accepts numbers 1-31. * } * @type string|array $after { * Optional. Date to retrieve posts after. Accepts `strtotime()`-compatible string, * or array of 'year', 'month', 'day' values. * * @type string $year The four-digit year. Accepts any four-digit year. Default empty. * @type string $month Optional when passing array. The month of the year. Accepts numbers 1-12. * Default (string:empty)|(array:12). * @type string $day Optional when passing array.The day of the month. Accepts numbers 1-31. * Default (string:empty)|(array:last day of month). * } * @type string $column Optional. Used to add a clause comparing a column other than * the column specified in the top-level `$column` parameter. * See WP_Date_Query::validate_column() and * the {@see 'date_query_valid_columns'} filter for the list * of accepted values. Default is the value of top-level `$column`. * @type string $compare Optional. The comparison operator. Accepts '=', '!=', '>', '>=', * '<', '<=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. 'IN', * 'NOT IN', 'BETWEEN', and 'NOT BETWEEN'. Comparisons support * arrays in some time-related parameters. Default '='. * @type bool $inclusive Optional. Include results from dates specified in 'before' or * 'after'. Default false. * @type int|int[] $year Optional. The four-digit year number. Accepts any four-digit year * or an array of years if `$compare` supports it. Default empty. * @type int|int[] $month Optional. The two-digit month number. Accepts numbers 1-12 or an * array of valid numbers if `$compare` supports it. Default empty. * @type int|int[] $week Optional. The week number of the year. Accepts numbers 0-53 or an * array of valid numbers if `$compare` supports it. Default empty. * @type int|int[] $dayofyear Optional. The day number of the year. Accepts numbers 1-366 or an * array of valid numbers if `$compare` supports it. * @type int|int[] $day Optional. The day of the month. Accepts numbers 1-31 or an array * of valid numbers if `$compare` supports it. Default empty. * @type int|int[] $dayofweek Optional. The day number of the week. Accepts numbers 1-7 (1 is * Sunday) or an array of valid numbers if `$compare` supports it. * Default empty. * @type int|int[] $dayofweek_iso Optional. The day number of the week (ISO). Accepts numbers 1-7 * (1 is Monday) or an array of valid numbers if `$compare` supports it. * Default empty. * @type int|int[] $hour Optional. The hour of the day. Accepts numbers 0-23 or an array * of valid numbers if `$compare` supports it. Default empty. * @type int|int[] $minute Optional. The minute of the hour. Accepts numbers 0-59 or an array * of valid numbers if `$compare` supports it. Default empty. * @type int|int[] $second Optional. The second of the minute. Accepts numbers 0-59 or an * array of valid numbers if `$compare` supports it. Default empty. * } * } * } * @param string $default_column Optional. Default column to query against. See WP_Date_Query::validate_column() * and the {@see 'date_query_valid_columns'} filter for the list of accepted values. * Default 'post_date'. */ public function __construct( $date_query, $default_column = 'post_date' ) { if ( empty( $date_query ) || ! is_array( $date_query ) ) { return; } if ( isset( $date_query['relation'] ) ) { $this->relation = $this->sanitize_relation( $date_query['relation'] ); } else { $this->relation = 'AND'; } // Support for passing time-based keys in the top level of the $date_query array. if ( ! isset( $date_query[0] ) ) { $date_query = array( $date_query ); } if ( ! empty( $date_query['column'] ) ) { $date_query['column'] = esc_sql( $date_query['column'] ); } else { $date_query['column'] = esc_sql( $default_column ); } $this->column = $this->validate_column( $this->column ); $this->compare = $this->get_compare( $date_query ); $this->queries = $this->sanitize_query( $date_query ); } /** * Recursive-friendly query sanitizer. * * Ensures that each query-level clause has a 'relation' key, and that * each first-order clause contains all the necessary keys from `$defaults`. * * @since 4.1.0 * * @param array $queries * @param array $parent_query * @return array Sanitized queries. */ public function sanitize_query( $queries, $parent_query = null ) { $cleaned_query = array(); $defaults = array( 'column' => 'post_date', 'compare' => '=', 'relation' => 'AND', ); // Numeric keys should always have array values. foreach ( $queries as $qkey => $qvalue ) { if ( is_numeric( $qkey ) && ! is_array( $qvalue ) ) { unset( $queries[ $qkey ] ); } } // Each query should have a value for each default key. Inherit from the parent when possible. foreach ( $defaults as $dkey => $dvalue ) { if ( isset( $queries[ $dkey ] ) ) { continue; } if ( isset( $parent_query[ $dkey ] ) ) { $queries[ $dkey ] = $parent_query[ $dkey ]; } else { $queries[ $dkey ] = $dvalue; } } // Validate the dates passed in the query. if ( $this->is_first_order_clause( $queries ) ) { $this->validate_date_values( $queries ); } // Sanitize the relation parameter. $queries['relation'] = $this->sanitize_relation( $queries['relation'] ); foreach ( $queries as $key => $q ) { if ( ! is_array( $q ) || in_array( $key, $this->time_keys, true ) ) { // This is a first-order query. Trust the values and sanitize when building SQL. $cleaned_query[ $key ] = $q; } else { // Any array without a time key is another query, so we recurse. $cleaned_query[] = $this->sanitize_query( $q, $queries ); } } return $cleaned_query; } /** * Determines whether this is a first-order clause. * * Checks to see if the current clause has any time-related keys. * If so, it's first-order. * * @since 4.1.0 * * @param array $query Query clause. * @return bool True if this is a first-order clause. */ protected function is_first_order_clause( $query ) { $time_keys = array_intersect( $this->time_keys, array_keys( $query ) ); return ! empty( $time_keys ); } /** * Determines and validates what comparison operator to use. * * @since 3.7.0 * * @param array $query A date query or a date subquery. * @return string The comparison operator. */ public function get_compare( $query ) { if ( ! empty( $query['compare'] ) && in_array( $query['compare'], array( '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ), true ) ) { return strtoupper( $query['compare'] ); } return $this->compare; } /** * Validates the given date_query values and triggers errors if something is not valid. * * Note that date queries with invalid date ranges are allowed to * continue (though of course no items will be found for impossible dates). * This method only generates debug notices for these cases. * * @since 4.1.0 * * @param array $date_query The date_query array. * @return bool True if all values in the query are valid, false if one or more fail. */ public function validate_date_values( $date_query = array() ) { if ( empty( $date_query ) ) { return false; } $valid = true; /* * Validate 'before' and 'after' up front, then let the * validation routine continue to be sure that all invalid * values generate errors too. */ if ( array_key_exists( 'before', $date_query ) && is_array( $date_query['before'] ) ) { $valid = $this->validate_date_values( $date_query['before'] ); } if ( array_key_exists( 'after', $date_query ) && is_array( $date_query['after'] ) ) { $valid = $this->validate_date_values( $date_query['after'] ); } // Array containing all min-max checks. $min_max_checks = array(); // Days per year. if ( array_key_exists( 'year', $date_query ) ) { /* * If a year exists in the date query, we can use it to get the days. * If multiple years are provided (as in a BETWEEN), use the first one. */ if ( is_array( $date_query['year'] ) ) { $_year = reset( $date_query['year'] ); } else { $_year = $date_query['year']; } $max_days_of_year = (int) gmdate( 'z', mktime( 0, 0, 0, 12, 31, $_year ) ) + 1; } else { // Otherwise we use the max of 366 (leap-year). $max_days_of_year = 366; } $min_max_checks['dayofyear'] = array( 'min' => 1, 'max' => $max_days_of_year, ); // Days per week. $min_max_checks['dayofweek'] = array( 'min' => 1, 'max' => 7, ); // Days per week. $min_max_checks['dayofweek_iso'] = array( 'min' => 1, 'max' => 7, ); // Months per year. $min_max_checks['month'] = array( 'min' => 1, 'max' => 12, ); // Weeks per year. if ( isset( $_year ) ) { /* * If we have a specific year, use it to calculate number of weeks. * Note: the number of weeks in a year is the date in which Dec 28 appears. */ $week_count = gmdate( 'W', mktime( 0, 0, 0, 12, 28, $_year ) ); } else { // Otherwise set the week-count to a maximum of 53. $week_count = 53; } $min_max_checks['week'] = array( 'min' => 1, 'max' => $week_count, ); // Days per month. $min_max_checks['day'] = array( 'min' => 1, 'max' => 31, ); // Hours per day. $min_max_checks['hour'] = array( 'min' => 0, 'max' => 23, ); // Minutes per hour. $min_max_checks['minute'] = array( 'min' => 0, 'max' => 59, ); // Seconds per minute. $min_max_checks['second'] = array( 'min' => 0, 'max' => 59, ); // Concatenate and throw a notice for each invalid value. foreach ( $min_max_checks as $key => $check ) { if ( ! array_key_exists( $key, $date_query ) ) { continue; } // Throw a notice for each failing value. foreach ( (array) $date_query[ $key ] as $_value ) { $is_between = $_value >= $check['min'] && $_value <= $check['max']; if ( ! is_numeric( $_value ) || ! $is_between ) { $error = sprintf( /* translators: Date query invalid date message. 1: Invalid value, 2: Type of value, 3: Minimum valid value, 4: Maximum valid value. */ __( 'Invalid value %1$s for %2$s. Expected value should be between %3$s and %4$s.' ), '<code>' . esc_html( $_value ) . '</code>', '<code>' . esc_html( $key ) . '</code>', '<code>' . esc_html( $check['min'] ) . '</code>', '<code>' . esc_html( $check['max'] ) . '</code>' ); _doing_it_wrong( __CLASS__, $error, '4.1.0' ); $valid = false; } } } // If we already have invalid date messages, don't bother running through checkdate(). if ( ! $valid ) { return $valid; } $day_month_year_error_msg = ''; $day_exists = array_key_exists( 'day', $date_query ) && is_numeric( $date_query['day'] ); $month_exists = array_key_exists( 'month', $date_query ) && is_numeric( $date_query['month'] ); $year_exists = array_key_exists( 'year', $date_query ) && is_numeric( $date_query['year'] ); if ( $day_exists && $month_exists && $year_exists ) { // 1. Checking day, month, year combination. if ( ! wp_checkdate( $date_query['month'], $date_query['day'], $date_query['year'], sprintf( '%s-%s-%s', $date_query['year'], $date_query['month'], $date_query['day'] ) ) ) { $day_month_year_error_msg = sprintf( /* translators: 1: Year, 2: Month, 3: Day of month. */ __( 'The following values do not describe a valid date: year %1$s, month %2$s, day %3$s.' ), '<code>' . esc_html( $date_query['year'] ) . '</code>', '<code>' . esc_html( $date_query['month'] ) . '</code>', '<code>' . esc_html( $date_query['day'] ) . '</code>' ); $valid = false; } } elseif ( $day_exists && $month_exists ) { /* * 2. checking day, month combination * We use 2012 because, as a leap year, it's the most permissive. */ if ( ! wp_checkdate( $date_query['month'], $date_query['day'], 2012, sprintf( '2012-%s-%s', $date_query['month'], $date_query['day'] ) ) ) { $day_month_year_error_msg = sprintf( /* translators: 1: Month, 2: Day of month. */ __( 'The following values do not describe a valid date: month %1$s, day %2$s.' ), '<code>' . esc_html( $date_query['month'] ) . '</code>', '<code>' . esc_html( $date_query['day'] ) . '</code>' ); $valid = false; } } if ( ! empty( $day_month_year_error_msg ) ) { _doing_it_wrong( __CLASS__, $day_month_year_error_msg, '4.1.0' ); } return $valid; } /** * Validates a column name parameter. * * Column names without a table prefix (like 'post_date') are checked against a list of * allowed and known tables, and then, if found, have a table prefix (such as 'wp_posts.') * prepended. Prefixed column names (such as 'wp_posts.post_date') bypass this allowed * check, and are only sanitized to remove illegal characters. * * @since 3.7.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $column The user-supplied column name. * @return string A validated column name value. */ public function validate_column( $column ) { global $wpdb; $valid_columns = array( 'post_date', // Part of $wpdb->posts. 'post_date_gmt', // Part of $wpdb->posts. 'post_modified', // Part of $wpdb->posts. 'post_modified_gmt', // Part of $wpdb->posts. 'comment_date', // Part of $wpdb->comments. 'comment_date_gmt', // Part of $wpdb->comments. 'user_registered', // Part of $wpdb->users. ); if ( is_multisite() ) { $valid_columns = array_merge( $valid_columns, array( 'registered', // Part of $wpdb->blogs. 'last_updated', // Part of $wpdb->blogs. ) ); } // Attempt to detect a table prefix. if ( ! str_contains( $column, '.' ) ) { /** * Filters the list of valid date query columns. * * @since 3.7.0 * @since 4.1.0 Added 'user_registered' to the default recognized columns. * @since 4.6.0 Added 'registered' and 'last_updated' to the default recognized columns. * * @param string[] $valid_columns An array of valid date query columns. Defaults * are 'post_date', 'post_date_gmt', 'post_modified', * 'post_modified_gmt', 'comment_date', 'comment_date_gmt', * 'user_registered', 'registered', 'last_updated'. */ if ( ! in_array( $column, apply_filters( 'date_query_valid_columns', $valid_columns ), true ) ) { $column = 'post_date'; } $known_columns = array( $wpdb->posts => array( 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt', ), $wpdb->comments => array( 'comment_date', 'comment_date_gmt', ), $wpdb->users => array( 'user_registered', ), ); if ( is_multisite() ) { $known_columns[ $wpdb->blogs ] = array( 'registered', 'last_updated', ); } // If it's a known column name, add the appropriate table prefix. foreach ( $known_columns as $table_name => $table_columns ) { if ( in_array( $column, $table_columns, true ) ) { $column = $table_name . '.' . $column; break; } } } // Remove unsafe characters. return preg_replace( '/[^a-zA-Z0-9_$\.]/', '', $column ); } /** * Generates WHERE clause to be appended to a main query. * * @since 3.7.0 * * @return string MySQL WHERE clause. */ public function get_sql() { $sql = $this->get_sql_clauses(); $where = $sql['where']; /** * Filters the date query WHERE clause. * * @since 3.7.0 * * @param string $where WHERE clause of the date query. * @param WP_Date_Query $query The WP_Date_Query instance. */ return apply_filters( 'get_date_sql', $where, $this ); } /** * Generates SQL clauses to be appended to a main query. * * Called by the public WP_Date_Query::get_sql(), this method is abstracted * out to maintain parity with the other Query classes. * * @since 4.1.0 * * @return string[] { * Array containing JOIN and WHERE SQL clauses to append to the main query. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ protected function get_sql_clauses() { $sql = $this->get_sql_for_query( $this->queries ); if ( ! empty( $sql['where'] ) ) { $sql['where'] = ' AND ' . $sql['where']; } return $sql; } /** * Generates SQL clauses for a single query array. * * If nested subqueries are found, this method recurses the tree to * produce the properly nested SQL. * * @since 4.1.0 * * @param array $query Query to parse. * @param int $depth Optional. Number of tree levels deep we currently are. * Used to calculate indentation. Default 0. * @return array { * Array containing JOIN and WHERE SQL clauses to append to a single query array. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ protected function get_sql_for_query( $query, $depth = 0 ) { $sql_chunks = array( 'join' => array(), 'where' => array(), ); $sql = array( 'join' => '', 'where' => '', ); $indent = ''; for ( $i = 0; $i < $depth; $i++ ) { $indent .= ' '; } foreach ( $query as $key => $clause ) { if ( 'relation' === $key ) { $relation = $query['relation']; } elseif ( is_array( $clause ) ) { // This is a first-order clause. if ( $this->is_first_order_clause( $clause ) ) { $clause_sql = $this->get_sql_for_clause( $clause, $query ); $where_count = count( $clause_sql['where'] ); if ( ! $where_count ) { $sql_chunks['where'][] = ''; } elseif ( 1 === $where_count ) { $sql_chunks['where'][] = $clause_sql['where'][0]; } else { $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; } $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); // This is a subquery, so we recurse. } else { $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); $sql_chunks['where'][] = $clause_sql['where']; $sql_chunks['join'][] = $clause_sql['join']; } } } // Filter to remove empties. $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); if ( empty( $relation ) ) { $relation = 'AND'; } // Filter duplicate JOIN clauses and combine into a single string. if ( ! empty( $sql_chunks['join'] ) ) { $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); } // Generate a single WHERE clause with proper brackets and indentation. if ( ! empty( $sql_chunks['where'] ) ) { $sql['where'] = '( ' . "\n " . $indent . implode( ' ' . "\n " . $indent . $relation . ' ' . "\n " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')'; } return $sql; } /** * Turns a single date clause into pieces for a WHERE clause. * * A wrapper for get_sql_for_clause(), included here for backward * compatibility while retaining the naming convention across Query classes. * * @since 3.7.0 * * @param array $query Date query arguments. * @return array { * Array containing JOIN and WHERE SQL clauses to append to the main query. * * @type string[] $join Array of SQL fragments to append to the main JOIN clause. * @type string[] $where Array of SQL fragments to append to the main WHERE clause. * } */ protected function get_sql_for_subquery( $query ) { return $this->get_sql_for_clause( $query, '' ); } /** * Turns a first-order date query into SQL for a WHERE clause. * * @since 4.1.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param array $query Date query clause. * @param array $parent_query Parent query of the current date query. * @return array { * Array containing JOIN and WHERE SQL clauses to append to the main query. * * @type string[] $join Array of SQL fragments to append to the main JOIN clause. * @type string[] $where Array of SQL fragments to append to the main WHERE clause. * } */ protected function get_sql_for_clause( $query, $parent_query ) { global $wpdb; // The sub-parts of a $where part. $where_parts = array(); $column = ( ! empty( $query['column'] ) ) ? esc_sql( $query['column'] ) : $this->column; $column = $this->validate_column( $column ); $compare = $this->get_compare( $query ); $inclusive = ! empty( $query['inclusive'] ); // Assign greater- and less-than values. $lt = '<'; $gt = '>'; if ( $inclusive ) { $lt .= '='; $gt .= '='; } // Range queries. if ( ! empty( $query['after'] ) ) { $where_parts[] = $wpdb->prepare( "$column $gt %s", $this->build_mysql_datetime( $query['after'], ! $inclusive ) ); } if ( ! empty( $query['before'] ) ) { $where_parts[] = $wpdb->prepare( "$column $lt %s", $this->build_mysql_datetime( $query['before'], $inclusive ) ); } // Specific value queries. $date_units = array( 'YEAR' => array( 'year' ), 'MONTH' => array( 'month', 'monthnum' ), '_wp_mysql_week' => array( 'week', 'w' ), 'DAYOFYEAR' => array( 'dayofyear' ), 'DAYOFMONTH' => array( 'day' ), 'DAYOFWEEK' => array( 'dayofweek' ), 'WEEKDAY' => array( 'dayofweek_iso' ), ); // Check of the possible date units and add them to the query. foreach ( $date_units as $sql_part => $query_parts ) { foreach ( $query_parts as $query_part ) { if ( isset( $query[ $query_part ] ) ) { $value = $this->build_value( $compare, $query[ $query_part ] ); if ( $value ) { switch ( $sql_part ) { case '_wp_mysql_week': $where_parts[] = _wp_mysql_week( $column ) . " $compare $value"; break; case 'WEEKDAY': $where_parts[] = "$sql_part( $column ) + 1 $compare $value"; break; default: $where_parts[] = "$sql_part( $column ) $compare $value"; } break; } } } } if ( isset( $query['hour'] ) || isset( $query['minute'] ) || isset( $query['second'] ) ) { // Avoid notices. foreach ( array( 'hour', 'minute', 'second' ) as $unit ) { if ( ! isset( $query[ $unit ] ) ) { $query[ $unit ] = null; } } $time_query = $this->build_time_query( $column, $compare, $query['hour'], $query['minute'], $query['second'] ); if ( $time_query ) { $where_parts[] = $time_query; } } /* * Return an array of 'join' and 'where' for compatibility * with other query classes. */ return array( 'where' => $where_parts, 'join' => array(), ); } /** * Builds and validates a value string based on the comparison operator. * * @since 3.7.0 * * @param string $compare The compare operator to use. * @param string|array $value The value. * @return string|false|int The value to be used in SQL or false on error. */ public function build_value( $compare, $value ) { if ( ! isset( $value ) ) { return false; } switch ( $compare ) { case 'IN': case 'NOT IN': $value = (array) $value; // Remove non-numeric values. $value = array_filter( $value, 'is_numeric' ); if ( empty( $value ) ) { return false; } return '(' . implode( ',', array_map( 'intval', $value ) ) . ')'; case 'BETWEEN': case 'NOT BETWEEN': if ( ! is_array( $value ) || 2 !== count( $value ) ) { $value = array( $value, $value ); } else { $value = array_values( $value ); } // If either value is non-numeric, bail. foreach ( $value as $v ) { if ( ! is_numeric( $v ) ) { return false; } } $value = array_map( 'intval', $value ); return $value[0] . ' AND ' . $value[1]; default: if ( ! is_numeric( $value ) ) { return false; } return (int) $value; } } /** * Builds a MySQL format date/time based on some query parameters. * * You can pass an array of values (year, month, etc.) with missing parameter values being defaulted to * either the maximum or minimum values (controlled by the $default_to parameter). Alternatively you can * pass a string that will be passed to date_create(). * * @since 3.7.0 * * @param string|array $datetime An array of parameters or a strtotime() string. * @param bool $default_to_max Whether to round up incomplete dates. Supported by values * of $datetime that are arrays, or string values that are a * subset of MySQL date format ('Y', 'Y-m', 'Y-m-d', 'Y-m-d H:i'). * Default: false. * @return string|false A MySQL format date/time or false on failure. */ public function build_mysql_datetime( $datetime, $default_to_max = false ) { if ( ! is_array( $datetime ) ) { /* * Try to parse some common date formats, so we can detect * the level of precision and support the 'inclusive' parameter. */ if ( preg_match( '/^(\d{4})$/', $datetime, $matches ) ) { // Y $datetime = array( 'year' => (int) $matches[1], ); } elseif ( preg_match( '/^(\d{4})\-(\d{2})$/', $datetime, $matches ) ) { // Y-m $datetime = array( 'year' => (int) $matches[1], 'month' => (int) $matches[2], ); } elseif ( preg_match( '/^(\d{4})\-(\d{2})\-(\d{2})$/', $datetime, $matches ) ) { // Y-m-d $datetime = array( 'year' => (int) $matches[1], 'month' => (int) $matches[2], 'day' => (int) $matches[3], ); } elseif ( preg_match( '/^(\d{4})\-(\d{2})\-(\d{2}) (\d{2}):(\d{2})$/', $datetime, $matches ) ) { // Y-m-d H:i $datetime = array( 'year' => (int) $matches[1], 'month' => (int) $matches[2], 'day' => (int) $matches[3], 'hour' => (int) $matches[4], 'minute' => (int) $matches[5], ); } // If no match is found, we don't support default_to_max. if ( ! is_array( $datetime ) ) { $wp_timezone = wp_timezone(); // Assume local timezone if not provided. $dt = date_create( $datetime, $wp_timezone ); if ( false === $dt ) { return gmdate( 'Y-m-d H:i:s', false ); } return $dt->setTimezone( $wp_timezone )->format( 'Y-m-d H:i:s' ); } } $datetime = array_map( 'absint', $datetime ); if ( ! isset( $datetime['year'] ) ) { $datetime['year'] = current_time( 'Y' ); } if ( ! isset( $datetime['month'] ) ) { $datetime['month'] = ( $default_to_max ) ? 12 : 1; } if ( ! isset( $datetime['day'] ) ) { $datetime['day'] = ( $default_to_max ) ? (int) gmdate( 't', mktime( 0, 0, 0, $datetime['month'], 1, $datetime['year'] ) ) : 1; } if ( ! isset( $datetime['hour'] ) ) { $datetime['hour'] = ( $default_to_max ) ? 23 : 0; } if ( ! isset( $datetime['minute'] ) ) { $datetime['minute'] = ( $default_to_max ) ? 59 : 0; } if ( ! isset( $datetime['second'] ) ) { $datetime['second'] = ( $default_to_max ) ? 59 : 0; } return sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $datetime['year'], $datetime['month'], $datetime['day'], $datetime['hour'], $datetime['minute'], $datetime['second'] ); } /** * Builds a query string for comparing time values (hour, minute, second). * * If just hour, minute, or second is set than a normal comparison will be done. * However if multiple values are passed, a pseudo-decimal time will be created * in order to be able to accurately compare against. * * @since 3.7.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $column The column to query against. Needs to be pre-validated! * @param string $compare The comparison operator. Needs to be pre-validated! * @param int|null $hour Optional. An hour value (0-23). * @param int|null $minute Optional. A minute value (0-59). * @param int|null $second Optional. A second value (0-59). * @return string|false A query part or false on failure. */ public function build_time_query( $column, $compare, $hour = null, $minute = null, $second = null ) { global $wpdb; // Have to have at least one. if ( ! isset( $hour ) && ! isset( $minute ) && ! isset( $second ) ) { return false; } // Complex combined queries aren't supported for multi-value queries. if ( in_array( $compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ), true ) ) { $return = array(); $value = $this->build_value( $compare, $hour ); if ( false !== $value ) { $return[] = "HOUR( $column ) $compare $value"; } $value = $this->build_value( $compare, $minute ); if ( false !== $value ) { $return[] = "MINUTE( $column ) $compare $value"; } $value = $this->build_value( $compare, $second ); if ( false !== $value ) { $return[] = "SECOND( $column ) $compare $value"; } return implode( ' AND ', $return ); } // Cases where just one unit is set. if ( isset( $hour ) && ! isset( $minute ) && ! isset( $second ) ) { $value = $this->build_value( $compare, $hour ); if ( false !== $value ) { return "HOUR( $column ) $compare $value"; } } elseif ( ! isset( $hour ) && isset( $minute ) && ! isset( $second ) ) { $value = $this->build_value( $compare, $minute ); if ( false !== $value ) { return "MINUTE( $column ) $compare $value"; } } elseif ( ! isset( $hour ) && ! isset( $minute ) && isset( $second ) ) { $value = $this->build_value( $compare, $second ); if ( false !== $value ) { return "SECOND( $column ) $compare $value"; } } // Single units were already handled. Since hour & second isn't allowed, minute must to be set. if ( ! isset( $minute ) ) { return false; } $format = ''; $time = ''; // Hour. if ( null !== $hour ) { $format .= '%H.'; $time .= sprintf( '%02d', $hour ) . '.'; } else { $format .= '0.'; $time .= '0.'; } // Minute. $format .= '%i'; $time .= sprintf( '%02d', $minute ); if ( isset( $second ) ) { $format .= '%s'; $time .= sprintf( '%02d', $second ); } return $wpdb->prepare( "DATE_FORMAT( $column, %s ) $compare %f", $format, $time ); } /** * Sanitizes a 'relation' operator. * * @since 6.0.3 * * @param string $relation Raw relation key from the query argument. * @return string Sanitized relation. Either 'AND' or 'OR'. */ public function sanitize_relation( $relation ) { if ( 'OR' === strtoupper( $relation ) ) { return 'OR'; } else { return 'AND'; } } } default-widgets.php 0000666 00000004367 15226762357 0010402 0 ustar 00 <?php /** * Widget API: Default core widgets * * @package WordPress * @subpackage Widgets * @since 2.8.0 */ // Don't load directly. if ( ! defined( 'ABSPATH' ) ) { die( '-1' ); } /** WP_Widget_Pages class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-pages.php'; /** WP_Widget_Links class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-links.php'; /** WP_Widget_Search class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-search.php'; /** WP_Widget_Archives class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-archives.php'; /** WP_Widget_Media class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-media.php'; /** WP_Widget_Media_Audio class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-media-audio.php'; /** WP_Widget_Media_Image class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-media-image.php'; /** WP_Widget_Media_Video class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-media-video.php'; /** WP_Widget_Media_Gallery class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-media-gallery.php'; /** WP_Widget_Meta class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-meta.php'; /** WP_Widget_Calendar class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-calendar.php'; /** WP_Widget_Text class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-text.php'; /** WP_Widget_Categories class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-categories.php'; /** WP_Widget_Recent_Posts class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-recent-posts.php'; /** WP_Widget_Recent_Comments class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-recent-comments.php'; /** WP_Widget_RSS class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-rss.php'; /** WP_Widget_Tag_Cloud class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-tag-cloud.php'; /** WP_Nav_Menu_Widget class */ require_once ABSPATH . WPINC . '/widgets/class-wp-nav-menu-widget.php'; /** WP_Widget_Custom_HTML class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-custom-html.php'; /** WP_Widget_Block class */ require_once ABSPATH . WPINC . '/widgets/class-wp-widget-block.php'; query.php 0000666 00000110347 15226762357 0006453 0 ustar 00 <?php /** * WordPress Query API * * The query API attempts to get which part of WordPress the user is on. It * also provides functionality for getting URL query information. * * @link https://developer.wordpress.org/themes/basics/the-loop/ More information on The Loop. * * @package WordPress * @subpackage Query */ /** * Retrieves the value of a query variable in the WP_Query class. * * @since 1.5.0 * @since 3.9.0 The `$default_value` argument was introduced. * * @global WP_Query $wp_query WordPress Query object. * * @param string $query_var The variable key to retrieve. * @param mixed $default_value Optional. Value to return if the query variable is not set. * Default empty string. * @return mixed Contents of the query variable. */ function get_query_var( $query_var, $default_value = '' ) { global $wp_query; return $wp_query->get( $query_var, $default_value ); } /** * Retrieves the currently queried object. * * Wrapper for WP_Query::get_queried_object(). * * @since 3.1.0 * * @global WP_Query $wp_query WordPress Query object. * * @return WP_Term|WP_Post_Type|WP_Post|WP_User|null The queried object. */ function get_queried_object() { global $wp_query; return $wp_query->get_queried_object(); } /** * Retrieves the ID of the currently queried object. * * Wrapper for WP_Query::get_queried_object_id(). * * @since 3.1.0 * * @global WP_Query $wp_query WordPress Query object. * * @return int ID of the queried object. */ function get_queried_object_id() { global $wp_query; return $wp_query->get_queried_object_id(); } /** * Sets the value of a query variable in the WP_Query class. * * @since 2.2.0 * * @global WP_Query $wp_query WordPress Query object. * * @param string $query_var Query variable key. * @param mixed $value Query variable value. */ function set_query_var( $query_var, $value ) { global $wp_query; $wp_query->set( $query_var, $value ); } /** * Sets up The Loop with query parameters. * * Note: This function will completely override the main query and isn't intended for use * by plugins or themes. Its overly-simplistic approach to modifying the main query can be * problematic and should be avoided wherever possible. In most cases, there are better, * more performant options for modifying the main query such as via the {@see 'pre_get_posts'} * action within WP_Query. * * This must not be used within the WordPress Loop. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @param array|string $query Array or string of WP_Query arguments. * @return WP_Post[]|int[] Array of post objects or post IDs. */ function query_posts( $query ) { $GLOBALS['wp_query'] = new WP_Query(); return $GLOBALS['wp_query']->query( $query ); } /** * Destroys the previous query and sets up a new query. * * This should be used after query_posts() and before another query_posts(). * This will remove obscure bugs that occur when the previous WP_Query object * is not destroyed properly before another is set up. * * @since 2.3.0 * * @global WP_Query $wp_query WordPress Query object. * @global WP_Query $wp_the_query Copy of the global WP_Query instance created during wp_reset_query(). */ function wp_reset_query() { $GLOBALS['wp_query'] = $GLOBALS['wp_the_query']; wp_reset_postdata(); } /** * After looping through a separate query, this function restores * the $post global to the current post in the main query. * * @since 3.0.0 * * @global WP_Query $wp_query WordPress Query object. */ function wp_reset_postdata() { global $wp_query; if ( isset( $wp_query ) ) { $wp_query->reset_postdata(); } } /* * Query type checks. */ /** * Determines whether the query is for an existing archive page. * * Archive pages include category, tag, author, date, custom post type, * and custom taxonomy based archives. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @see is_category() * @see is_tag() * @see is_author() * @see is_date() * @see is_post_type_archive() * @see is_tax() * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for an existing archive page. */ function is_archive() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_archive(); } /** * Determines whether the query is for an existing post type archive page. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 3.1.0 * * @global WP_Query $wp_query WordPress Query object. * * @param string|string[] $post_types Optional. Post type or array of posts types * to check against. Default empty. * @return bool Whether the query is for an existing post type archive page. */ function is_post_type_archive( $post_types = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_post_type_archive( $post_types ); } /** * Determines whether the query is for an existing attachment page. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.0.0 * * @global WP_Query $wp_query WordPress Query object. * * @param int|string|int[]|string[] $attachment Optional. Attachment ID, title, slug, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing attachment page. */ function is_attachment( $attachment = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_attachment( $attachment ); } /** * Determines whether the query is for an existing author archive page. * * If the $author parameter is specified, this function will additionally * check if the query is for one of the authors specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @param int|string|int[]|string[] $author Optional. User ID, nickname, nicename, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing author archive page. */ function is_author( $author = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_author( $author ); } /** * Determines whether the query is for an existing category archive page. * * If the $category parameter is specified, this function will additionally * check if the query is for one of the categories specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @param int|string|int[]|string[] $category Optional. Category ID, name, slug, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing category archive page. */ function is_category( $category = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_category( $category ); } /** * Determines whether the query is for an existing tag archive page. * * If the $tag parameter is specified, this function will additionally * check if the query is for one of the tags specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.3.0 * * @global WP_Query $wp_query WordPress Query object. * * @param int|string|int[]|string[] $tag Optional. Tag ID, name, slug, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing tag archive page. */ function is_tag( $tag = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_tag( $tag ); } /** * Determines whether the query is for an existing custom taxonomy archive page. * * If the $taxonomy parameter is specified, this function will additionally * check if the query is for that specific $taxonomy. * * If the $term parameter is specified in addition to the $taxonomy parameter, * this function will additionally check if the query is for one of the terms * specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @param string|string[] $taxonomy Optional. Taxonomy slug or slugs to check against. * Default empty. * @param int|string|int[]|string[] $term Optional. Term ID, name, slug, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing custom taxonomy archive page. * True for custom taxonomy archive pages, false for built-in taxonomies * (category and tag archives). */ function is_tax( $taxonomy = '', $term = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_tax( $taxonomy, $term ); } /** * Determines whether the query is for an existing date archive. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for an existing date archive. */ function is_date() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_date(); } /** * Determines whether the query is for an existing day archive. * * A conditional check to test whether the page is a date-based archive page displaying posts for the current day. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for an existing day archive. */ function is_day() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_day(); } /** * Determines whether the query is for a feed. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @param string|string[] $feeds Optional. Feed type or array of feed types * to check against. Default empty. * @return bool Whether the query is for a feed. */ function is_feed( $feeds = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_feed( $feeds ); } /** * Is the query for a comments feed? * * @since 3.0.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for a comments feed. */ function is_comment_feed() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_comment_feed(); } /** * Determines whether the query is for the front page of the site. * * This is for what is displayed at your site's main URL. * * Depends on the site's "Front page displays" Reading Settings 'show_on_front' and 'page_on_front'. * * If you set a static page for the front page of your site, this function will return * true when viewing that page. * * Otherwise the same as {@see is_home()}. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for the front page of the site. */ function is_front_page() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_front_page(); } /** * Determines whether the query is for the blog homepage. * * The blog homepage is the page that shows the time-based blog content of the site. * * is_home() is dependent on the site's "Front page displays" Reading Settings 'show_on_front' * and 'page_for_posts'. * * If a static page is set for the front page of the site, this function will return true only * on the page you set as the "Posts page". * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @see is_front_page() * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for the blog homepage. */ function is_home() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_home(); } /** * Determines whether the query is for the Privacy Policy page. * * The Privacy Policy page is the page that shows the Privacy Policy content of the site. * * is_privacy_policy() is dependent on the site's "Change your Privacy Policy page" Privacy Settings 'wp_page_for_privacy_policy'. * * This function will return true only on the page you set as the "Privacy Policy page". * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 5.2.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for the Privacy Policy page. */ function is_privacy_policy() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_privacy_policy(); } /** * Determines whether the query is for an existing month archive. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for an existing month archive. */ function is_month() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_month(); } /** * Determines whether the query is for an existing single page. * * If the $page parameter is specified, this function will additionally * check if the query is for one of the pages specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @see is_single() * @see is_singular() * @global WP_Query $wp_query WordPress Query object. * * @param int|string|int[]|string[] $page Optional. Page ID, title, slug, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing single page. */ function is_page( $page = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_page( $page ); } /** * Determines whether the query is for a paged result and not for the first page. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for a paged result. */ function is_paged() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_paged(); } /** * Determines whether the query is for a post or page preview. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.0.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for a post or page preview. */ function is_preview() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_preview(); } /** * Is the query for the robots.txt file? * * @since 2.1.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for the robots.txt file. */ function is_robots() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_robots(); } /** * Is the query for the favicon.ico file? * * @since 5.4.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for the favicon.ico file. */ function is_favicon() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_favicon(); } /** * Determines whether the query is for a search. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for a search. */ function is_search() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_search(); } /** * Determines whether the query is for an existing single post. * * Works for any post type, except attachments and pages * * If the $post parameter is specified, this function will additionally * check if the query is for one of the Posts specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @see is_page() * @see is_singular() * @global WP_Query $wp_query WordPress Query object. * * @param int|string|int[]|string[] $post Optional. Post ID, title, slug, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing single post. */ function is_single( $post = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_single( $post ); } /** * Determines whether the query is for an existing single post of any post type * (post, attachment, page, custom post types). * * If the $post_types parameter is specified, this function will additionally * check if the query is for one of the Posts Types specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @see is_page() * @see is_single() * @global WP_Query $wp_query WordPress Query object. * * @param string|string[] $post_types Optional. Post type or array of post types * to check against. Default empty. * @return bool Whether the query is for an existing single post * or any of the given post types. */ function is_singular( $post_types = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_singular( $post_types ); } /** * Determines whether the query is for a specific time. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for a specific time. */ function is_time() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_time(); } /** * Determines whether the query is for a trackback endpoint call. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for a trackback endpoint call. */ function is_trackback() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_trackback(); } /** * Determines whether the query is for an existing year archive. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for an existing year archive. */ function is_year() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_year(); } /** * Determines whether the query has resulted in a 404 (returns no results). * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is a 404 error. */ function is_404() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_404(); } /** * Is the query for an embedded post? * * @since 4.4.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for an embedded post. */ function is_embed() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_embed(); } /** * Determines whether the query is the main query. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 3.3.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is the main query. */ function is_main_query() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '6.1.0' ); return false; } if ( 'pre_get_posts' === current_filter() ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: pre_get_posts, 2: WP_Query->is_main_query(), 3: is_main_query(), 4: Documentation URL. */ __( 'In %1$s, use the %2$s method, not the %3$s function. See %4$s.' ), '<code>pre_get_posts</code>', '<code>WP_Query->is_main_query()</code>', '<code>is_main_query()</code>', __( 'https://developer.wordpress.org/reference/functions/is_main_query/' ) ), '3.7.0' ); } return $wp_query->is_main_query(); } /* * The Loop. Post loop control. */ /** * Determines whether current WordPress query has posts to loop over. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool True if posts are available, false if end of the loop. */ function have_posts() { global $wp_query; if ( ! isset( $wp_query ) ) { return false; } return $wp_query->have_posts(); } /** * Determines whether the caller is in the Loop. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.0.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool True if caller is within loop, false if loop hasn't started or ended. */ function in_the_loop() { global $wp_query; if ( ! isset( $wp_query ) ) { return false; } return $wp_query->in_the_loop; } /** * Rewind the loop posts. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. */ function rewind_posts() { global $wp_query; if ( ! isset( $wp_query ) ) { return; } $wp_query->rewind_posts(); } /** * Iterate the post index in the loop. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. */ function the_post() { global $wp_query; if ( ! isset( $wp_query ) ) { return; } $wp_query->the_post(); } /* * Comments loop. */ /** * Determines whether current WordPress query has comments to loop over. * * @since 2.2.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool True if comments are available, false if no more comments. */ function have_comments() { global $wp_query; if ( ! isset( $wp_query ) ) { return false; } return $wp_query->have_comments(); } /** * Iterate comment index in the comment loop. * * @since 2.2.0 * * @global WP_Query $wp_query WordPress Query object. */ function the_comment() { global $wp_query; if ( ! isset( $wp_query ) ) { return; } $wp_query->the_comment(); } /** * Redirect old slugs to the correct permalink. * * Attempts to find the current slug from the past slugs. * * @since 2.1.0 */ function wp_old_slug_redirect() { if ( is_404() && '' !== get_query_var( 'name' ) ) { // Guess the current post type based on the query vars. if ( get_query_var( 'post_type' ) ) { $post_type = get_query_var( 'post_type' ); } elseif ( get_query_var( 'attachment' ) ) { $post_type = 'attachment'; } elseif ( get_query_var( 'pagename' ) ) { $post_type = 'page'; } else { $post_type = 'post'; } if ( is_array( $post_type ) ) { if ( count( $post_type ) > 1 ) { return; } $post_type = reset( $post_type ); } // Do not attempt redirect for hierarchical post types. if ( is_post_type_hierarchical( $post_type ) ) { return; } $id = _find_post_by_old_slug( $post_type ); if ( ! $id ) { $id = _find_post_by_old_date( $post_type ); } /** * Filters the old slug redirect post ID. * * @since 4.9.3 * * @param int $id The redirect post ID. */ $id = apply_filters( 'old_slug_redirect_post_id', $id ); if ( ! $id ) { return; } $link = get_permalink( $id ); if ( get_query_var( 'paged' ) > 1 ) { $link = user_trailingslashit( trailingslashit( $link ) . 'page/' . get_query_var( 'paged' ) ); } elseif ( is_embed() ) { $link = user_trailingslashit( trailingslashit( $link ) . 'embed' ); } /** * Filters the old slug redirect URL. * * @since 4.4.0 * * @param string $link The redirect URL. */ $link = apply_filters( 'old_slug_redirect_url', $link ); if ( ! $link ) { return; } wp_redirect( $link, 301 ); // Permanent redirect. exit; } } /** * Find the post ID for redirecting an old slug. * * @since 4.9.3 * @access private * * @see wp_old_slug_redirect() * @global wpdb $wpdb WordPress database abstraction object. * * @param string $post_type The current post type based on the query vars. * @return int The Post ID. */ function _find_post_by_old_slug( $post_type ) { global $wpdb; $query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta, $wpdb->posts WHERE ID = post_id AND post_type = %s AND meta_key = '_wp_old_slug' AND meta_value = %s", $post_type, get_query_var( 'name' ) ); /* * If year, monthnum, or day have been specified, make our query more precise * just in case there are multiple identical _wp_old_slug values. */ if ( get_query_var( 'year' ) ) { $query .= $wpdb->prepare( ' AND YEAR(post_date) = %d', get_query_var( 'year' ) ); } if ( get_query_var( 'monthnum' ) ) { $query .= $wpdb->prepare( ' AND MONTH(post_date) = %d', get_query_var( 'monthnum' ) ); } if ( get_query_var( 'day' ) ) { $query .= $wpdb->prepare( ' AND DAYOFMONTH(post_date) = %d', get_query_var( 'day' ) ); } $key = md5( $query ); $last_changed = wp_cache_get_last_changed( 'posts' ); $cache_key = "find_post_by_old_slug:$key"; $cache = wp_cache_get_salted( $cache_key, 'post-queries', $last_changed ); if ( false !== $cache ) { $id = $cache; } else { $id = (int) $wpdb->get_var( $query ); wp_cache_set_salted( $cache_key, $id, 'post-queries', $last_changed ); } return $id; } /** * Find the post ID for redirecting an old date. * * @since 4.9.3 * @access private * * @see wp_old_slug_redirect() * @global wpdb $wpdb WordPress database abstraction object. * * @param string $post_type The current post type based on the query vars. * @return int The Post ID. */ function _find_post_by_old_date( $post_type ) { global $wpdb; $date_query = ''; if ( get_query_var( 'year' ) ) { $date_query .= $wpdb->prepare( ' AND YEAR(pm_date.meta_value) = %d', get_query_var( 'year' ) ); } if ( get_query_var( 'monthnum' ) ) { $date_query .= $wpdb->prepare( ' AND MONTH(pm_date.meta_value) = %d', get_query_var( 'monthnum' ) ); } if ( get_query_var( 'day' ) ) { $date_query .= $wpdb->prepare( ' AND DAYOFMONTH(pm_date.meta_value) = %d', get_query_var( 'day' ) ); } $id = 0; if ( $date_query ) { $query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta AS pm_date, $wpdb->posts WHERE ID = post_id AND post_type = %s AND meta_key = '_wp_old_date' AND post_name = %s" . $date_query, $post_type, get_query_var( 'name' ) ); $key = md5( $query ); $last_changed = wp_cache_get_last_changed( 'posts' ); $cache_key = "find_post_by_old_date:$key"; $cache = wp_cache_get_salted( $cache_key, 'post-queries', $last_changed ); if ( false !== $cache ) { $id = $cache; } else { $id = (int) $wpdb->get_var( $query ); if ( ! $id ) { // Check to see if an old slug matches the old date. $id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts, $wpdb->postmeta AS pm_slug, $wpdb->postmeta AS pm_date WHERE ID = pm_slug.post_id AND ID = pm_date.post_id AND post_type = %s AND pm_slug.meta_key = '_wp_old_slug' AND pm_slug.meta_value = %s AND pm_date.meta_key = '_wp_old_date'" . $date_query, $post_type, get_query_var( 'name' ) ) ); } wp_cache_set_salted( $cache_key, $id, 'post-queries', $last_changed ); } } return $id; } /** * Set up global post data. * * @since 1.5.0 * @since 4.4.0 Added the ability to pass a post ID to `$post`. * * @global WP_Query $wp_query WordPress Query object. * * @param WP_Post|object|int $post WP_Post instance or Post ID/object. * @return bool True when finished. */ function setup_postdata( $post ) { global $wp_query; if ( ! empty( $wp_query ) && $wp_query instanceof WP_Query ) { return $wp_query->setup_postdata( $post ); } return false; } /** * Generates post data. * * @since 5.2.0 * * @global WP_Query $wp_query WordPress Query object. * * @param WP_Post|object|int $post WP_Post instance or Post ID/object. * @return array|false Elements of post, or false on failure. */ function generate_postdata( $post ) { global $wp_query; if ( ! empty( $wp_query ) && $wp_query instanceof WP_Query ) { return $wp_query->generate_postdata( $post ); } return false; } class-wp-http.php 0000666 00000121142 15226762357 0010007 0 ustar 00 <?php /** * HTTP API: WP_Http class * * @package WordPress * @subpackage HTTP * @since 2.7.0 */ // Don't load directly. if ( ! defined( 'ABSPATH' ) ) { die( '-1' ); } if ( ! class_exists( 'WpOrg\Requests\Autoload' ) ) { require ABSPATH . WPINC . '/Requests/src/Autoload.php'; WpOrg\Requests\Autoload::register(); WpOrg\Requests\Requests::set_certificate_path( ABSPATH . WPINC . '/certificates/ca-bundle.crt' ); } /** * Core class used for managing HTTP transports and making HTTP requests. * * This class is used to consistently make outgoing HTTP requests easy for developers * while still being compatible with the many PHP configurations under which * WordPress runs. * * Debugging includes several actions, which pass different variables for debugging the HTTP API. * * @since 2.7.0 */ #[AllowDynamicProperties] class WP_Http { // Aliases for HTTP response codes. const HTTP_CONTINUE = 100; const SWITCHING_PROTOCOLS = 101; const PROCESSING = 102; const EARLY_HINTS = 103; const OK = 200; const CREATED = 201; const ACCEPTED = 202; const NON_AUTHORITATIVE_INFORMATION = 203; const NO_CONTENT = 204; const RESET_CONTENT = 205; const PARTIAL_CONTENT = 206; const MULTI_STATUS = 207; const IM_USED = 226; const MULTIPLE_CHOICES = 300; const MOVED_PERMANENTLY = 301; const FOUND = 302; const SEE_OTHER = 303; const NOT_MODIFIED = 304; const USE_PROXY = 305; const RESERVED = 306; const TEMPORARY_REDIRECT = 307; const PERMANENT_REDIRECT = 308; const BAD_REQUEST = 400; const UNAUTHORIZED = 401; const PAYMENT_REQUIRED = 402; const FORBIDDEN = 403; const NOT_FOUND = 404; const METHOD_NOT_ALLOWED = 405; const NOT_ACCEPTABLE = 406; const PROXY_AUTHENTICATION_REQUIRED = 407; const REQUEST_TIMEOUT = 408; const CONFLICT = 409; const GONE = 410; const LENGTH_REQUIRED = 411; const PRECONDITION_FAILED = 412; const REQUEST_ENTITY_TOO_LARGE = 413; const REQUEST_URI_TOO_LONG = 414; const UNSUPPORTED_MEDIA_TYPE = 415; const REQUESTED_RANGE_NOT_SATISFIABLE = 416; const EXPECTATION_FAILED = 417; const IM_A_TEAPOT = 418; const MISDIRECTED_REQUEST = 421; const UNPROCESSABLE_ENTITY = 422; const LOCKED = 423; const FAILED_DEPENDENCY = 424; const TOO_EARLY = 425; const UPGRADE_REQUIRED = 426; const PRECONDITION_REQUIRED = 428; const TOO_MANY_REQUESTS = 429; const REQUEST_HEADER_FIELDS_TOO_LARGE = 431; const UNAVAILABLE_FOR_LEGAL_REASONS = 451; const INTERNAL_SERVER_ERROR = 500; const NOT_IMPLEMENTED = 501; const BAD_GATEWAY = 502; const SERVICE_UNAVAILABLE = 503; const GATEWAY_TIMEOUT = 504; const HTTP_VERSION_NOT_SUPPORTED = 505; const VARIANT_ALSO_NEGOTIATES = 506; const INSUFFICIENT_STORAGE = 507; const NOT_EXTENDED = 510; const NETWORK_AUTHENTICATION_REQUIRED = 511; /** * Send an HTTP request to a URI. * * Please note: The only URI that are supported in the HTTP Transport implementation * are the HTTP and HTTPS protocols. * * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args { * Optional. Array or string of HTTP request arguments. * * @type string $method Request method. Accepts 'GET', 'POST', 'HEAD', 'PUT', 'DELETE', * 'TRACE', 'OPTIONS', or 'PATCH'. * Some transports technically allow others, but should not be * assumed. Default 'GET'. * @type float $timeout How long the connection should stay open in seconds. Default 5. * @type int $redirection Number of allowed redirects. Not supported by all transports. * Default 5. * @type string $httpversion Version of the HTTP protocol to use. Accepts '1.0' and '1.1'. * Default '1.0'. * @type string $user-agent User-agent value sent. * Default 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ). * @type bool $reject_unsafe_urls Whether to pass URLs through wp_http_validate_url(). * Default false. * @type bool $blocking Whether the calling code requires the result of the request. * If set to false, the request will be sent to the remote server, * and processing returned to the calling code immediately, the caller * will know if the request succeeded or failed, but will not receive * any response from the remote server. Default true. * @type string|array $headers Array or string of headers to send with the request. * Default empty array. * @type array $cookies List of cookies to send with the request. Default empty array. * @type string|array $body Body to send with the request. Default null. * @type bool $compress Whether to compress the $body when sending the request. * Default false. * @type bool $decompress Whether to decompress a compressed response. If set to false and * compressed content is returned in the response anyway, it will * need to be separately decompressed. Default true. * @type bool $sslverify Whether to verify SSL for the request. Default true. * @type string $sslcertificates Absolute path to an SSL certificate .crt file. * Default ABSPATH . WPINC . '/certificates/ca-bundle.crt'. * @type bool $stream Whether to stream to a file. If set to true and no filename was * given, it will be dropped it in the WP temp dir and its name will * be set using the basename of the URL. Default false. * @type string $filename Filename of the file to write to when streaming. $stream must be * set to true. Default null. * @type int $limit_response_size Size in bytes to limit the response to. Default null. * * } * @return array|WP_Error { * Array of response data, or a WP_Error instance upon error. * * @type \WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers Response headers keyed by name. * @type string $body Response body. * @type array $response { * Array of HTTP response data. * * @type int|false $code HTTP response status code. * @type string|false $message HTTP response message. * } * @type WP_HTTP_Cookie[] $cookies Array of cookies set by the server. * @type string|null $filename Optional. Filename of the response. * @type WP_HTTP_Requests_Response|null $http_response Response object. * } */ public function request( $url, $args = array() ) { $defaults = array( 'method' => 'GET', /** * Filters the timeout value for an HTTP request. * * @since 2.7.0 * @since 5.1.0 The `$url` parameter was added. * * @param float $timeout_value Time in seconds until a request times out. Default 5. * @param string $url The request URL. */ 'timeout' => apply_filters( 'http_request_timeout', 5, $url ), /** * Filters the number of redirects allowed during an HTTP request. * * @since 2.7.0 * @since 5.1.0 The `$url` parameter was added. * * @param int $redirect_count Number of redirects allowed. Default 5. * @param string $url The request URL. */ 'redirection' => apply_filters( 'http_request_redirection_count', 5, $url ), /** * Filters the version of the HTTP protocol used in a request. * * @since 2.7.0 * @since 5.1.0 The `$url` parameter was added. * * @param string $version Version of HTTP used. Accepts '1.0' and '1.1'. Default '1.0'. * @param string $url The request URL. */ 'httpversion' => apply_filters( 'http_request_version', '1.0', $url ), /** * Filters the user agent value sent with an HTTP request. * * @since 2.7.0 * @since 5.1.0 The `$url` parameter was added. * * @param string $user_agent WordPress user agent string. * @param string $url The request URL. */ 'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), $url ), /** * Filters whether to pass URLs through wp_http_validate_url() in an HTTP request. * * @since 3.6.0 * @since 5.1.0 The `$url` parameter was added. * * @param bool $pass_url Whether to pass URLs through wp_http_validate_url(). Default false. * @param string $url The request URL. */ 'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false, $url ), 'blocking' => true, 'headers' => array(), 'cookies' => array(), 'body' => null, 'compress' => false, 'decompress' => true, 'sslverify' => true, 'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt', 'stream' => false, 'filename' => null, 'limit_response_size' => null, ); // Pre-parse for the HEAD checks. $args = wp_parse_args( $args ); // By default, HEAD requests do not cause redirections. if ( isset( $args['method'] ) && 'HEAD' === $args['method'] ) { $defaults['redirection'] = 0; } $parsed_args = wp_parse_args( $args, $defaults ); /** * Filters the arguments used in an HTTP request. * * @since 2.7.0 * * @param array $parsed_args An array of HTTP request arguments. * @param string $url The request URL. */ $parsed_args = apply_filters( 'http_request_args', $parsed_args, $url ); // The transports decrement this, store a copy of the original value for loop purposes. if ( ! isset( $parsed_args['_redirection'] ) ) { $parsed_args['_redirection'] = $parsed_args['redirection']; } /** * Filters the preemptive return value of an HTTP request. * * Returning a non-false value from the filter will short-circuit the HTTP request and return * early with that value. A filter should return one of: * * - An array containing 'headers', 'body', 'response', 'cookies', and 'filename' elements * - A WP_Error instance * - Boolean false to avoid short-circuiting the response * * Returning any other value may result in unexpected behavior. * * @since 2.9.0 * * @param false|array|WP_Error $response A preemptive return value of an HTTP request. Default false. * @param array $parsed_args HTTP request arguments. * @param string $url The request URL. */ $pre = apply_filters( 'pre_http_request', false, $parsed_args, $url ); if ( false !== $pre ) { return $pre; } if ( function_exists( 'wp_kses_bad_protocol' ) ) { if ( $parsed_args['reject_unsafe_urls'] ) { $url = wp_http_validate_url( $url ); } if ( $url ) { $url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) ); } } $parsed_url = parse_url( $url ); if ( empty( $url ) || empty( $parsed_url['scheme'] ) ) { $response = new WP_Error( 'http_request_failed', __( 'A valid URL was not provided.' ) ); /** This action is documented in wp-includes/class-wp-http.php */ do_action( 'http_api_debug', $response, 'response', 'WpOrg\Requests\Requests', $parsed_args, $url ); return $response; } if ( $this->block_request( $url ) ) { $response = new WP_Error( 'http_request_not_executed', __( 'User has blocked requests through HTTP.' ) ); /** This action is documented in wp-includes/class-wp-http.php */ do_action( 'http_api_debug', $response, 'response', 'WpOrg\Requests\Requests', $parsed_args, $url ); return $response; } // If we are streaming to a file but no filename was given drop it in the WP temp dir // and pick its name using the basename of the $url. if ( $parsed_args['stream'] ) { if ( empty( $parsed_args['filename'] ) ) { $parsed_args['filename'] = get_temp_dir() . basename( $url ); } // Force some settings if we are streaming to a file and check for existence // and perms of destination directory. $parsed_args['blocking'] = true; if ( ! wp_is_writable( dirname( $parsed_args['filename'] ) ) ) { $response = new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) ); /** This action is documented in wp-includes/class-wp-http.php */ do_action( 'http_api_debug', $response, 'response', 'WpOrg\Requests\Requests', $parsed_args, $url ); return $response; } } if ( is_null( $parsed_args['headers'] ) ) { $parsed_args['headers'] = array(); } // WP allows passing in headers as a string, weirdly. if ( ! is_array( $parsed_args['headers'] ) ) { $processed_headers = self::processHeaders( $parsed_args['headers'] ); $parsed_args['headers'] = $processed_headers['headers']; } // Setup arguments. $headers = $parsed_args['headers']; $data = $parsed_args['body']; $type = $parsed_args['method']; $options = array( 'timeout' => $parsed_args['timeout'], 'useragent' => $parsed_args['user-agent'], 'blocking' => $parsed_args['blocking'], 'hooks' => new WP_HTTP_Requests_Hooks( $url, $parsed_args ), ); // Ensure redirects follow browser behavior. $options['hooks']->register( 'requests.before_redirect', array( static::class, 'browser_redirect_compatibility' ) ); // Validate redirected URLs. if ( function_exists( 'wp_kses_bad_protocol' ) && $parsed_args['reject_unsafe_urls'] ) { $options['hooks']->register( 'requests.before_redirect', array( static::class, 'validate_redirects' ) ); } if ( $parsed_args['stream'] ) { $options['filename'] = $parsed_args['filename']; } if ( empty( $parsed_args['redirection'] ) ) { $options['follow_redirects'] = false; } else { $options['redirects'] = $parsed_args['redirection']; } // Use byte limit, if we can. if ( isset( $parsed_args['limit_response_size'] ) ) { $options['max_bytes'] = $parsed_args['limit_response_size']; } // If we've got cookies, use and convert them to WpOrg\Requests\Cookie. if ( ! empty( $parsed_args['cookies'] ) ) { $options['cookies'] = self::normalize_cookies( $parsed_args['cookies'] ); } // SSL certificate handling. if ( ! $parsed_args['sslverify'] ) { $options['verify'] = false; $options['verifyname'] = false; } else { $options['verify'] = $parsed_args['sslcertificates']; } // All non-GET/HEAD requests should put the arguments in the form body. if ( 'HEAD' !== $type && 'GET' !== $type ) { $options['data_format'] = 'body'; } /** * Filters whether SSL should be verified for non-local requests. * * @since 2.8.0 * @since 5.1.0 The `$url` parameter was added. * * @param bool|string $ssl_verify Boolean to control whether to verify the SSL connection * or path to an SSL certificate. * @param string $url The request URL. */ $options['verify'] = apply_filters( 'https_ssl_verify', $options['verify'], $url ); // Check for proxies. $proxy = new WP_HTTP_Proxy(); if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) { $options['proxy'] = new WpOrg\Requests\Proxy\Http( $proxy->host() . ':' . $proxy->port() ); if ( $proxy->use_authentication() ) { $options['proxy']->use_authentication = true; $options['proxy']->user = $proxy->username(); $options['proxy']->pass = $proxy->password(); } } // Avoid issues where mbstring.func_overload is enabled. mbstring_binary_safe_encoding(); try { $requests_response = WpOrg\Requests\Requests::request( $url, $headers, $data, $type, $options ); // Convert the response into an array. $http_response = new WP_HTTP_Requests_Response( $requests_response, $parsed_args['filename'] ); $response = $http_response->to_array(); // Add the original object to the array. $response['http_response'] = $http_response; } catch ( WpOrg\Requests\Exception $e ) { $response = new WP_Error( 'http_request_failed', $e->getMessage() ); } reset_mbstring_encoding(); /** * Fires after an HTTP API response is received and before the response is returned. * * @since 2.8.0 * * @param array|WP_Error $response HTTP response or WP_Error object. * @param string $context Context under which the hook is fired. * @param string $class HTTP transport used. * @param array $parsed_args HTTP request arguments. * @param string $url The request URL. */ do_action( 'http_api_debug', $response, 'response', 'WpOrg\Requests\Requests', $parsed_args, $url ); if ( is_wp_error( $response ) ) { return $response; } if ( ! $parsed_args['blocking'] ) { return array( 'headers' => array(), 'body' => '', 'response' => array( 'code' => false, 'message' => false, ), 'cookies' => array(), 'http_response' => null, ); } /** * Filters a successful HTTP API response immediately before the response is returned. * * @since 2.9.0 * * @param array $response HTTP response. * @param array $parsed_args HTTP request arguments. * @param string $url The request URL. */ return apply_filters( 'http_response', $response, $parsed_args, $url ); } /** * Normalizes cookies for using in Requests. * * @since 4.6.0 * * @param array $cookies Array of cookies to send with the request. * @return WpOrg\Requests\Cookie\Jar Cookie holder object. */ public static function normalize_cookies( $cookies ) { $cookie_jar = new WpOrg\Requests\Cookie\Jar(); foreach ( $cookies as $name => $value ) { if ( $value instanceof WP_Http_Cookie ) { $attributes = array_filter( $value->get_attributes(), static function ( $attr ) { return null !== $attr; } ); $cookie_jar[ $value->name ] = new WpOrg\Requests\Cookie( (string) $value->name, $value->value, $attributes, array( 'host-only' => $value->host_only ) ); } elseif ( is_scalar( $value ) ) { $cookie_jar[ $name ] = new WpOrg\Requests\Cookie( (string) $name, (string) $value ); } } return $cookie_jar; } /** * Match redirect behavior to browser handling. * * Changes 302 redirects from POST to GET to match browser handling. Per * RFC 7231, user agents can deviate from the strict reading of the * specification for compatibility purposes. * * @since 4.6.0 * * @param string $location URL to redirect to. * @param array $headers Headers for the redirect. * @param string|array $data Body to send with the request. * @param array $options Redirect request options. * @param WpOrg\Requests\Response $original Response object. */ public static function browser_redirect_compatibility( $location, $headers, $data, &$options, $original ) { // Browser compatibility. if ( 302 === $original->status_code ) { $options['type'] = WpOrg\Requests\Requests::GET; } } /** * Validate redirected URLs. * * @since 4.7.5 * * @throws WpOrg\Requests\Exception On unsuccessful URL validation. * @param string $location URL to redirect to. */ public static function validate_redirects( $location ) { if ( ! wp_http_validate_url( $location ) ) { throw new WpOrg\Requests\Exception( __( 'A valid URL was not provided.' ), 'wp_http.redirect_failed_validation' ); } } /** * Tests which transports are capable of supporting the request. * * @since 3.2.0 * @deprecated 6.4.0 Use WpOrg\Requests\Requests::get_transport_class() * @see WpOrg\Requests\Requests::get_transport_class() * * @param array $args Request arguments. * @param string $url URL to request. * @return string|false Class name for the first transport that claims to support the request. * False if no transport claims to support the request. */ public function _get_first_available_transport( $args, $url = null ) { $transports = array( 'curl', 'streams' ); /** * Filters which HTTP transports are available and in what order. * * @since 3.7.0 * @deprecated 6.4.0 Use WpOrg\Requests\Requests::get_transport_class() * * @param string[] $transports Array of HTTP transports to check. Default array contains * 'curl' and 'streams', in that order. * @param array $args HTTP request arguments. * @param string $url The URL to request. */ $request_order = apply_filters_deprecated( 'http_api_transports', array( $transports, $args, $url ), '6.4.0' ); // Loop over each transport on each HTTP request looking for one which will serve this request's needs. foreach ( $request_order as $transport ) { if ( in_array( $transport, $transports, true ) ) { $transport = ucfirst( $transport ); } $class = 'WP_Http_' . $transport; // Check to see if this transport is a possibility, calls the transport statically. if ( ! call_user_func( array( $class, 'test' ), $args, $url ) ) { continue; } return $class; } return false; } /** * Dispatches a HTTP request to a supporting transport. * * Tests each transport in order to find a transport which matches the request arguments. * Also caches the transport instance to be used later. * * The order for requests is cURL, and then PHP Streams. * * @since 3.2.0 * @deprecated 5.1.0 Use WP_Http::request() * @see WP_Http::request() * * @param string $url URL to request. * @param array $args Request arguments. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. * A WP_Error instance upon error. */ private function _dispatch_request( $url, $args ) { static $transports = array(); $class = $this->_get_first_available_transport( $args, $url ); if ( ! $class ) { return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) ); } // Transport claims to support request, instantiate it and give it a whirl. if ( empty( $transports[ $class ] ) ) { $transports[ $class ] = new $class(); } $response = $transports[ $class ]->request( $url, $args ); /** This action is documented in wp-includes/class-wp-http.php */ do_action( 'http_api_debug', $response, 'response', $class, $args, $url ); if ( is_wp_error( $response ) ) { return $response; } /** This filter is documented in wp-includes/class-wp-http.php */ return apply_filters( 'http_response', $response, $args, $url ); } /** * Uses the POST HTTP method. * * Used for sending data that is expected to be in the body. * * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. * A WP_Error instance upon error. See WP_Http::response() for details. */ public function post( $url, $args = array() ) { $defaults = array( 'method' => 'POST' ); $parsed_args = wp_parse_args( $args, $defaults ); return $this->request( $url, $parsed_args ); } /** * Uses the GET HTTP method. * * Used for sending data that is expected to be in the body. * * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. * A WP_Error instance upon error. See WP_Http::response() for details. */ public function get( $url, $args = array() ) { $defaults = array( 'method' => 'GET' ); $parsed_args = wp_parse_args( $args, $defaults ); return $this->request( $url, $parsed_args ); } /** * Uses the HEAD HTTP method. * * Used for sending data that is expected to be in the body. * * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. * A WP_Error instance upon error. See WP_Http::response() for details. */ public function head( $url, $args = array() ) { $defaults = array( 'method' => 'HEAD' ); $parsed_args = wp_parse_args( $args, $defaults ); return $this->request( $url, $parsed_args ); } /** * Parses the responses and splits the parts into headers and body. * * @since 2.7.0 * * @param string $response The full response string. * @return array { * Array with response headers and body. * * @type string $headers HTTP response headers. * @type string $body HTTP response body. * } */ public static function processResponse( $response ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid $response = explode( "\r\n\r\n", $response, 2 ); return array( 'headers' => $response[0], 'body' => isset( $response[1] ) ? $response[1] : '', ); } /** * Transforms header string into an array. * * @since 2.7.0 * * @param string|array $headers The original headers. If a string is passed, it will be converted * to an array. If an array is passed, then it is assumed to be * raw header data with numeric keys with the headers as the values. * No headers must be passed that were already processed. * @param string $url Optional. The URL that was requested. Default empty. * @return array { * Processed string headers. If duplicate headers are encountered, * then a numbered array is returned as the value of that header-key. * * @type array $response { * @type int $code The response status code. Default 0. * @type string $message The response message. Default empty. * } * @type array $newheaders The processed header data as a multidimensional array. * @type WP_Http_Cookie[] $cookies If the original headers contain the 'Set-Cookie' key, * an array containing `WP_Http_Cookie` objects is returned. * } */ public static function processHeaders( $headers, $url = '' ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid // Split headers, one per array element. if ( is_string( $headers ) ) { // Tolerate line terminator: CRLF = LF (RFC 2616 19.3). $headers = str_replace( "\r\n", "\n", $headers ); /* * Unfold folded header fields. LWS = [CRLF] 1*( SP | HT ) <US-ASCII SP, space (32)>, * <US-ASCII HT, horizontal-tab (9)> (RFC 2616 2.2). */ $headers = preg_replace( '/\n[ \t]/', ' ', $headers ); // Create the headers array. $headers = explode( "\n", $headers ); } $response = array( 'code' => 0, 'message' => '', ); /* * If a redirection has taken place, The headers for each page request may have been passed. * In this case, determine the final HTTP header and parse from there. */ for ( $i = count( $headers ) - 1; $i >= 0; $i-- ) { if ( ! empty( $headers[ $i ] ) && ! str_contains( $headers[ $i ], ':' ) ) { $headers = array_splice( $headers, $i ); break; } } $cookies = array(); $newheaders = array(); foreach ( (array) $headers as $tempheader ) { if ( empty( $tempheader ) ) { continue; } if ( ! str_contains( $tempheader, ':' ) ) { $stack = explode( ' ', $tempheader, 3 ); $stack[] = ''; list( , $response['code'], $response['message']) = $stack; continue; } list($key, $value) = explode( ':', $tempheader, 2 ); $key = strtolower( $key ); $value = trim( $value ); if ( isset( $newheaders[ $key ] ) ) { if ( ! is_array( $newheaders[ $key ] ) ) { $newheaders[ $key ] = array( $newheaders[ $key ] ); } $newheaders[ $key ][] = $value; } else { $newheaders[ $key ] = $value; } if ( 'set-cookie' === $key ) { $cookies[] = new WP_Http_Cookie( $value, $url ); } } // Cast the Response Code to an int. $response['code'] = (int) $response['code']; return array( 'response' => $response, 'headers' => $newheaders, 'cookies' => $cookies, ); } /** * Takes the arguments for a ::request() and checks for the cookie array. * * If it's found, then it upgrades any basic name => value pairs to WP_Http_Cookie instances, * which are each parsed into strings and added to the Cookie: header (within the arguments array). * Edits the array by reference. * * @since 2.8.0 * * @param array $r Full array of args passed into ::request() */ public static function buildCookieHeader( &$r ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid if ( ! empty( $r['cookies'] ) ) { // Upgrade any name => value cookie pairs to WP_HTTP_Cookie instances. foreach ( $r['cookies'] as $name => $value ) { if ( ! is_object( $value ) ) { $r['cookies'][ $name ] = new WP_Http_Cookie( array( 'name' => $name, 'value' => $value, ) ); } } $cookies_header = ''; foreach ( (array) $r['cookies'] as $cookie ) { $cookies_header .= $cookie->getHeaderValue() . '; '; } $cookies_header = substr( $cookies_header, 0, -2 ); $r['headers']['cookie'] = $cookies_header; } } /** * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification. * * Based off the HTTP http_encoding_dechunk function. * * @link https://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding. * * @since 2.7.0 * * @param string $body Body content. * @return string Chunked decoded body on success or raw body on failure. */ public static function chunkTransferDecode( $body ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid // The body is not chunked encoded or is malformed. if ( ! preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', trim( $body ) ) ) { return $body; } $parsed_body = ''; // We'll be altering $body, so need a backup in case of error. $body_original = $body; while ( true ) { $has_chunk = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $body, $match ); if ( ! $has_chunk || empty( $match[1] ) ) { return $body_original; } $length = hexdec( $match[1] ); $chunk_length = strlen( $match[0] ); // Parse out the chunk of data. $parsed_body .= substr( $body, $chunk_length, $length ); // Remove the chunk from the raw data. $body = substr( $body, $length + $chunk_length ); // End of the document. if ( '0' === trim( $body ) ) { return $parsed_body; } } } /** * Determines whether an HTTP API request to the given URL should be blocked. * * Those who are behind a proxy and want to prevent access to certain hosts may do so. This will * prevent plugins from working and core functionality, if you don't include `api.wordpress.org`. * * You block external URL requests by defining `WP_HTTP_BLOCK_EXTERNAL` as true in your `wp-config.php` * file and this will only allow localhost and your site to make requests. The constant * `WP_ACCESSIBLE_HOSTS` will allow additional hosts to go through for requests. The format of the * `WP_ACCESSIBLE_HOSTS` constant is a comma separated list of hostnames to allow, wildcard domains * are supported, eg `*.wordpress.org` will allow for all subdomains of `wordpress.org` to be contacted. * * @since 2.8.0 * * @link https://core.trac.wordpress.org/ticket/8927 Allow preventing external requests. * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_ACCESSIBLE_HOSTS * * @param string $uri URI of url. * @return bool True to block, false to allow. */ public function block_request( $uri ) { // We don't need to block requests, because nothing is blocked. if ( ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ! WP_HTTP_BLOCK_EXTERNAL ) { return false; } $check = parse_url( $uri ); if ( ! $check ) { return true; } $home = parse_url( get_option( 'siteurl' ) ); // Don't block requests back to ourselves by default. if ( 'localhost' === $check['host'] || ( isset( $home['host'] ) && $home['host'] === $check['host'] ) ) { /** * Filters whether to block local HTTP API requests. * * A local request is one to `localhost` or to the same host as the site itself. * * @since 2.8.0 * * @param bool $block Whether to block local requests. Default false. */ return apply_filters( 'block_local_requests', false ); } if ( ! defined( 'WP_ACCESSIBLE_HOSTS' ) ) { return true; } static $accessible_hosts = null; static $wildcard_regex = array(); if ( null === $accessible_hosts ) { $accessible_hosts = preg_split( '|,\s*|', WP_ACCESSIBLE_HOSTS ); if ( str_contains( WP_ACCESSIBLE_HOSTS, '*' ) ) { $wildcard_regex = array(); foreach ( $accessible_hosts as $host ) { $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) ); } $wildcard_regex = '/^(' . implode( '|', $wildcard_regex ) . ')$/i'; } } if ( ! empty( $wildcard_regex ) ) { return ! preg_match( $wildcard_regex, $check['host'] ); } else { return ! in_array( $check['host'], $accessible_hosts, true ); // Inverse logic, if it's in the array, then don't block it. } } /** * Used as a wrapper for PHP's parse_url() function that handles edgecases in < PHP 5.4.7. * * @deprecated 4.4.0 Use wp_parse_url() * @see wp_parse_url() * * @param string $url The URL to parse. * @return bool|array False on failure; Array of URL components on success; * See parse_url()'s return values. */ protected static function parse_url( $url ) { _deprecated_function( __METHOD__, '4.4.0', 'wp_parse_url()' ); return wp_parse_url( $url ); } /** * Converts a relative URL to an absolute URL relative to a given URL. * * If an Absolute URL is provided, no processing of that URL is done. * * @since 3.4.0 * * @param string $maybe_relative_path The URL which might be relative. * @param string $url The URL which $maybe_relative_path is relative to. * @return string An Absolute URL, in a failure condition where the URL cannot be parsed, the relative URL will be returned. */ public static function make_absolute_url( $maybe_relative_path, $url ) { if ( empty( $url ) ) { return $maybe_relative_path; } $url_parts = wp_parse_url( $url ); if ( ! $url_parts ) { return $maybe_relative_path; } $relative_url_parts = wp_parse_url( $maybe_relative_path ); if ( ! $relative_url_parts ) { return $maybe_relative_path; } // Check for a scheme on the 'relative' URL. if ( ! empty( $relative_url_parts['scheme'] ) ) { return $maybe_relative_path; } $absolute_path = $url_parts['scheme'] . '://'; // Schemeless URLs will make it this far, so we check for a host in the relative URL // and convert it to a protocol-URL. if ( isset( $relative_url_parts['host'] ) ) { $absolute_path .= $relative_url_parts['host']; if ( isset( $relative_url_parts['port'] ) ) { $absolute_path .= ':' . $relative_url_parts['port']; } } else { $absolute_path .= $url_parts['host']; if ( isset( $url_parts['port'] ) ) { $absolute_path .= ':' . $url_parts['port']; } } // Start off with the absolute URL path. $path = ! empty( $url_parts['path'] ) ? $url_parts['path'] : '/'; // If it's a root-relative path, then great. if ( ! empty( $relative_url_parts['path'] ) && '/' === $relative_url_parts['path'][0] ) { $path = $relative_url_parts['path']; // Else it's a relative path. } elseif ( ! empty( $relative_url_parts['path'] ) ) { // Strip off any file components from the absolute path. $path = substr( $path, 0, strrpos( $path, '/' ) + 1 ); // Build the new path. $path .= $relative_url_parts['path']; // Strip all /path/../ out of the path. while ( strpos( $path, '../' ) > 1 ) { $path = preg_replace( '![^/]+/\.\./!', '', $path ); } // Strip any final leading ../ from the path. $path = preg_replace( '!^/(\.\./)+!', '', $path ); } // Add the query string. if ( ! empty( $relative_url_parts['query'] ) ) { $path .= '?' . $relative_url_parts['query']; } // Add the fragment. if ( ! empty( $relative_url_parts['fragment'] ) ) { $path .= '#' . $relative_url_parts['fragment']; } return $absolute_path . '/' . ltrim( $path, '/' ); } /** * Handles an HTTP redirect and follows it if appropriate. * * @since 3.7.0 * * @param string $url The URL which was requested. * @param array $args The arguments which were used to make the request. * @param array $response The response of the HTTP request. * @return array|false|WP_Error An HTTP API response array if the redirect is successfully followed, * false if no redirect is present, or a WP_Error object if there's an error. */ public static function handle_redirects( $url, $args, $response ) { // If no redirects are present, or, redirects were not requested, perform no action. if ( ! isset( $response['headers']['location'] ) || 0 === $args['_redirection'] ) { return false; } // Only perform redirections on redirection http codes. if ( $response['response']['code'] > 399 || $response['response']['code'] < 300 ) { return false; } // Don't redirect if we've run out of redirects. if ( $args['redirection']-- <= 0 ) { return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); } $redirect_location = $response['headers']['location']; // If there were multiple Location headers, use the last header specified. if ( is_array( $redirect_location ) ) { $redirect_location = array_pop( $redirect_location ); } $redirect_location = self::make_absolute_url( $redirect_location, $url ); // POST requests should not POST to a redirected location. if ( 'POST' === $args['method'] ) { if ( in_array( $response['response']['code'], array( 302, 303 ), true ) ) { $args['method'] = 'GET'; } } // Include valid cookies in the redirect process. if ( ! empty( $response['cookies'] ) ) { foreach ( $response['cookies'] as $cookie ) { if ( $cookie->test( $redirect_location ) ) { $args['cookies'][] = $cookie; } } } return wp_remote_request( $redirect_location, $args ); } /** * Determines if a specified string represents an IP address or not. * * This function also detects the type of the IP address, returning either * '4' or '6' to represent an IPv4 and IPv6 address respectively. * This does not verify if the IP is a valid IP, only that it appears to be * an IP address. * * @link http://home.deds.nl/~aeron/regex/ for IPv6 regex. * * @since 3.7.0 * * @param string $maybe_ip A suspected IP address. * @return int|false Upon success, '4' or '6' to represent an IPv4 or IPv6 address, false upon failure. */ public static function is_ip_address( $maybe_ip ) { if ( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $maybe_ip ) ) { return 4; } if ( str_contains( $maybe_ip, ':' ) && preg_match( '/^(((?=.*(::))(?!.*\3.+\3))\3?|([\dA-F]{1,4}(\3|:\b|$)|\2))(?4){5}((?4){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i', trim( $maybe_ip, ' []' ) ) ) { return 6; } return false; } } https-detection-20260605124018.php 0000666 00000013341 15226762357 0012002 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-oembed.php 0000666 00000075666 15226762357 0010306 0 ustar 00 <?php /** * API for fetching the HTML to embed remote content based on a provided URL * * Used internally by the WP_Embed class, but is designed to be generic. * * @link https://developer.wordpress.org/advanced-administration/wordpress/oembed/ * @link http://oembed.com/ * * @package WordPress * @subpackage oEmbed */ /** * Core class used to implement oEmbed functionality. * * @since 2.9.0 */ #[AllowDynamicProperties] class WP_oEmbed { /** * A list of oEmbed providers. * * @since 2.9.0 * @var array */ public $providers = array(); /** * A list of an early oEmbed providers. * * @since 4.0.0 * @var array */ public static $early_providers = array(); /** * A list of private/protected methods, used for backward compatibility. * * @since 4.2.0 * @var array */ private $compat_methods = array( '_fetch_with_format', '_parse_json', '_parse_xml', '_parse_xml_body' ); /** * Constructor. * * @since 2.9.0 */ public function __construct() { $host = urlencode( home_url() ); $providers = array( '#https?://((m|www)\.)?youtube\.com/watch.*#i' => array( 'https://www.youtube.com/oembed', true ), '#https?://((m|www)\.)?youtube\.com/playlist.*#i' => array( 'https://www.youtube.com/oembed', true ), '#https?://((m|www)\.)?youtube\.com/shorts/*#i' => array( 'https://www.youtube.com/oembed', true ), '#https?://((m|www)\.)?youtube\.com/live/*#i' => array( 'https://www.youtube.com/oembed', true ), '#https?://youtu\.be/.*#i' => array( 'https://www.youtube.com/oembed', true ), '#https?://(.+\.)?vimeo\.com/.*#i' => array( 'https://vimeo.com/api/oembed.{format}', true ), '#https?://(www\.)?dailymotion\.com/.*#i' => array( 'https://www.dailymotion.com/services/oembed', true ), '#https?://dai\.ly/.*#i' => array( 'https://www.dailymotion.com/services/oembed', true ), '#https?://(www\.)?flickr\.com/.*#i' => array( 'https://www.flickr.com/services/oembed/', true ), '#https?://flic\.kr/.*#i' => array( 'https://www.flickr.com/services/oembed/', true ), '#https?://(.+\.)?smugmug\.com/.*#i' => array( 'https://api.smugmug.com/services/oembed/', true ), '#https?://(www\.)?scribd\.com/(doc|document)/.*#i' => array( 'https://www.scribd.com/services/oembed', true ), '#https?://wordpress\.tv/.*#i' => array( 'https://wordpress.tv/oembed/', true ), '#https?://(.+\.)?crowdsignal\.net/.*#i' => array( 'https://api.crowdsignal.com/oembed', true ), '#https?://(.+\.)?polldaddy\.com/.*#i' => array( 'https://api.crowdsignal.com/oembed', true ), '#https?://poll\.fm/.*#i' => array( 'https://api.crowdsignal.com/oembed', true ), '#https?://(.+\.)?survey\.fm/.*#i' => array( 'https://api.crowdsignal.com/oembed', true ), '#https?://(www\.)?twitter\.com/\w{1,15}/status(es)?/.*#i' => array( 'https://publish.twitter.com/oembed', true ), '#https?://(www\.)?twitter\.com/\w{1,15}$#i' => array( 'https://publish.twitter.com/oembed', true ), '#https?://(www\.)?twitter\.com/\w{1,15}/likes$#i' => array( 'https://publish.twitter.com/oembed', true ), '#https?://(www\.)?twitter\.com/\w{1,15}/lists/.*#i' => array( 'https://publish.twitter.com/oembed', true ), '#https?://(www\.)?twitter\.com/\w{1,15}/timelines/.*#i' => array( 'https://publish.twitter.com/oembed', true ), '#https?://(www\.)?twitter\.com/i/moments/.*#i' => array( 'https://publish.twitter.com/oembed', true ), '#https?://(www\.)?soundcloud\.com/.*#i' => array( 'https://soundcloud.com/oembed', true ), '#https?://(open|play)\.spotify\.com/.*#i' => array( 'https://embed.spotify.com/oembed/', true ), '#https?://(.+\.)?imgur\.com/.*#i' => array( 'https://api.imgur.com/oembed', true ), '#https?://(www\.)?issuu\.com/.+/docs/.+#i' => array( 'https://issuu.com/oembed_wp', true ), '#https?://(www\.)?mixcloud\.com/.*#i' => array( 'https://app.mixcloud.com/oembed/', true ), '#https?://(www\.|embed\.)?ted\.com/talks/.*#i' => array( 'https://www.ted.com/services/v1/oembed.{format}', true ), '#https?://(www\.)?(animoto|video214)\.com/play/.*#i' => array( 'https://animoto.com/oembeds/create', true ), '#https?://(.+)\.tumblr\.com/.*#i' => array( 'https://www.tumblr.com/oembed/1.0', true ), '#https?://(www\.)?kickstarter\.com/projects/.*#i' => array( 'https://www.kickstarter.com/services/oembed', true ), '#https?://kck\.st/.*#i' => array( 'https://www.kickstarter.com/services/oembed', true ), '#https?://cloudup\.com/.*#i' => array( 'https://cloudup.com/oembed', true ), '#https?://(www\.)?reverbnation\.com/.*#i' => array( 'https://www.reverbnation.com/oembed', true ), '#https?://videopress\.com/v/.*#' => array( 'https://public-api.wordpress.com/oembed/?for=' . $host, true ), '#https?://(www\.)?reddit\.com/r/[^/]+/comments/.*#i' => array( 'https://www.reddit.com/oembed', true ), '#https?://(www\.)?speakerdeck\.com/.*#i' => array( 'https://speakerdeck.com/oembed.{format}', true ), '#https?://([a-z0-9-]+\.)?amazon\.(com|com\.mx|com\.br|ca)/.*#i' => array( 'https://read.amazon.com/kp/api/oembed', true ), '#https?://([a-z0-9-]+\.)?amazon\.(co\.uk|de|fr|it|es|in|nl|ru)/.*#i' => array( 'https://read.amazon.co.uk/kp/api/oembed', true ), '#https?://([a-z0-9-]+\.)?amazon\.(co\.jp|com\.au)/.*#i' => array( 'https://read.amazon.com.au/kp/api/oembed', true ), '#https?://([a-z0-9-]+\.)?amazon\.cn/.*#i' => array( 'https://read.amazon.cn/kp/api/oembed', true ), '#https?://(www\.)?a\.co/.*#i' => array( 'https://read.amazon.com/kp/api/oembed', true ), '#https?://(www\.)?amzn\.to/.*#i' => array( 'https://read.amazon.com/kp/api/oembed', true ), '#https?://(www\.)?amzn\.eu/.*#i' => array( 'https://read.amazon.co.uk/kp/api/oembed', true ), '#https?://(www\.)?amzn\.in/.*#i' => array( 'https://read.amazon.in/kp/api/oembed', true ), '#https?://(www\.)?amzn\.asia/.*#i' => array( 'https://read.amazon.com.au/kp/api/oembed', true ), '#https?://(www\.)?z\.cn/.*#i' => array( 'https://read.amazon.cn/kp/api/oembed', true ), '#https?://www\.someecards\.com/.+-cards/.+#i' => array( 'https://www.someecards.com/v2/oembed/', true ), '#https?://www\.someecards\.com/usercards/viewcard/.+#i' => array( 'https://www.someecards.com/v2/oembed/', true ), '#https?://some\.ly\/.+#i' => array( 'https://www.someecards.com/v2/oembed/', true ), '#https?://(www\.)?tiktok\.com/.*/video/.*#i' => array( 'https://www.tiktok.com/oembed', true ), '#https?://(www\.)?tiktok\.com/@.*#i' => array( 'https://www.tiktok.com/oembed', true ), '#https?://([a-z]{2}|www)\.pinterest\.com(\.(au|mx))?/.*#i' => array( 'https://www.pinterest.com/oembed.json', true ), '#https?://(www\.)?wolframcloud\.com/obj/.+#i' => array( 'https://www.wolframcloud.com/oembed', true ), '#https?://pca\.st/.+#i' => array( 'https://pca.st/oembed.json', true ), '#https?://((play|www)\.)?anghami\.com/.*#i' => array( 'https://api.anghami.com/rest/v1/oembed.view', true ), '#https?://bsky.app/profile/.*/post/.*#i' => array( 'https://embed.bsky.app/oembed', true ), '#https?://(www\.)?canva\.com/design/.*/view.*#i' => array( 'https://canva.com/_oembed', true ), ); if ( ! empty( self::$early_providers['add'] ) ) { foreach ( self::$early_providers['add'] as $format => $data ) { $providers[ $format ] = $data; } } if ( ! empty( self::$early_providers['remove'] ) ) { foreach ( self::$early_providers['remove'] as $format ) { unset( $providers[ $format ] ); } } self::$early_providers = array(); /** * Filters the list of sanctioned oEmbed providers. * * Since WordPress 4.4, oEmbed discovery is enabled for all users and allows embedding of sanitized * iframes. The providers in this list are sanctioned, meaning they are trusted and allowed to * embed any content, such as iframes, videos, JavaScript, and arbitrary HTML. * * Supported providers: * * | Provider | Flavor | Since | * | ------------ | ----------------------------------------- | ------- | * | Dailymotion | dailymotion.com | 2.9.0 | * | Flickr | flickr.com | 2.9.0 | * | Scribd | scribd.com | 2.9.0 | * | Vimeo | vimeo.com | 2.9.0 | * | WordPress.tv | wordpress.tv | 2.9.0 | * | YouTube | youtube.com/watch | 2.9.0 | * | Crowdsignal | polldaddy.com | 3.0.0 | * | SmugMug | smugmug.com | 3.0.0 | * | YouTube | youtu.be | 3.0.0 | * | Twitter | twitter.com | 3.4.0 | * | SoundCloud | soundcloud.com | 3.5.0 | * | Dailymotion | dai.ly | 3.6.0 | * | Flickr | flic.kr | 3.6.0 | * | Spotify | spotify.com | 3.6.0 | * | Imgur | imgur.com | 3.9.0 | * | Animoto | animoto.com | 4.0.0 | * | Animoto | video214.com | 4.0.0 | * | Issuu | issuu.com | 4.0.0 | * | Mixcloud | mixcloud.com | 4.0.0 | * | Crowdsignal | poll.fm | 4.0.0 | * | TED | ted.com | 4.0.0 | * | YouTube | youtube.com/playlist | 4.0.0 | * | Tumblr | tumblr.com | 4.2.0 | * | Kickstarter | kickstarter.com | 4.2.0 | * | Kickstarter | kck.st | 4.2.0 | * | Cloudup | cloudup.com | 4.3.0 | * | ReverbNation | reverbnation.com | 4.4.0 | * | VideoPress | videopress.com | 4.4.0 | * | Reddit | reddit.com | 4.4.0 | * | Speaker Deck | speakerdeck.com | 4.4.0 | * | Twitter | twitter.com/timelines | 4.5.0 | * | Twitter | twitter.com/moments | 4.5.0 | * | Twitter | twitter.com/user | 4.7.0 | * | Twitter | twitter.com/likes | 4.7.0 | * | Twitter | twitter.com/lists | 4.7.0 | * | Screencast | screencast.com | 4.8.0 | * | Amazon | amazon.com (com.mx, com.br, ca) | 4.9.0 | * | Amazon | amazon.de (fr, it, es, in, nl, ru, co.uk) | 4.9.0 | * | Amazon | amazon.co.jp (com.au) | 4.9.0 | * | Amazon | amazon.cn | 4.9.0 | * | Amazon | a.co | 4.9.0 | * | Amazon | amzn.to (eu, in, asia) | 4.9.0 | * | Amazon | z.cn | 4.9.0 | * | Someecards | someecards.com | 4.9.0 | * | Someecards | some.ly | 4.9.0 | * | Crowdsignal | survey.fm | 5.1.0 | * | TikTok | tiktok.com | 5.4.0 | * | Pinterest | pinterest.com | 5.9.0 | * | WolframCloud | wolframcloud.com | 5.9.0 | * | Pocket Casts | pocketcasts.com | 6.1.0 | * | Crowdsignal | crowdsignal.net | 6.2.0 | * | Anghami | anghami.com | 6.3.0 | * | Bluesky | bsky.app | 6.6.0 | * | Canva | canva.com | 6.8.0 | * * No longer supported providers: * * | Provider | Flavor | Since | Removed | * | ------------ | -------------------- | --------- | --------- | * | Qik | qik.com | 2.9.0 | 3.9.0 | * | Viddler | viddler.com | 2.9.0 | 4.0.0 | * | Revision3 | revision3.com | 2.9.0 | 4.2.0 | * | Blip | blip.tv | 2.9.0 | 4.4.0 | * | Rdio | rdio.com | 3.6.0 | 4.4.1 | * | Rdio | rd.io | 3.6.0 | 4.4.1 | * | Vine | vine.co | 4.1.0 | 4.9.0 | * | Photobucket | photobucket.com | 2.9.0 | 5.1.0 | * | Funny or Die | funnyordie.com | 3.0.0 | 5.1.0 | * | CollegeHumor | collegehumor.com | 4.0.0 | 5.3.1 | * | Hulu | hulu.com | 2.9.0 | 5.5.0 | * | Instagram | instagram.com | 3.5.0 | 5.5.2 | * | Instagram | instagr.am | 3.5.0 | 5.5.2 | * | Instagram TV | instagram.com | 5.1.0 | 5.5.2 | * | Instagram TV | instagr.am | 5.1.0 | 5.5.2 | * | Facebook | facebook.com | 4.7.0 | 5.5.2 | * | Meetup.com | meetup.com | 3.9.0 | 6.0.1 | * | Meetup.com | meetu.ps | 3.9.0 | 6.0.1 | * | SlideShare | slideshare.net | 3.5.0 | 6.6.0 | * | Screencast | screencast.com | 4.8.0 | 6.8.2 | * * @see wp_oembed_add_provider() * * @since 2.9.0 * * @param array[] $providers An array of arrays containing data about popular oEmbed providers. */ $this->providers = apply_filters( 'oembed_providers', $providers ); // Fix any embeds that contain new lines in the middle of the HTML which breaks wpautop(). add_filter( 'oembed_dataparse', array( $this, '_strip_newlines' ), 10, 3 ); } /** * Exposes private/protected methods for backward compatibility. * * @since 4.0.0 * * @param string $name Method to call. * @param array $arguments Arguments to pass when calling. * @return mixed|false Return value of the callback, false otherwise. */ public function __call( $name, $arguments ) { if ( in_array( $name, $this->compat_methods, true ) ) { return $this->$name( ...$arguments ); } return false; } /** * Takes a URL and returns the corresponding oEmbed provider's URL, if there is one. * * @since 4.0.0 * * @see WP_oEmbed::discover() * * @param string $url The URL to the content. * @param string|array $args { * Optional. Additional provider arguments. Default empty. * * @type bool $discover Optional. Determines whether to attempt to discover link tags * at the given URL for an oEmbed provider when the provider URL * is not found in the built-in providers list. Default true. * } * @return string|false The oEmbed provider URL on success, false on failure. */ public function get_provider( $url, $args = '' ) { $args = wp_parse_args( $args ); $provider = false; if ( ! isset( $args['discover'] ) ) { $args['discover'] = true; } foreach ( $this->providers as $matchmask => $data ) { list( $providerurl, $regex ) = $data; // Turn the asterisk-type provider URLs into regex. if ( ! $regex ) { $matchmask = '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) ) . '#i'; $matchmask = preg_replace( '|^#http\\\://|', '#https?\://', $matchmask ); } if ( preg_match( $matchmask, $url ) ) { $provider = str_replace( '{format}', 'json', $providerurl ); // JSON is easier to deal with than XML. break; } } if ( ! $provider && $args['discover'] ) { $provider = $this->discover( $url ); } return $provider; } /** * Adds an oEmbed provider. * * The provider is added just-in-time when wp_oembed_add_provider() is called before * the {@see 'plugins_loaded'} hook. * * The just-in-time addition is for the benefit of the {@see 'oembed_providers'} filter. * * @since 4.0.0 * * @see wp_oembed_add_provider() * * @param string $format Format of URL that this provider can handle. You can use * asterisks as wildcards. * @param string $provider The URL to the oEmbed provider.. * @param bool $regex Optional. Whether the $format parameter is in a regex format. * Default false. */ public static function _add_provider_early( $format, $provider, $regex = false ) { if ( empty( self::$early_providers['add'] ) ) { self::$early_providers['add'] = array(); } self::$early_providers['add'][ $format ] = array( $provider, $regex ); } /** * Removes an oEmbed provider. * * The provider is removed just-in-time when wp_oembed_remove_provider() is called before * the {@see 'plugins_loaded'} hook. * * The just-in-time removal is for the benefit of the {@see 'oembed_providers'} filter. * * @since 4.0.0 * * @see wp_oembed_remove_provider() * * @param string $format The format of URL that this provider can handle. You can use * asterisks as wildcards. */ public static function _remove_provider_early( $format ) { if ( empty( self::$early_providers['remove'] ) ) { self::$early_providers['remove'] = array(); } self::$early_providers['remove'][] = $format; } /** * Takes a URL and attempts to return the oEmbed data. * * @see WP_oEmbed::fetch() * * @since 4.8.0 * * @param string $url The URL to the content that should be attempted to be embedded. * @param string|array $args Optional. Additional arguments for retrieving embed HTML. * See wp_oembed_get() for accepted arguments. Default empty. * @return object|false The result in the form of an object on success, false on failure. */ public function get_data( $url, $args = '' ) { $args = wp_parse_args( $args ); $provider = $this->get_provider( $url, $args ); if ( ! $provider ) { return false; } $data = $this->fetch( $provider, $url, $args ); if ( false === $data ) { return false; } return $data; } /** * The do-it-all function that takes a URL and attempts to return the HTML. * * @see WP_oEmbed::fetch() * @see WP_oEmbed::data2html() * * @since 2.9.0 * * @param string $url The URL to the content that should be attempted to be embedded. * @param string|array $args Optional. Additional arguments for retrieving embed HTML. * See wp_oembed_get() for accepted arguments. Default empty. * @return string|false The UNSANITIZED (and potentially unsafe) HTML that should be used to embed * on success, false on failure. */ public function get_html( $url, $args = '' ) { /** * Filters the oEmbed result before any HTTP requests are made. * * This allows one to short-circuit the default logic, perhaps by * replacing it with a routine that is more optimal for your setup. * * Returning a non-null value from the filter will effectively short-circuit retrieval * and return the passed value instead. * * @since 4.5.3 * * @param null|string $result The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. * Default null to continue retrieving the result. * @param string $url The URL to the content that should be attempted to be embedded. * @param string|array $args Optional. Additional arguments for retrieving embed HTML. * See wp_oembed_get() for accepted arguments. Default empty. */ $pre = apply_filters( 'pre_oembed_result', null, $url, $args ); if ( null !== $pre ) { return $pre; } $data = $this->get_data( $url, $args ); if ( false === $data ) { return false; } /** * Filters the HTML returned by the oEmbed provider. * * @since 2.9.0 * * @param string|false $data The returned oEmbed HTML (false if unsafe). * @param string $url URL of the content to be embedded. * @param string|array $args Optional. Additional arguments for retrieving embed HTML. * See wp_oembed_get() for accepted arguments. Default empty. */ return apply_filters( 'oembed_result', $this->data2html( $data, $url ), $url, $args ); } /** * Attempts to discover link tags at the given URL for an oEmbed provider. * * @since 2.9.0 * * @param string $url The URL that should be inspected for discovery `<link>` tags. * @return string|false The oEmbed provider URL on success, false on failure. */ public function discover( $url ) { $providers = array(); $args = array( 'limit_response_size' => 153600, // 150 KB ); /** * Filters oEmbed remote get arguments. * * @since 4.0.0 * * @see WP_Http::request() * * @param array $args oEmbed remote get arguments. * @param string $url URL to be inspected. */ $args = apply_filters( 'oembed_remote_get_args', $args, $url ); // Fetch URL content. $request = wp_safe_remote_get( $url, $args ); $html = wp_remote_retrieve_body( $request ); if ( $html ) { /** * Filters the link types that contain oEmbed provider URLs. * * @since 2.9.0 * * @param string[] $format Array of oEmbed link types. Accepts 'application/json+oembed', * 'text/xml+oembed', and 'application/xml+oembed' (incorrect, * used by at least Vimeo). */ $linktypes = apply_filters( 'oembed_linktypes', array( 'application/json+oembed' => 'json', 'text/xml+oembed' => 'xml', 'application/xml+oembed' => 'xml', ) ); // Strip <body>. $html_head_end = stripos( $html, '</head>' ); if ( $html_head_end ) { $html = substr( $html, 0, $html_head_end ); } // Do a quick check. $tagfound = false; foreach ( $linktypes as $linktype => $format ) { if ( stripos( $html, $linktype ) ) { $tagfound = true; break; } } if ( $tagfound && preg_match_all( '#<link([^<>]+)/?>#iU', $html, $links ) ) { foreach ( $links[1] as $link ) { $atts = shortcode_parse_atts( $link ); if ( ! empty( $atts['type'] ) && ! empty( $linktypes[ $atts['type'] ] ) && ! empty( $atts['href'] ) ) { $providers[ $linktypes[ $atts['type'] ] ] = htmlspecialchars_decode( $atts['href'] ); // Stop here if it's JSON (that's all we need). if ( 'json' === $linktypes[ $atts['type'] ] ) { break; } } } } } // JSON is preferred to XML. if ( ! empty( $providers['json'] ) ) { return $providers['json']; } elseif ( ! empty( $providers['xml'] ) ) { return $providers['xml']; } else { return false; } } /** * Connects to an oEmbed provider and returns the result. * * @since 2.9.0 * * @param string $provider The URL to the oEmbed provider. * @param string $url The URL to the content that is desired to be embedded. * @param string|array $args Optional. Additional arguments for retrieving embed HTML. * See wp_oembed_get() for accepted arguments. Default empty. * @return object|false The result in the form of an object on success, false on failure. */ public function fetch( $provider, $url, $args = '' ) { $args = wp_parse_args( $args, wp_embed_defaults( $url ) ); $provider = add_query_arg( 'maxwidth', (int) $args['width'], $provider ); $provider = add_query_arg( 'maxheight', (int) $args['height'], $provider ); $provider = add_query_arg( 'url', urlencode( $url ), $provider ); $provider = add_query_arg( 'dnt', 1, $provider ); /** * Filters the oEmbed URL to be fetched. * * @since 2.9.0 * @since 4.9.0 The `dnt` (Do Not Track) query parameter was added to all oEmbed provider URLs. * * @param string $provider URL of the oEmbed provider. * @param string $url URL of the content to be embedded. * @param array $args Optional. Additional arguments for retrieving embed HTML. * See wp_oembed_get() for accepted arguments. Default empty. */ $provider = apply_filters( 'oembed_fetch_url', $provider, $url, $args ); foreach ( array( 'json', 'xml' ) as $format ) { $result = $this->_fetch_with_format( $provider, $format ); if ( is_wp_error( $result ) && 'not-implemented' === $result->get_error_code() ) { continue; } return ( $result && ! is_wp_error( $result ) ) ? $result : false; } return false; } /** * Fetches result from an oEmbed provider for a specific format and complete provider URL * * @since 3.0.0 * * @param string $provider_url_with_args URL to the provider with full arguments list (url, maxheight, etc.) * @param string $format Format to use. * @return object|false|WP_Error The result in the form of an object on success, false on failure. */ private function _fetch_with_format( $provider_url_with_args, $format ) { $provider_url_with_args = add_query_arg( 'format', $format, $provider_url_with_args ); /** This filter is documented in wp-includes/class-wp-oembed.php */ $args = apply_filters( 'oembed_remote_get_args', array(), $provider_url_with_args ); $response = wp_safe_remote_get( $provider_url_with_args, $args ); if ( 501 === wp_remote_retrieve_response_code( $response ) ) { return new WP_Error( 'not-implemented' ); } $body = wp_remote_retrieve_body( $response ); if ( ! $body ) { return false; } $parse_method = "_parse_$format"; return $this->$parse_method( $body ); } /** * Parses a json response body. * * @since 3.0.0 * * @param string $response_body * @return object|false */ private function _parse_json( $response_body ) { $data = json_decode( trim( $response_body ) ); return ( $data && is_object( $data ) ) ? $data : false; } /** * Parses an XML response body. * * @since 3.0.0 * * @param string $response_body * @return object|false */ private function _parse_xml( $response_body ) { if ( ! function_exists( 'libxml_disable_entity_loader' ) ) { return false; } if ( PHP_VERSION_ID < 80000 ) { /* * This function has been deprecated in PHP 8.0 because in libxml 2.9.0, external entity loading * is disabled by default, so this function is no longer needed to protect against XXE attacks. */ $loader = libxml_disable_entity_loader( true ); } $errors = libxml_use_internal_errors( true ); $return = $this->_parse_xml_body( $response_body ); libxml_use_internal_errors( $errors ); if ( PHP_VERSION_ID < 80000 && isset( $loader ) ) { // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.libxml_disable_entity_loaderDeprecated libxml_disable_entity_loader( $loader ); } return $return; } /** * Serves as a helper function for parsing an XML response body. * * @since 3.6.0 * * @param string $response_body * @return stdClass|false */ private function _parse_xml_body( $response_body ) { if ( ! function_exists( 'simplexml_import_dom' ) || ! class_exists( 'DOMDocument', false ) ) { return false; } $dom = new DOMDocument(); $success = $dom->loadXML( $response_body ); if ( ! $success ) { return false; } if ( isset( $dom->doctype ) ) { return false; } foreach ( $dom->childNodes as $child ) { if ( XML_DOCUMENT_TYPE_NODE === $child->nodeType ) { return false; } } $xml = simplexml_import_dom( $dom ); if ( ! $xml ) { return false; } $return = new stdClass(); foreach ( $xml as $key => $value ) { $return->$key = (string) $value; } return $return; } /** * Converts a data object from WP_oEmbed::fetch() and returns the HTML. * * @since 2.9.0 * * @param object $data A data object result from an oEmbed provider. * @param string $url The URL to the content that is desired to be embedded. * @return string|false The HTML needed to embed on success, false on failure. */ public function data2html( $data, $url ) { if ( ! is_object( $data ) || empty( $data->type ) ) { return false; } $return = false; switch ( $data->type ) { case 'photo': if ( empty( $data->url ) || empty( $data->width ) || empty( $data->height ) ) { break; } if ( ! is_string( $data->url ) || ! is_numeric( $data->width ) || ! is_numeric( $data->height ) ) { break; } $title = ! empty( $data->title ) && is_string( $data->title ) ? $data->title : ''; $return = '<a href="' . esc_url( $url ) . '"><img src="' . esc_url( $data->url ) . '" alt="' . esc_attr( $title ) . '" width="' . esc_attr( $data->width ) . '" height="' . esc_attr( $data->height ) . '" /></a>'; break; case 'video': case 'rich': if ( ! empty( $data->html ) && is_string( $data->html ) ) { $return = $data->html; } break; case 'link': if ( ! empty( $data->title ) && is_string( $data->title ) ) { $return = '<a href="' . esc_url( $url ) . '">' . esc_html( $data->title ) . '</a>'; } break; default: $return = false; } /** * Filters the returned oEmbed HTML. * * Use this filter to add support for custom data types, or to filter the result. * * @since 2.9.0 * * @param string|false $return The returned oEmbed HTML, or false on failure. * @param object $data A data object result from an oEmbed provider. * @param string $url The URL of the content to be embedded. */ return apply_filters( 'oembed_dataparse', $return, $data, $url ); } /** * Strips any new lines from the HTML. * * @since 2.9.0 as strip_scribd_newlines() * @since 3.0.0 * * @param string|false $html Existing HTML. * @param object $data Data object from WP_oEmbed::data2html() * @param string $url The original URL passed to oEmbed. * @return string|false Possibly modified $html. */ public function _strip_newlines( $html, $data, $url ) { if ( ! str_contains( $html, "\n" ) ) { return $html; } $count = 1; $found = array(); $token = '__PRE__'; $search = array( "\t", "\n", "\r", ' ' ); $replace = array( '__TAB__', '__NL__', '__CR__', '__SPACE__' ); $tokenized = str_replace( $search, $replace, $html ); preg_match_all( '#(<pre[^>]*>.+?</pre>)#i', $tokenized, $matches, PREG_SET_ORDER ); foreach ( $matches as $i => $match ) { $tag_html = str_replace( $replace, $search, $match[0] ); $tag_token = $token . $i; $found[ $tag_token ] = $tag_html; $html = str_replace( $tag_html, $tag_token, $html, $count ); } $replaced = str_replace( $replace, $search, $html ); $stripped = str_replace( array( "\r\n", "\n" ), '', $replaced ); $pre = array_values( $found ); $tokens = array_keys( $found ); return str_replace( $tokens, $pre, $stripped ); } } class-wp-customize-date-time-control.php 0000666 00000022346 15226762357 0014405 0 ustar 00 <?php /** * Customize API: WP_Customize_Date_Time_Control class * * @package WordPress * @subpackage Customize * @since 4.9.0 */ /** * Customize Date Time Control class. * * @since 4.9.0 * * @see WP_Customize_Control */ class WP_Customize_Date_Time_Control extends WP_Customize_Control { /** * Customize control type. * * @since 4.9.0 * @var string */ public $type = 'date_time'; /** * Minimum Year. * * @since 4.9.0 * @var int */ public $min_year = 1000; /** * Maximum Year. * * @since 4.9.0 * @var int */ public $max_year = 9999; /** * Allow past date, if set to false user can only select future date. * * @since 4.9.0 * @var bool */ public $allow_past_date = true; /** * Whether hours, minutes, and meridian should be shown. * * @since 4.9.0 * @var bool */ public $include_time = true; /** * If set to false the control will appear in 24 hour format, * the value will still be saved in Y-m-d H:i:s format. * * @since 4.9.0 * @var bool */ public $twelve_hour_format = true; /** * Don't render the control's content - it's rendered with a JS template. * * @since 4.9.0 */ public function render_content() {} /** * Export data to JS. * * @since 4.9.0 * @return array */ public function json() { $data = parent::json(); $data['maxYear'] = (int) $this->max_year; $data['minYear'] = (int) $this->min_year; $data['allowPastDate'] = (bool) $this->allow_past_date; $data['twelveHourFormat'] = (bool) $this->twelve_hour_format; $data['includeTime'] = (bool) $this->include_time; return $data; } /** * Renders a JS template for the content of date time control. * * @since 4.9.0 */ public function content_template() { $data = array_merge( $this->json(), $this->get_month_choices() ); $timezone_info = $this->get_timezone_info(); $date_format = get_option( 'date_format' ); $date_format = preg_replace( '/(?<!\\\\)[Yyo]/', '%1$s', $date_format ); $date_format = preg_replace( '/(?<!\\\\)[FmMn]/', '%2$s', $date_format ); $date_format = preg_replace( '/(?<!\\\\)[jd]/', '%3$s', $date_format ); // Fallback to ISO date format if year, month, or day are missing from the date format. if ( 1 !== substr_count( $date_format, '%1$s' ) || 1 !== substr_count( $date_format, '%2$s' ) || 1 !== substr_count( $date_format, '%3$s' ) ) { $date_format = '%1$s-%2$s-%3$s'; } ?> <# _.defaults( data, <?php echo wp_json_encode( $data ); ?> ); #> <# var idPrefix = _.uniqueId( 'el' ) + '-'; #> <# if ( data.label ) { #> <span class="customize-control-title"> {{ data.label }} </span> <# } #> <div class="customize-control-notifications-container"></div> <# if ( data.description ) { #> <span class="description customize-control-description">{{ data.description }}</span> <# } #> <div class="date-time-fields {{ data.includeTime ? 'includes-time' : '' }}"> <fieldset class="day-row"> <legend class="title-day {{ ! data.includeTime ? 'screen-reader-text' : '' }}"><?php esc_html_e( 'Date' ); ?></legend> <div class="day-fields clear"> <?php ob_start(); ?> <label for="{{ idPrefix }}date-time-month" class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ esc_html_e( 'Month' ); ?> </label> <select id="{{ idPrefix }}date-time-month" class="date-input month" data-component="month"> <# _.each( data.month_choices, function( choice ) { if ( _.isObject( choice ) && ! _.isUndefined( choice.text ) && ! _.isUndefined( choice.value ) ) { text = choice.text; value = choice.value; } #> <option value="{{ value }}" > {{ text }} </option> <# } ); #> </select> <?php $month_field = trim( ob_get_clean() ); ?> <?php ob_start(); ?> <label for="{{ idPrefix }}date-time-day" class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ esc_html_e( 'Day' ); ?> </label> <input id="{{ idPrefix }}date-time-day" type="number" size="2" autocomplete="off" class="date-input day tiny-text" data-component="day" min="1" max="31" /> <?php $day_field = trim( ob_get_clean() ); ?> <?php ob_start(); ?> <label for="{{ idPrefix }}date-time-year" class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ esc_html_e( 'Year' ); ?> </label> <input id="{{ idPrefix }}date-time-year" type="number" size="4" autocomplete="off" class="date-input year tiny-text" data-component="year" min="{{ data.minYear }}" max="{{ data.maxYear }}"> <?php $year_field = trim( ob_get_clean() ); ?> <?php printf( $date_format, $year_field, $month_field, $day_field ); ?> </div> </fieldset> <# if ( data.includeTime ) { #> <fieldset class="time-row clear"> <legend class="title-time"><?php esc_html_e( 'Time' ); ?></legend> <div class="time-fields clear"> <label for="{{ idPrefix }}date-time-hour" class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ esc_html_e( 'Hour' ); ?> </label> <# var maxHour = data.twelveHourFormat ? 12 : 23; #> <# var minHour = data.twelveHourFormat ? 1 : 0; #> <input id="{{ idPrefix }}date-time-hour" type="number" size="2" autocomplete="off" class="date-input hour tiny-text" data-component="hour" min="{{ minHour }}" max="{{ maxHour }}"> : <label for="{{ idPrefix }}date-time-minute" class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ esc_html_e( 'Minute' ); ?> </label> <input id="{{ idPrefix }}date-time-minute" type="number" size="2" autocomplete="off" class="date-input minute tiny-text" data-component="minute" min="0" max="59"> <# if ( data.twelveHourFormat ) { #> <label for="{{ idPrefix }}date-time-meridian" class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ esc_html_e( 'Meridian' ); ?> </label> <select id="{{ idPrefix }}date-time-meridian" class="date-input meridian" data-component="meridian"> <option value="am"><?php esc_html_e( 'AM' ); ?></option> <option value="pm"><?php esc_html_e( 'PM' ); ?></option> </select> <# } #> <p><?php echo $timezone_info['description']; ?></p> </div> </fieldset> <# } #> </div> <?php } /** * Generate options for the month Select. * * Based on touch_time(). * * @since 4.9.0 * * @see touch_time() * * @global WP_Locale $wp_locale WordPress date and time locale object. * * @return array */ public function get_month_choices() { global $wp_locale; $months = array(); for ( $i = 1; $i < 13; $i++ ) { $month_text = $wp_locale->get_month_abbrev( $wp_locale->get_month( $i ) ); /* translators: 1: Month number (01, 02, etc.), 2: Month abbreviation. */ $months[ $i ]['text'] = sprintf( __( '%1$s-%2$s' ), $i, $month_text ); $months[ $i ]['value'] = $i; } return array( 'month_choices' => $months, ); } /** * Get timezone info. * * @since 4.9.0 * * @return array { * Timezone info. All properties are optional. * * @type string $abbr Timezone abbreviation. Examples: PST or CEST. * @type string $description Human-readable timezone description as HTML. * } */ public function get_timezone_info() { $tz_string = get_option( 'timezone_string' ); $timezone_info = array(); if ( $tz_string ) { try { $tz = new DateTimeZone( $tz_string ); } catch ( Exception $e ) { $tz = ''; } if ( $tz ) { $now = new DateTime( 'now', $tz ); $formatted_gmt_offset = $this->format_gmt_offset( $tz->getOffset( $now ) / HOUR_IN_SECONDS ); $tz_name = str_replace( '_', ' ', $tz->getName() ); $timezone_info['abbr'] = $now->format( 'T' ); $timezone_info['description'] = sprintf( /* translators: 1: Timezone name, 2: Timezone abbreviation, 3: UTC abbreviation and offset, 4: UTC offset. */ __( 'Your timezone is set to %1$s (%2$s), currently %3$s (Coordinated Universal Time %4$s).' ), $tz_name, '<abbr>' . $timezone_info['abbr'] . '</abbr>', '<abbr>UTC</abbr>' . $formatted_gmt_offset, $formatted_gmt_offset ); } else { $timezone_info['description'] = ''; } } else { $formatted_gmt_offset = $this->format_gmt_offset( (int) get_option( 'gmt_offset', 0 ) ); $timezone_info['description'] = sprintf( /* translators: 1: UTC abbreviation and offset, 2: UTC offset. */ __( 'Your timezone is set to %1$s (Coordinated Universal Time %2$s).' ), '<abbr>UTC</abbr>' . $formatted_gmt_offset, $formatted_gmt_offset ); } return $timezone_info; } /** * Format GMT Offset. * * @since 4.9.0 * * @see wp_timezone_choice() * * @param float $offset Offset in hours. * @return string Formatted offset. */ public function format_gmt_offset( $offset ) { if ( 0 <= $offset ) { $formatted_offset = '+' . (string) $offset; } else { $formatted_offset = (string) $offset; } $formatted_offset = str_replace( array( '.25', '.5', '.75' ), array( ':15', ':30', ':45' ), $formatted_offset ); return $formatted_offset; } } https-migration-20260606072910-20260621203656.php 0000666 00000011205 15226762357 0013404 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260605070331.php 0000666 00000011205 15226762357 0012010 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260605043510.php 0000666 00000011205 15226762357 0012007 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-theme.php 0000666 00000200422 15226762357 0010131 0 ustar 00 <?php /** * WP_Theme Class * * @package WordPress * @subpackage Theme * @since 3.4.0 */ #[AllowDynamicProperties] final class WP_Theme implements ArrayAccess { /** * Whether the theme has been marked as updateable. * * @since 4.4.0 * @var bool * * @see WP_MS_Themes_List_Table */ public $update = false; /** * Headers for style.css files. * * @since 3.4.0 * @since 5.4.0 Added `Requires at least` and `Requires PHP` headers. * @since 6.1.0 Added `Update URI` header. * @var string[] */ private static $file_headers = array( 'Name' => 'Theme Name', 'ThemeURI' => 'Theme URI', 'Description' => 'Description', 'Author' => 'Author', 'AuthorURI' => 'Author URI', 'Version' => 'Version', 'Template' => 'Template', 'Status' => 'Status', 'Tags' => 'Tags', 'TextDomain' => 'Text Domain', 'DomainPath' => 'Domain Path', 'RequiresWP' => 'Requires at least', 'RequiresPHP' => 'Requires PHP', 'UpdateURI' => 'Update URI', ); /** * Default themes. * * @since 3.4.0 * @since 3.5.0 Added the Twenty Twelve theme. * @since 3.6.0 Added the Twenty Thirteen theme. * @since 3.8.0 Added the Twenty Fourteen theme. * @since 4.1.0 Added the Twenty Fifteen theme. * @since 4.4.0 Added the Twenty Sixteen theme. * @since 4.7.0 Added the Twenty Seventeen theme. * @since 5.0.0 Added the Twenty Nineteen theme. * @since 5.3.0 Added the Twenty Twenty theme. * @since 5.6.0 Added the Twenty Twenty-One theme. * @since 5.9.0 Added the Twenty Twenty-Two theme. * @since 6.1.0 Added the Twenty Twenty-Three theme. * @since 6.4.0 Added the Twenty Twenty-Four theme. * @since 6.7.0 Added the Twenty Twenty-Five theme. * @var string[] */ private static $default_themes = array( 'classic' => 'WordPress Classic', 'default' => 'WordPress Default', 'twentyten' => 'Twenty Ten', 'twentyeleven' => 'Twenty Eleven', 'twentytwelve' => 'Twenty Twelve', 'twentythirteen' => 'Twenty Thirteen', 'twentyfourteen' => 'Twenty Fourteen', 'twentyfifteen' => 'Twenty Fifteen', 'twentysixteen' => 'Twenty Sixteen', 'twentyseventeen' => 'Twenty Seventeen', 'twentynineteen' => 'Twenty Nineteen', 'twentytwenty' => 'Twenty Twenty', 'twentytwentyone' => 'Twenty Twenty-One', 'twentytwentytwo' => 'Twenty Twenty-Two', 'twentytwentythree' => 'Twenty Twenty-Three', 'twentytwentyfour' => 'Twenty Twenty-Four', 'twentytwentyfive' => 'Twenty Twenty-Five', ); /** * Renamed theme tags. * * @since 3.8.0 * @var string[] */ private static $tag_map = array( 'fixed-width' => 'fixed-layout', 'flexible-width' => 'fluid-layout', ); /** * Absolute path to the theme root, usually wp-content/themes * * @since 3.4.0 * @var string */ private $theme_root; /** * Header data from the theme's style.css file. * * @since 3.4.0 * @var array */ private $headers = array(); /** * Header data from the theme's style.css file after being sanitized. * * @since 3.4.0 * @var array */ private $headers_sanitized; /** * Is this theme a block theme. * * @since 6.2.0 * @var bool */ private $block_theme; /** * Header name from the theme's style.css after being translated. * * Cached due to sorting functions running over the translated name. * * @since 3.4.0 * @var string */ private $name_translated; /** * Errors encountered when initializing the theme. * * @since 3.4.0 * @var WP_Error */ private $errors; /** * The directory name of the theme's files, inside the theme root. * * In the case of a child theme, this is directory name of the child theme. * Otherwise, 'stylesheet' is the same as 'template'. * * @since 3.4.0 * @var string */ private $stylesheet; /** * The directory name of the theme's files, inside the theme root. * * In the case of a child theme, this is the directory name of the parent theme. * Otherwise, 'template' is the same as 'stylesheet'. * * @since 3.4.0 * @var string */ private $template; /** * A reference to the parent theme, in the case of a child theme. * * @since 3.4.0 * @var WP_Theme */ private $parent; /** * URL to the theme root, usually an absolute URL to wp-content/themes * * @since 3.4.0 * @var string */ private $theme_root_uri; /** * Flag for whether the theme's textdomain is loaded. * * @since 3.4.0 * @var bool */ private $textdomain_loaded; /** * Stores an md5 hash of the theme root, to function as the cache key. * * @since 3.4.0 * @var string */ private $cache_hash; /** * Block template folders. * * @since 6.4.0 * @var string[] */ private $block_template_folders; /** * Default values for template folders. * * @since 6.4.0 * @var string[] */ private $default_template_folders = array( 'wp_template' => 'templates', 'wp_template_part' => 'parts', ); /** * Flag for whether the themes cache bucket should be persistently cached. * * Default is false. Can be set with the {@see 'wp_cache_themes_persistently'} filter. * * @since 3.4.0 * @var bool */ private static $persistently_cache; /** * Expiration time for the themes cache bucket. * * By default the bucket is not cached, so this value is useless. * * @since 3.4.0 * @var bool */ private static $cache_expiration = 1800; /** * Constructor for WP_Theme. * * @since 3.4.0 * * @global string[] $wp_theme_directories * * @param string $theme_dir Directory of the theme within the theme_root. * @param string $theme_root Theme root. * @param WP_Theme|null $_child If this theme is a parent theme, the child may be passed for validation purposes. */ public function __construct( $theme_dir, $theme_root, $_child = null ) { global $wp_theme_directories; // Initialize caching on first run. if ( ! isset( self::$persistently_cache ) ) { /** This action is documented in wp-includes/theme.php */ self::$persistently_cache = apply_filters( 'wp_cache_themes_persistently', false, 'WP_Theme' ); if ( self::$persistently_cache ) { wp_cache_add_global_groups( 'themes' ); if ( is_int( self::$persistently_cache ) ) { self::$cache_expiration = self::$persistently_cache; } } else { wp_cache_add_non_persistent_groups( 'themes' ); } } // Handle a numeric theme directory as a string. $theme_dir = (string) $theme_dir; $this->theme_root = $theme_root; $this->stylesheet = $theme_dir; // Correct a situation where the theme is 'some-directory/some-theme' but 'some-directory' was passed in as part of the theme root instead. if ( ! in_array( $theme_root, (array) $wp_theme_directories, true ) && in_array( dirname( $theme_root ), (array) $wp_theme_directories, true ) ) { $this->stylesheet = basename( $this->theme_root ) . '/' . $this->stylesheet; $this->theme_root = dirname( $theme_root ); } $this->cache_hash = md5( $this->theme_root . '/' . $this->stylesheet ); $theme_file = $this->stylesheet . '/style.css'; $cache = $this->cache_get( 'theme' ); if ( is_array( $cache ) ) { foreach ( array( 'block_template_folders', 'block_theme', 'errors', 'headers', 'template' ) as $key ) { if ( isset( $cache[ $key ] ) ) { $this->$key = $cache[ $key ]; } } if ( $this->errors ) { return; } if ( isset( $cache['theme_root_template'] ) ) { $theme_root_template = $cache['theme_root_template']; } } elseif ( ! file_exists( $this->theme_root . '/' . $theme_file ) ) { $this->headers['Name'] = $this->stylesheet; if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet ) ) { $this->errors = new WP_Error( 'theme_not_found', sprintf( /* translators: %s: Theme directory name. */ __( 'The theme directory "%s" does not exist.' ), esc_html( $this->stylesheet ) ) ); } else { $this->errors = new WP_Error( 'theme_no_stylesheet', __( 'Stylesheet is missing.' ) ); } $this->template = $this->stylesheet; $this->block_theme = false; $this->block_template_folders = $this->default_template_folders; $this->cache_add( 'theme', array( 'block_template_folders' => $this->block_template_folders, 'block_theme' => $this->block_theme, 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template, ) ); if ( ! file_exists( $this->theme_root ) ) { // Don't cache this one. $this->errors->add( 'theme_root_missing', __( '<strong>Error:</strong> The themes directory is either empty or does not exist. Please check your installation.' ) ); } return; } elseif ( ! is_readable( $this->theme_root . '/' . $theme_file ) ) { $this->headers['Name'] = $this->stylesheet; $this->errors = new WP_Error( 'theme_stylesheet_not_readable', __( 'Stylesheet is not readable.' ) ); $this->template = $this->stylesheet; $this->block_theme = false; $this->block_template_folders = $this->default_template_folders; $this->cache_add( 'theme', array( 'block_template_folders' => $this->block_template_folders, 'block_theme' => $this->block_theme, 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template, ) ); return; } else { $this->headers = get_file_data( $this->theme_root . '/' . $theme_file, self::$file_headers, 'theme' ); /* * Default themes always trump their pretenders. * Properly identify default themes that are inside a directory within wp-content/themes. */ $default_theme_slug = array_search( $this->headers['Name'], self::$default_themes, true ); if ( $default_theme_slug ) { if ( basename( $this->stylesheet ) !== $default_theme_slug ) { $this->headers['Name'] .= '/' . $this->stylesheet; } } } if ( ! $this->template && $this->stylesheet === $this->headers['Template'] ) { $this->errors = new WP_Error( 'theme_child_invalid', sprintf( /* translators: %s: Template. */ __( 'The theme defines itself as its parent theme. Please check the %s header.' ), '<code>Template</code>' ) ); $this->cache_add( 'theme', array( 'block_template_folders' => $this->get_block_template_folders(), 'block_theme' => $this->is_block_theme(), 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, ) ); return; } // (If template is set from cache [and there are no errors], we know it's good.) if ( ! $this->template ) { $this->template = $this->headers['Template']; } if ( ! $this->template ) { $this->template = $this->stylesheet; $theme_path = $this->theme_root . '/' . $this->stylesheet; if ( ! $this->is_block_theme() && ! file_exists( $theme_path . '/index.php' ) ) { $error_message = sprintf( /* translators: 1: templates/index.html, 2: index.php, 3: Documentation URL, 4: Template, 5: style.css */ __( 'Template is missing. Standalone themes need to have a %1$s or %2$s template file. <a href="%3$s">Child themes</a> need to have a %4$s header in the %5$s stylesheet.' ), '<code>templates/index.html</code>', '<code>index.php</code>', __( 'https://developer.wordpress.org/themes/advanced-topics/child-themes/' ), '<code>Template</code>', '<code>style.css</code>' ); $this->errors = new WP_Error( 'theme_no_index', $error_message ); $this->cache_add( 'theme', array( 'block_template_folders' => $this->get_block_template_folders(), 'block_theme' => $this->block_theme, 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template, ) ); return; } } // If we got our data from cache, we can assume that 'template' is pointing to the right place. if ( ! is_array( $cache ) && $this->template !== $this->stylesheet && ! file_exists( $this->theme_root . '/' . $this->template . '/index.php' ) ) { /* * If we're in a directory of themes inside /themes, look for the parent nearby. * wp-content/themes/directory-of-themes/* */ $parent_dir = dirname( $this->stylesheet ); $directories = search_theme_directories(); if ( '.' !== $parent_dir && file_exists( $this->theme_root . '/' . $parent_dir . '/' . $this->template . '/index.php' ) ) { $this->template = $parent_dir . '/' . $this->template; } elseif ( $directories && isset( $directories[ $this->template ] ) ) { /* * Look for the template in the search_theme_directories() results, in case it is in another theme root. * We don't look into directories of themes, just the theme root. */ $theme_root_template = $directories[ $this->template ]['theme_root']; } else { // Parent theme is missing. $this->errors = new WP_Error( 'theme_no_parent', sprintf( /* translators: %s: Theme directory name. */ __( 'The parent theme is missing. Please install the "%s" parent theme.' ), esc_html( $this->template ) ) ); $this->cache_add( 'theme', array( 'block_template_folders' => $this->get_block_template_folders(), 'block_theme' => $this->is_block_theme(), 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template, ) ); $this->parent = new WP_Theme( $this->template, $this->theme_root, $this ); return; } } // Set the parent, if we're a child theme. if ( $this->template !== $this->stylesheet ) { // If we are a parent, then there is a problem. Only two generations allowed! Cancel things out. if ( $_child instanceof WP_Theme && $_child->template === $this->stylesheet ) { $_child->parent = null; $_child->errors = new WP_Error( 'theme_parent_invalid', sprintf( /* translators: %s: Theme directory name. */ __( 'The "%s" theme is not a valid parent theme.' ), esc_html( $_child->template ) ) ); $_child->cache_add( 'theme', array( 'block_template_folders' => $_child->get_block_template_folders(), 'block_theme' => $_child->is_block_theme(), 'headers' => $_child->headers, 'errors' => $_child->errors, 'stylesheet' => $_child->stylesheet, 'template' => $_child->template, ) ); // The two themes actually reference each other with the Template header. if ( $_child->stylesheet === $this->template ) { $this->errors = new WP_Error( 'theme_parent_invalid', sprintf( /* translators: %s: Theme directory name. */ __( 'The "%s" theme is not a valid parent theme.' ), esc_html( $this->template ) ) ); $this->cache_add( 'theme', array( 'block_template_folders' => $this->get_block_template_folders(), 'block_theme' => $this->is_block_theme(), 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template, ) ); } return; } // Set the parent. Pass the current instance so we can do the checks above and assess errors. $this->parent = new WP_Theme( $this->template, isset( $theme_root_template ) ? $theme_root_template : $this->theme_root, $this ); } if ( wp_paused_themes()->get( $this->stylesheet ) && ( ! is_wp_error( $this->errors ) || ! isset( $this->errors->errors['theme_paused'] ) ) ) { $this->errors = new WP_Error( 'theme_paused', __( 'This theme failed to load properly and was paused within the admin backend.' ) ); } // We're good. If we didn't retrieve from cache, set it. if ( ! is_array( $cache ) ) { $cache = array( 'block_theme' => $this->is_block_theme(), 'block_template_folders' => $this->get_block_template_folders(), 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template, ); // If the parent theme is in another root, we'll want to cache this. Avoids an entire branch of filesystem calls above. if ( isset( $theme_root_template ) ) { $cache['theme_root_template'] = $theme_root_template; } $this->cache_add( 'theme', $cache ); } } /** * When converting the object to a string, the theme name is returned. * * @since 3.4.0 * * @return string Theme name, ready for display (translated) */ public function __toString() { return (string) $this->display( 'Name' ); } /** * __isset() magic method for properties formerly returned by current_theme_info() * * @since 3.4.0 * * @param string $offset Property to check if set. * @return bool Whether the given property is set. */ public function __isset( $offset ) { static $properties = array( 'name', 'title', 'version', 'parent_theme', 'template_dir', 'stylesheet_dir', 'template', 'stylesheet', 'screenshot', 'description', 'author', 'tags', 'theme_root', 'theme_root_uri', ); return in_array( $offset, $properties, true ); } /** * __get() magic method for properties formerly returned by current_theme_info() * * @since 3.4.0 * * @param string $offset Property to get. * @return mixed Property value. */ public function __get( $offset ) { switch ( $offset ) { case 'name': case 'title': return $this->get( 'Name' ); case 'version': return $this->get( 'Version' ); case 'parent_theme': return $this->parent() ? $this->parent()->get( 'Name' ) : ''; case 'template_dir': return $this->get_template_directory(); case 'stylesheet_dir': return $this->get_stylesheet_directory(); case 'template': return $this->get_template(); case 'stylesheet': return $this->get_stylesheet(); case 'screenshot': return $this->get_screenshot( 'relative' ); // 'author' and 'description' did not previously return translated data. case 'description': return $this->display( 'Description' ); case 'author': return $this->display( 'Author' ); case 'tags': return $this->get( 'Tags' ); case 'theme_root': return $this->get_theme_root(); case 'theme_root_uri': return $this->get_theme_root_uri(); // For cases where the array was converted to an object. default: return $this->offsetGet( $offset ); } } /** * Method to implement ArrayAccess for keys formerly returned by get_themes() * * @since 3.4.0 * * @param mixed $offset * @param mixed $value */ #[ReturnTypeWillChange] public function offsetSet( $offset, $value ) {} /** * Method to implement ArrayAccess for keys formerly returned by get_themes() * * @since 3.4.0 * * @param mixed $offset */ #[ReturnTypeWillChange] public function offsetUnset( $offset ) {} /** * Method to implement ArrayAccess for keys formerly returned by get_themes() * * @since 3.4.0 * * @param mixed $offset * @return bool */ #[ReturnTypeWillChange] public function offsetExists( $offset ) { static $keys = array( 'Name', 'Version', 'Status', 'Title', 'Author', 'Author Name', 'Author URI', 'Description', 'Template', 'Stylesheet', 'Template Files', 'Stylesheet Files', 'Template Dir', 'Stylesheet Dir', 'Screenshot', 'Tags', 'Theme Root', 'Theme Root URI', 'Parent Theme', ); return in_array( $offset, $keys, true ); } /** * Method to implement ArrayAccess for keys formerly returned by get_themes(). * * Author, Author Name, Author URI, and Description did not previously return * translated data. We are doing so now as it is safe to do. However, as * Name and Title could have been used as the key for get_themes(), both remain * untranslated for back compatibility. This means that ['Name'] is not ideal, * and care should be taken to use `$theme::display( 'Name' )` to get a properly * translated header. * * @since 3.4.0 * * @param mixed $offset * @return mixed */ #[ReturnTypeWillChange] public function offsetGet( $offset ) { switch ( $offset ) { case 'Name': case 'Title': /* * See note above about using translated data. get() is not ideal. * It is only for backward compatibility. Use display(). */ return $this->get( 'Name' ); case 'Author': return $this->display( 'Author' ); case 'Author Name': return $this->display( 'Author', false ); case 'Author URI': return $this->display( 'AuthorURI' ); case 'Description': return $this->display( 'Description' ); case 'Version': case 'Status': return $this->get( $offset ); case 'Template': return $this->get_template(); case 'Stylesheet': return $this->get_stylesheet(); case 'Template Files': return $this->get_files( 'php', 1, true ); case 'Stylesheet Files': return $this->get_files( 'css', 0, false ); case 'Template Dir': return $this->get_template_directory(); case 'Stylesheet Dir': return $this->get_stylesheet_directory(); case 'Screenshot': return $this->get_screenshot( 'relative' ); case 'Tags': return $this->get( 'Tags' ); case 'Theme Root': return $this->get_theme_root(); case 'Theme Root URI': return $this->get_theme_root_uri(); case 'Parent Theme': return $this->parent() ? $this->parent()->get( 'Name' ) : ''; default: return null; } } /** * Returns errors property. * * @since 3.4.0 * * @return WP_Error|false WP_Error if there are errors, or false. */ public function errors() { return is_wp_error( $this->errors ) ? $this->errors : false; } /** * Determines whether the theme exists. * * A theme with errors exists. A theme with the error of 'theme_not_found', * meaning that the theme's directory was not found, does not exist. * * @since 3.4.0 * * @return bool Whether the theme exists. */ public function exists() { return ! ( $this->errors() && in_array( 'theme_not_found', $this->errors()->get_error_codes(), true ) ); } /** * Returns reference to the parent theme. * * @since 3.4.0 * * @return WP_Theme|false Parent theme, or false if the active theme is not a child theme. */ public function parent() { return isset( $this->parent ) ? $this->parent : false; } /** * Perform reinitialization tasks. * * Prevents a callback from being injected during unserialization of an object. */ public function __wakeup() { if ( $this->parent && ! $this->parent instanceof self ) { throw new UnexpectedValueException(); } if ( $this->headers && ! is_array( $this->headers ) ) { throw new UnexpectedValueException(); } foreach ( $this->headers as $value ) { if ( ! is_string( $value ) ) { throw new UnexpectedValueException(); } } $this->headers_sanitized = array(); } /** * Adds theme data to cache. * * Cache entries keyed by the theme and the type of data. * * @since 3.4.0 * * @param string $key Type of data to store (theme, screenshot, headers, post_templates) * @param array|string $data Data to store * @return bool Return value from wp_cache_add() */ private function cache_add( $key, $data ) { return wp_cache_add( $key . '-' . $this->cache_hash, $data, 'themes', self::$cache_expiration ); } /** * Gets theme data from cache. * * Cache entries are keyed by the theme and the type of data. * * @since 3.4.0 * * @param string $key Type of data to retrieve (theme, screenshot, headers, post_templates) * @return mixed Retrieved data */ private function cache_get( $key ) { return wp_cache_get( $key . '-' . $this->cache_hash, 'themes' ); } /** * Clears the cache for the theme. * * @since 3.4.0 */ public function cache_delete() { foreach ( array( 'theme', 'screenshot', 'headers', 'post_templates' ) as $key ) { wp_cache_delete( $key . '-' . $this->cache_hash, 'themes' ); } $this->template = null; $this->textdomain_loaded = null; $this->theme_root_uri = null; $this->parent = null; $this->errors = null; $this->headers_sanitized = null; $this->name_translated = null; $this->block_theme = null; $this->block_template_folders = null; $this->headers = array(); $this->__construct( $this->stylesheet, $this->theme_root ); $this->delete_pattern_cache(); } /** * Gets a raw, unformatted theme header. * * The header is sanitized, but is not translated, and is not marked up for display. * To get a theme header for display, use the display() method. * * Use the get_template() method, not the 'Template' header, for finding the template. * The 'Template' header is only good for what was written in the style.css, while * get_template() takes into account where WordPress actually located the theme and * whether it is actually valid. * * @since 3.4.0 * * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags. * @return string|array|false String or array (for Tags header) on success, false on failure. */ public function get( $header ) { if ( ! isset( $this->headers[ $header ] ) ) { return false; } if ( ! isset( $this->headers_sanitized ) ) { $this->headers_sanitized = $this->cache_get( 'headers' ); if ( ! is_array( $this->headers_sanitized ) ) { $this->headers_sanitized = array(); } } if ( isset( $this->headers_sanitized[ $header ] ) ) { return $this->headers_sanitized[ $header ]; } // If themes are a persistent group, sanitize everything and cache it. One cache add is better than many cache sets. if ( self::$persistently_cache ) { foreach ( array_keys( $this->headers ) as $_header ) { $this->headers_sanitized[ $_header ] = $this->sanitize_header( $_header, $this->headers[ $_header ] ); } $this->cache_add( 'headers', $this->headers_sanitized ); } else { $this->headers_sanitized[ $header ] = $this->sanitize_header( $header, $this->headers[ $header ] ); } return $this->headers_sanitized[ $header ]; } /** * Gets a theme header, formatted and translated for display. * * @since 3.4.0 * * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags. * @param bool $markup Optional. Whether to mark up the header. Defaults to true. * @param bool $translate Optional. Whether to translate the header. Defaults to true. * @return string|array|false Processed header. An array for Tags if `$markup` is false, string otherwise. * False on failure. */ public function display( $header, $markup = true, $translate = true ) { $value = $this->get( $header ); if ( false === $value ) { return false; } if ( $translate && ( empty( $value ) || ! $this->load_textdomain() ) ) { $translate = false; } if ( $translate ) { $value = $this->translate_header( $header, $value ); } if ( $markup ) { $value = $this->markup_header( $header, $value, $translate ); } return $value; } /** * Sanitizes a theme header. * * @since 3.4.0 * @since 5.4.0 Added support for `Requires at least` and `Requires PHP` headers. * @since 6.1.0 Added support for `Update URI` header. * * @param string $header Theme header. Accepts 'Name', 'Description', 'Author', 'Version', * 'ThemeURI', 'AuthorURI', 'Status', 'Tags', 'RequiresWP', 'RequiresPHP', * 'UpdateURI'. * @param string $value Value to sanitize. * @return string|array An array for Tags header, string otherwise. */ private function sanitize_header( $header, $value ) { switch ( $header ) { case 'Status': if ( ! $value ) { $value = 'publish'; break; } // Fall through otherwise. case 'Name': static $header_tags = array( 'abbr' => array( 'title' => true ), 'acronym' => array( 'title' => true ), 'code' => true, 'em' => true, 'strong' => true, ); $value = wp_kses( $value, $header_tags ); break; case 'Author': // There shouldn't be anchor tags in Author, but some themes like to be challenging. case 'Description': static $header_tags_with_a = array( 'a' => array( 'href' => true, 'title' => true, ), 'abbr' => array( 'title' => true ), 'acronym' => array( 'title' => true ), 'code' => true, 'em' => true, 'strong' => true, ); $value = wp_kses( $value, $header_tags_with_a ); break; case 'ThemeURI': case 'AuthorURI': $value = sanitize_url( $value ); break; case 'Tags': $value = array_filter( array_map( 'trim', explode( ',', strip_tags( $value ) ) ) ); break; case 'Version': case 'RequiresWP': case 'RequiresPHP': case 'UpdateURI': $value = strip_tags( $value ); break; } return $value; } /** * Marks up a theme header. * * @since 3.4.0 * * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags. * @param string|array $value Value to mark up. An array for Tags header, string otherwise. * @param string $translate Whether the header has been translated. * @return string Value, marked up. */ private function markup_header( $header, $value, $translate ) { switch ( $header ) { case 'Name': if ( empty( $value ) ) { $value = esc_html( $this->get_stylesheet() ); } break; case 'Description': $value = wptexturize( $value ); break; case 'Author': if ( $this->get( 'AuthorURI' ) ) { $value = sprintf( '<a href="%1$s">%2$s</a>', $this->display( 'AuthorURI', true, $translate ), $value ); } elseif ( ! $value ) { $value = __( 'Anonymous' ); } break; case 'Tags': static $comma = null; if ( ! isset( $comma ) ) { $comma = wp_get_list_item_separator(); } $value = implode( $comma, $value ); break; case 'ThemeURI': case 'AuthorURI': $value = esc_url( $value ); break; } return $value; } /** * Translates a theme header. * * @since 3.4.0 * * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags. * @param string|array $value Value to translate. An array for Tags header, string otherwise. * @return string|array Translated value. An array for Tags header, string otherwise. */ private function translate_header( $header, $value ) { switch ( $header ) { case 'Name': // Cached for sorting reasons. if ( isset( $this->name_translated ) ) { return $this->name_translated; } // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain $this->name_translated = translate( $value, $this->get( 'TextDomain' ) ); return $this->name_translated; case 'Tags': if ( empty( $value ) || ! function_exists( 'get_theme_feature_list' ) ) { return $value; } static $tags_list; if ( ! isset( $tags_list ) ) { $tags_list = array( // As of 4.6, deprecated tags which are only used to provide translation for older themes. 'black' => __( 'Black' ), 'blue' => __( 'Blue' ), 'brown' => __( 'Brown' ), 'gray' => __( 'Gray' ), 'green' => __( 'Green' ), 'orange' => __( 'Orange' ), 'pink' => __( 'Pink' ), 'purple' => __( 'Purple' ), 'red' => __( 'Red' ), 'silver' => __( 'Silver' ), 'tan' => __( 'Tan' ), 'white' => __( 'White' ), 'yellow' => __( 'Yellow' ), 'dark' => _x( 'Dark', 'color scheme' ), 'light' => _x( 'Light', 'color scheme' ), 'fixed-layout' => __( 'Fixed Layout' ), 'fluid-layout' => __( 'Fluid Layout' ), 'responsive-layout' => __( 'Responsive Layout' ), 'blavatar' => __( 'Blavatar' ), 'photoblogging' => __( 'Photoblogging' ), 'seasonal' => __( 'Seasonal' ), ); $feature_list = get_theme_feature_list( false ); // No API. foreach ( $feature_list as $tags ) { $tags_list += $tags; } } foreach ( $value as &$tag ) { if ( isset( $tags_list[ $tag ] ) ) { $tag = $tags_list[ $tag ]; } elseif ( isset( self::$tag_map[ $tag ] ) ) { $tag = $tags_list[ self::$tag_map[ $tag ] ]; } } return $value; default: // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain $value = translate( $value, $this->get( 'TextDomain' ) ); } return $value; } /** * Returns the directory name of the theme's "stylesheet" files, inside the theme root. * * In the case of a child theme, this is directory name of the child theme. * Otherwise, get_stylesheet() is the same as get_template(). * * @since 3.4.0 * * @return string Stylesheet */ public function get_stylesheet() { return $this->stylesheet; } /** * Returns the directory name of the theme's "template" files, inside the theme root. * * In the case of a child theme, this is the directory name of the parent theme. * Otherwise, the get_template() is the same as get_stylesheet(). * * @since 3.4.0 * * @return string Template */ public function get_template() { return $this->template; } /** * Returns the absolute path to the directory of a theme's "stylesheet" files. * * In the case of a child theme, this is the absolute path to the directory * of the child theme's files. * * @since 3.4.0 * * @return string Absolute path of the stylesheet directory. */ public function get_stylesheet_directory() { if ( $this->errors() && in_array( 'theme_root_missing', $this->errors()->get_error_codes(), true ) ) { return ''; } return $this->theme_root . '/' . $this->stylesheet; } /** * Returns the absolute path to the directory of a theme's "template" files. * * In the case of a child theme, this is the absolute path to the directory * of the parent theme's files. * * @since 3.4.0 * * @return string Absolute path of the template directory. */ public function get_template_directory() { if ( $this->parent() ) { $theme_root = $this->parent()->theme_root; } else { $theme_root = $this->theme_root; } return $theme_root . '/' . $this->template; } /** * Returns the URL to the directory of a theme's "stylesheet" files. * * In the case of a child theme, this is the URL to the directory of the * child theme's files. * * @since 3.4.0 * * @return string URL to the stylesheet directory. */ public function get_stylesheet_directory_uri() { return $this->get_theme_root_uri() . '/' . str_replace( '%2F', '/', rawurlencode( $this->stylesheet ) ); } /** * Returns the URL to the directory of a theme's "template" files. * * In the case of a child theme, this is the URL to the directory of the * parent theme's files. * * @since 3.4.0 * * @return string URL to the template directory. */ public function get_template_directory_uri() { if ( $this->parent() ) { $theme_root_uri = $this->parent()->get_theme_root_uri(); } else { $theme_root_uri = $this->get_theme_root_uri(); } return $theme_root_uri . '/' . str_replace( '%2F', '/', rawurlencode( $this->template ) ); } /** * Returns the absolute path to the directory of the theme root. * * This is typically the absolute path to wp-content/themes. * * @since 3.4.0 * * @return string Theme root. */ public function get_theme_root() { return $this->theme_root; } /** * Returns the URL to the directory of the theme root. * * This is typically the absolute URL to wp-content/themes. This forms the basis * for all other URLs returned by WP_Theme, so we pass it to the public function * get_theme_root_uri() and allow it to run the {@see 'theme_root_uri'} filter. * * @since 3.4.0 * * @return string Theme root URI. */ public function get_theme_root_uri() { if ( ! isset( $this->theme_root_uri ) ) { $this->theme_root_uri = get_theme_root_uri( $this->stylesheet, $this->theme_root ); } return $this->theme_root_uri; } /** * Returns the main screenshot file for the theme. * * The main screenshot is called screenshot.png. gif and jpg extensions are also allowed. * * Screenshots for a theme must be in the stylesheet directory. (In the case of child * themes, parent theme screenshots are not inherited.) * * @since 3.4.0 * * @param string $uri Type of URL to return, either 'relative' or an absolute URI. Defaults to absolute URI. * @return string|false Screenshot file. False if the theme does not have a screenshot. */ public function get_screenshot( $uri = 'uri' ) { $screenshot = $this->cache_get( 'screenshot' ); if ( $screenshot ) { if ( 'relative' === $uri ) { return $screenshot; } return $this->get_stylesheet_directory_uri() . '/' . $screenshot; } elseif ( 0 === $screenshot ) { return false; } foreach ( array( 'png', 'gif', 'jpg', 'jpeg', 'webp', 'avif' ) as $ext ) { if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) { $this->cache_add( 'screenshot', 'screenshot.' . $ext ); if ( 'relative' === $uri ) { return 'screenshot.' . $ext; } return $this->get_stylesheet_directory_uri() . '/' . 'screenshot.' . $ext; } } $this->cache_add( 'screenshot', 0 ); return false; } /** * Returns files in the theme's directory. * * @since 3.4.0 * * @param string[]|string $type Optional. Array of extensions to find, string of a single extension, * or null for all extensions. Default null. * @param int $depth Optional. How deep to search for files. Defaults to a flat scan (0 depth). * -1 depth is infinite. * @param bool $search_parent Optional. Whether to return parent files. Default false. * @return string[] Array of files, keyed by the path to the file relative to the theme's directory, with the values * being absolute paths. */ public function get_files( $type = null, $depth = 0, $search_parent = false ) { $files = (array) self::scandir( $this->get_stylesheet_directory(), $type, $depth ); if ( $search_parent && $this->parent() ) { $files += (array) self::scandir( $this->get_template_directory(), $type, $depth ); } return array_filter( $files ); } /** * Returns the theme's post templates. * * @since 4.7.0 * @since 5.8.0 Include block templates. * * @return array[] Array of page template arrays, keyed by post type and filename, * with the value of the translated header name. */ public function get_post_templates() { // If you screw up your active theme and we invalidate your parent, most things still work. Let it slide. if ( $this->errors() && $this->errors()->get_error_codes() !== array( 'theme_parent_invalid' ) ) { return array(); } $post_templates = $this->cache_get( 'post_templates' ); if ( ! is_array( $post_templates ) ) { $post_templates = array(); $files = (array) $this->get_files( 'php', 1, true ); foreach ( $files as $file => $full_path ) { if ( ! preg_match( '|Template Name:(.*)$|mi', file_get_contents( $full_path ), $header ) ) { continue; } $types = array( 'page' ); if ( preg_match( '|Template Post Type:(.*)$|mi', file_get_contents( $full_path ), $type ) ) { $types = explode( ',', _cleanup_header_comment( $type[1] ) ); } foreach ( $types as $type ) { $type = sanitize_key( $type ); if ( ! isset( $post_templates[ $type ] ) ) { $post_templates[ $type ] = array(); } $post_templates[ $type ][ $file ] = _cleanup_header_comment( $header[1] ); } } $this->cache_add( 'post_templates', $post_templates ); } if ( current_theme_supports( 'block-templates' ) ) { $block_templates = get_block_templates( array(), 'wp_template' ); foreach ( get_post_types( array( 'public' => true ) ) as $type ) { foreach ( $block_templates as $block_template ) { if ( ! $block_template->is_custom ) { continue; } if ( isset( $block_template->post_types ) && ! in_array( $type, $block_template->post_types, true ) ) { continue; } $post_templates[ $type ][ $block_template->slug ] = $block_template->title; } } } if ( $this->load_textdomain() ) { foreach ( $post_templates as &$post_type ) { foreach ( $post_type as &$post_template ) { $post_template = $this->translate_header( 'Template Name', $post_template ); } } } return $post_templates; } /** * Returns the theme's post templates for a given post type. * * @since 3.4.0 * @since 4.7.0 Added the `$post_type` parameter. * * @param WP_Post|null $post Optional. The post being edited, provided for context. * @param string $post_type Optional. Post type to get the templates for. Default 'page'. * If a post is provided, its post type is used. * @return string[] Array of template header names keyed by the template file name. */ public function get_page_templates( $post = null, $post_type = 'page' ) { if ( $post ) { $post_type = get_post_type( $post ); } $post_templates = $this->get_post_templates(); $post_templates = isset( $post_templates[ $post_type ] ) ? $post_templates[ $post_type ] : array(); /** * Filters list of page templates for a theme. * * @since 4.9.6 * * @param string[] $post_templates Array of template header names keyed by the template file name. * @param WP_Theme $theme The theme object. * @param WP_Post|null $post The post being edited, provided for context, or null. * @param string $post_type Post type to get the templates for. */ $post_templates = (array) apply_filters( 'theme_templates', $post_templates, $this, $post, $post_type ); /** * Filters list of page templates for a theme. * * The dynamic portion of the hook name, `$post_type`, refers to the post type. * * Possible hook names include: * * - `theme_post_templates` * - `theme_page_templates` * - `theme_attachment_templates` * * @since 3.9.0 * @since 4.4.0 Converted to allow complete control over the `$page_templates` array. * @since 4.7.0 Added the `$post_type` parameter. * * @param string[] $post_templates Array of template header names keyed by the template file name. * @param WP_Theme $theme The theme object. * @param WP_Post|null $post The post being edited, provided for context, or null. * @param string $post_type Post type to get the templates for. */ $post_templates = (array) apply_filters( "theme_{$post_type}_templates", $post_templates, $this, $post, $post_type ); return $post_templates; } /** * Scans a directory for files of a certain extension. * * @since 3.4.0 * * @param string $path Absolute path to search. * @param array|string|null $extensions Optional. Array of extensions to find, string of a single extension, * or null for all extensions. Default null. * @param int $depth Optional. How many levels deep to search for files. Accepts 0, 1+, or * -1 (infinite depth). Default 0. * @param string $relative_path Optional. The basename of the absolute path. Used to control the * returned path for the found files, particularly when this function * recurses to lower depths. Default empty. * @return string[]|false Array of files, keyed by the path to the file relative to the `$path` directory prepended * with `$relative_path`, with the values being absolute paths. False otherwise. */ private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) { if ( ! is_dir( $path ) ) { return false; } if ( $extensions ) { $extensions = (array) $extensions; $_extensions = implode( '|', $extensions ); } $relative_path = trailingslashit( $relative_path ); if ( '/' === $relative_path ) { $relative_path = ''; } $results = scandir( $path ); $files = array(); /** * Filters the array of excluded directories and files while scanning theme folder. * * @since 4.7.4 * * @param string[] $exclusions Array of excluded directories and files. */ $exclusions = (array) apply_filters( 'theme_scandir_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) ); foreach ( $results as $result ) { if ( '.' === $result[0] || in_array( $result, $exclusions, true ) ) { continue; } if ( is_dir( $path . '/' . $result ) ) { if ( ! $depth ) { continue; } $found = self::scandir( $path . '/' . $result, $extensions, $depth - 1, $relative_path . $result ); $files = array_merge_recursive( $files, $found ); } elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) { $files[ $relative_path . $result ] = $path . '/' . $result; } } return $files; } /** * Loads the theme's textdomain. * * Translation files are not inherited from the parent theme. TODO: If this fails for the * child theme, it should probably try to load the parent theme's translations. * * @since 3.4.0 * * @return bool True if the textdomain was successfully loaded or has already been loaded. * False if no textdomain was specified in the file headers, or if the domain could not be loaded. */ public function load_textdomain() { if ( isset( $this->textdomain_loaded ) ) { return $this->textdomain_loaded; } $textdomain = $this->get( 'TextDomain' ); if ( ! $textdomain ) { $this->textdomain_loaded = false; return false; } if ( is_textdomain_loaded( $textdomain ) ) { $this->textdomain_loaded = true; return true; } $path = $this->get_stylesheet_directory(); $domainpath = $this->get( 'DomainPath' ); if ( $domainpath ) { $path .= $domainpath; } else { $path .= '/languages'; } $this->textdomain_loaded = load_theme_textdomain( $textdomain, $path ); return $this->textdomain_loaded; } /** * Determines whether the theme is allowed (multisite only). * * @since 3.4.0 * * @param string $check Optional. Whether to check only the 'network'-wide settings, the 'site' * settings, or 'both'. Defaults to 'both'. * @param int $blog_id Optional. Ignored if only network-wide settings are checked. Defaults to current site. * @return bool Whether the theme is allowed for the network. Returns true in single-site. */ public function is_allowed( $check = 'both', $blog_id = null ) { if ( ! is_multisite() ) { return true; } if ( 'both' === $check || 'network' === $check ) { $allowed = self::get_allowed_on_network(); if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) { return true; } } if ( 'both' === $check || 'site' === $check ) { $allowed = self::get_allowed_on_site( $blog_id ); if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) { return true; } } return false; } /** * Returns whether this theme is a block-based theme or not. * * @since 5.9.0 * * @return bool */ public function is_block_theme() { if ( isset( $this->block_theme ) ) { return $this->block_theme; } $paths_to_index_block_template = array( $this->get_file_path( '/templates/index.html' ), $this->get_file_path( '/block-templates/index.html' ), ); $this->block_theme = false; foreach ( $paths_to_index_block_template as $path_to_index_block_template ) { if ( is_file( $path_to_index_block_template ) && is_readable( $path_to_index_block_template ) ) { $this->block_theme = true; break; } } return $this->block_theme; } /** * Retrieves the path of a file in the theme. * * Searches in the stylesheet directory before the template directory so themes * which inherit from a parent theme can just override one file. * * @since 5.9.0 * * @param string $file Optional. File to search for in the stylesheet directory. * @return string The path of the file. */ public function get_file_path( $file = '' ) { $file = ltrim( $file, '/' ); $stylesheet_directory = $this->get_stylesheet_directory(); $template_directory = $this->get_template_directory(); if ( empty( $file ) ) { $path = $stylesheet_directory; } elseif ( $stylesheet_directory !== $template_directory && file_exists( $stylesheet_directory . '/' . $file ) ) { $path = $stylesheet_directory . '/' . $file; } else { $path = $template_directory . '/' . $file; } /** This filter is documented in wp-includes/link-template.php */ return apply_filters( 'theme_file_path', $path, $file ); } /** * Determines the latest WordPress default theme that is installed. * * This hits the filesystem. * * @since 4.4.0 * * @return WP_Theme|false Object, or false if no theme is installed, which would be bad. */ public static function get_core_default_theme() { foreach ( array_reverse( self::$default_themes ) as $slug => $name ) { $theme = wp_get_theme( $slug ); if ( $theme->exists() ) { return $theme; } } return false; } /** * Returns array of stylesheet names of themes allowed on the site or network. * * @since 3.4.0 * * @param int $blog_id Optional. ID of the site. Defaults to the current site. * @return string[] Array of stylesheet names. */ public static function get_allowed( $blog_id = null ) { /** * Filters the array of themes allowed on the network. * * Site is provided as context so that a list of network allowed themes can * be filtered further. * * @since 4.5.0 * * @param string[] $allowed_themes An array of theme stylesheet names. * @param int $blog_id ID of the site. */ $network = (array) apply_filters( 'network_allowed_themes', self::get_allowed_on_network(), $blog_id ); return $network + self::get_allowed_on_site( $blog_id ); } /** * Returns array of stylesheet names of themes allowed on the network. * * @since 3.4.0 * * @return string[] Array of stylesheet names. */ public static function get_allowed_on_network() { static $allowed_themes; if ( ! isset( $allowed_themes ) ) { $allowed_themes = (array) get_site_option( 'allowedthemes' ); } /** * Filters the array of themes allowed on the network. * * @since MU (3.0.0) * * @param string[] $allowed_themes An array of theme stylesheet names. */ $allowed_themes = apply_filters( 'allowed_themes', $allowed_themes ); return $allowed_themes; } /** * Returns array of stylesheet names of themes allowed on the site. * * @since 3.4.0 * * @param int $blog_id Optional. ID of the site. Defaults to the current site. * @return string[] Array of stylesheet names. */ public static function get_allowed_on_site( $blog_id = null ) { static $allowed_themes = array(); if ( ! $blog_id || ! is_multisite() ) { $blog_id = get_current_blog_id(); } if ( isset( $allowed_themes[ $blog_id ] ) ) { /** * Filters the array of themes allowed on the site. * * @since 4.5.0 * * @param string[] $allowed_themes An array of theme stylesheet names. * @param int $blog_id ID of the site. Defaults to current site. */ return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id ); } $current = get_current_blog_id() === $blog_id; if ( $current ) { $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' ); } else { switch_to_blog( $blog_id ); $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' ); restore_current_blog(); } /* * This is all super old MU back compat joy. * 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name. */ if ( false === $allowed_themes[ $blog_id ] ) { if ( $current ) { $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' ); } else { switch_to_blog( $blog_id ); $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' ); restore_current_blog(); } if ( ! is_array( $allowed_themes[ $blog_id ] ) || empty( $allowed_themes[ $blog_id ] ) ) { $allowed_themes[ $blog_id ] = array(); } else { $converted = array(); $themes = wp_get_themes(); foreach ( $themes as $stylesheet => $theme_data ) { if ( isset( $allowed_themes[ $blog_id ][ $theme_data->get( 'Name' ) ] ) ) { $converted[ $stylesheet ] = true; } } $allowed_themes[ $blog_id ] = $converted; } // Set the option so we never have to go through this pain again. if ( is_admin() && $allowed_themes[ $blog_id ] ) { if ( $current ) { update_option( 'allowedthemes', $allowed_themes[ $blog_id ], false ); delete_option( 'allowed_themes' ); } else { switch_to_blog( $blog_id ); update_option( 'allowedthemes', $allowed_themes[ $blog_id ], false ); delete_option( 'allowed_themes' ); restore_current_blog(); } } } /** This filter is documented in wp-includes/class-wp-theme.php */ return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id ); } /** * Returns the folder names of the block template directories. * * @since 6.4.0 * * @return string[] { * Folder names used by block themes. * * @type string $wp_template Theme-relative directory name for block templates. * @type string $wp_template_part Theme-relative directory name for block template parts. * } */ public function get_block_template_folders() { // Return set/cached value if available. if ( isset( $this->block_template_folders ) ) { return $this->block_template_folders; } $this->block_template_folders = $this->default_template_folders; $stylesheet_directory = $this->get_stylesheet_directory(); // If the theme uses deprecated block template folders. if ( file_exists( $stylesheet_directory . '/block-templates' ) || file_exists( $stylesheet_directory . '/block-template-parts' ) ) { $this->block_template_folders = array( 'wp_template' => 'block-templates', 'wp_template_part' => 'block-template-parts', ); } return $this->block_template_folders; } /** * Gets block pattern data for a specified theme. * Each pattern is defined as a PHP file and defines * its metadata using plugin-style headers. The minimum required definition is: * * /** * * Title: My Pattern * * Slug: my-theme/my-pattern * * * * The output of the PHP source corresponds to the content of the pattern, e.g.: * * <main><p><?php echo "Hello"; ?></p></main> * * If applicable, this will collect from both parent and child theme. * * Other settable fields include: * * - Description * - Viewport Width * - Inserter (yes/no) * - Categories (comma-separated values) * - Keywords (comma-separated values) * - Block Types (comma-separated values) * - Post Types (comma-separated values) * - Template Types (comma-separated values) * * @since 6.4.0 * * @return array Block pattern data. */ public function get_block_patterns() { $can_use_cached = ! wp_is_development_mode( 'theme' ); $pattern_data = $this->get_pattern_cache(); if ( is_array( $pattern_data ) ) { if ( $can_use_cached ) { return $pattern_data; } // If in development mode, clear pattern cache. $this->delete_pattern_cache(); } $dirpath = $this->get_stylesheet_directory() . '/patterns'; $pattern_data = array(); if ( ! file_exists( $dirpath ) ) { if ( $can_use_cached ) { $this->set_pattern_cache( $pattern_data ); } return $pattern_data; } $files = (array) self::scandir( $dirpath, 'php', -1 ); /** * Filters list of block pattern files for a theme. * * @since 6.8.0 * * @param array $files Array of theme files found within `patterns` directory. * @param string $dirpath Path of theme `patterns` directory being scanned. */ $files = apply_filters( 'theme_block_pattern_files', $files, $dirpath ); $dirpath = trailingslashit( $dirpath ); if ( ! $files ) { if ( $can_use_cached ) { $this->set_pattern_cache( $pattern_data ); } return $pattern_data; } $default_headers = array( 'title' => 'Title', 'slug' => 'Slug', 'description' => 'Description', 'viewportWidth' => 'Viewport Width', 'inserter' => 'Inserter', 'categories' => 'Categories', 'keywords' => 'Keywords', 'blockTypes' => 'Block Types', 'postTypes' => 'Post Types', 'templateTypes' => 'Template Types', ); $properties_to_parse = array( 'categories', 'keywords', 'blockTypes', 'postTypes', 'templateTypes', ); foreach ( $files as $file ) { $pattern = get_file_data( $file, $default_headers ); if ( empty( $pattern['slug'] ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: file name. */ __( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ), $file ), '6.0.0' ); continue; } if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern['slug'] ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: file name; 2: slug value found. */ __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ), $file, $pattern['slug'] ), '6.0.0' ); } // Title is a required property. if ( ! $pattern['title'] ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: file name. */ __( 'Could not register file "%s" as a block pattern ("Title" field missing)' ), $file ), '6.0.0' ); continue; } // For properties of type array, parse data as comma-separated. foreach ( $properties_to_parse as $property ) { if ( ! empty( $pattern[ $property ] ) ) { $pattern[ $property ] = array_filter( wp_parse_list( (string) $pattern[ $property ] ) ); } else { unset( $pattern[ $property ] ); } } // Parse properties of type int. $property = 'viewportWidth'; if ( ! empty( $pattern[ $property ] ) ) { $pattern[ $property ] = (int) $pattern[ $property ]; } else { unset( $pattern[ $property ] ); } // Parse properties of type bool. $property = 'inserter'; if ( ! empty( $pattern[ $property ] ) ) { $pattern[ $property ] = in_array( strtolower( $pattern[ $property ] ), array( 'yes', 'true' ), true ); } else { unset( $pattern[ $property ] ); } $key = str_replace( $dirpath, '', $file ); $pattern_data[ $key ] = $pattern; } if ( $can_use_cached ) { $this->set_pattern_cache( $pattern_data ); } return $pattern_data; } /** * Gets block pattern cache. * * @since 6.4.0 * @since 6.6.0 Uses transients to cache regardless of site environment. * * @return array|false Returns an array of patterns if cache is found, otherwise false. */ private function get_pattern_cache() { if ( ! $this->exists() ) { return false; } $pattern_data = get_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash ); if ( is_array( $pattern_data ) && $pattern_data['version'] === $this->get( 'Version' ) ) { return $pattern_data['patterns']; } return false; } /** * Sets block pattern cache. * * @since 6.4.0 * @since 6.6.0 Uses transients to cache regardless of site environment. * * @param array $patterns Block patterns data to set in cache. */ private function set_pattern_cache( array $patterns ) { $pattern_data = array( 'version' => $this->get( 'Version' ), 'patterns' => $patterns, ); /** * Filters the cache expiration time for theme files. * * @since 6.6.0 * * @param int $cache_expiration Cache expiration time in seconds. * @param string $cache_type Type of cache being set. */ $cache_expiration = (int) apply_filters( 'wp_theme_files_cache_ttl', self::$cache_expiration, 'theme_block_patterns' ); // We don't want to cache patterns infinitely. if ( $cache_expiration <= 0 ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: %1$s: The filter name.*/ __( 'The %1$s filter must return an integer value greater than 0.' ), '<code>wp_theme_files_cache_ttl</code>' ), '6.6.0' ); $cache_expiration = self::$cache_expiration; } set_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash, $pattern_data, $cache_expiration ); } /** * Clears block pattern cache. * * @since 6.4.0 * @since 6.6.0 Uses transients to cache regardless of site environment. */ public function delete_pattern_cache() { delete_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash ); } /** * Enables a theme for all sites on the current network. * * @since 4.6.0 * * @param string|string[] $stylesheets Stylesheet name or array of stylesheet names. */ public static function network_enable_theme( $stylesheets ) { if ( ! is_multisite() ) { return; } if ( ! is_array( $stylesheets ) ) { $stylesheets = array( $stylesheets ); } $allowed_themes = get_site_option( 'allowedthemes' ); foreach ( $stylesheets as $stylesheet ) { $allowed_themes[ $stylesheet ] = true; } update_site_option( 'allowedthemes', $allowed_themes ); } /** * Disables a theme for all sites on the current network. * * @since 4.6.0 * * @param string|string[] $stylesheets Stylesheet name or array of stylesheet names. */ public static function network_disable_theme( $stylesheets ) { if ( ! is_multisite() ) { return; } if ( ! is_array( $stylesheets ) ) { $stylesheets = array( $stylesheets ); } $allowed_themes = get_site_option( 'allowedthemes' ); foreach ( $stylesheets as $stylesheet ) { if ( isset( $allowed_themes[ $stylesheet ] ) ) { unset( $allowed_themes[ $stylesheet ] ); } } update_site_option( 'allowedthemes', $allowed_themes ); } /** * Sorts themes by name. * * @since 3.4.0 * * @param WP_Theme[] $themes Array of theme objects to sort (passed by reference). */ public static function sort_by_name( &$themes ) { if ( str_starts_with( get_user_locale(), 'en_' ) ) { uasort( $themes, array( 'WP_Theme', '_name_sort' ) ); } else { foreach ( $themes as $key => $theme ) { $theme->translate_header( 'Name', $theme->headers['Name'] ); } uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) ); } } /** * Callback function for usort() to naturally sort themes by name. * * Accesses the Name header directly from the class for maximum speed. * Would choke on HTML but we don't care enough to slow it down with strip_tags(). * * @since 3.4.0 * * @param WP_Theme $a First theme. * @param WP_Theme $b Second theme. * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally. * Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort(). */ private static function _name_sort( $a, $b ) { return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] ); } /** * Callback function for usort() to naturally sort themes by translated name. * * @since 3.4.0 * * @param WP_Theme $a First theme. * @param WP_Theme $b Second theme. * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally. * Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort(). */ private static function _name_sort_i18n( $a, $b ) { return strnatcasecmp( $a->name_translated, $b->name_translated ); } private static function _check_headers_property_has_correct_type( $headers ) { if ( ! is_array( $headers ) ) { return false; } foreach ( $headers as $key => $value ) { if ( ! is_string( $key ) || ! is_string( $value ) ) { return false; } } return true; } } https-migration-20260622010845.php 0000666 00000011205 15226762357 0012013 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260606151055.php 0000666 00000011205 15226762357 0012014 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-token-map.php 0000666 00000067712 15226762357 0010737 0 ustar 00 <?php /** * Class for efficiently looking up and mapping string keys to string values, with limits. * * @package WordPress * @since 6.6.0 */ /** * WP_Token_Map class. * * Use this class in specific circumstances with a static set of lookup keys which map to * a static set of transformed values. For example, this class is used to map HTML named * character references to their equivalent UTF-8 values. * * This class works differently than code calling `in_array()` and other methods. It * internalizes lookup logic and provides helper interfaces to optimize lookup and * transformation. It provides a method for precomputing the lookup tables and storing * them as PHP source code. * * All tokens and substitutions must be shorter than 256 bytes. * * Example: * * $smilies = WP_Token_Map::from_array( array( * '8O' => '😯', * ':(' => '🙁', * ':)' => '🙂', * ':?' => '😕', * ) ); * * true === $smilies->contains( ':)' ); * false === $smilies->contains( 'simile' ); * * '😕' === $smilies->read_token( 'Not sure :?.', 9, $length_of_smily_syntax ); * 2 === $length_of_smily_syntax; * * ## Precomputing the Token Map. * * Creating the class involves some work sorting and organizing the tokens and their * replacement values. In order to skip this, it's possible for the class to export * its state and be used as actual PHP source code. * * Example: * * // Export with four spaces as the indent, only for the sake of this docblock. * // The default indent is a tab character. * $indent = ' '; * echo $smilies->precomputed_php_source_table( $indent ); * * // Output, to be pasted into a PHP source file: * WP_Token_Map::from_precomputed_table( * array( * "storage_version" => "6.6.0", * "key_length" => 2, * "groups" => "", * "long_words" => array(), * "small_words" => "8O\x00:)\x00:(\x00:?\x00", * "small_mappings" => array( "😯", "🙂", "🙁", "😕" ) * ) * ); * * ## Large vs. small words. * * This class uses a short prefix called the "key" to optimize lookup of its tokens. * This means that some tokens may be shorter than or equal in length to that key. * Those words that are longer than the key are called "large" while those shorter * than or equal to the key length are called "small." * * This separation of large and small words is incidental to the way this class * optimizes lookup, and should be considered an internal implementation detail * of the class. It may still be important to be aware of it, however. * * ## Determining Key Length. * * The choice of the size of the key length should be based on the data being stored in * the token map. It should divide the data as evenly as possible, but should not create * so many groups that a large fraction of the groups only contain a single token. * * For the HTML5 named character references, a key length of 2 was found to provide a * sufficient spread and should be a good default for relatively large sets of tokens. * * However, for some data sets this might be too long. For example, a list of smilies * may be too small for a key length of 2. Perhaps 1 would be more appropriate. It's * best to experiment and determine empirically which values are appropriate. * * ## Generate Pre-Computed Source Code. * * Since the `WP_Token_Map` is designed for relatively static lookups, it can be * advantageous to precompute the values and instantiate a table that has already * sorted and grouped the tokens and built the lookup strings. * * This can be done with `WP_Token_Map::precomputed_php_source_table()`. * * Note that if there is a leading character that all tokens need, such as `&` for * HTML named character references, it can be beneficial to exclude this from the * token map. Instead, find occurrences of the leading character and then use the * token map to see if the following characters complete the token. * * Example: * * $map = WP_Token_Map::from_array( array( 'simple_smile:' => '🙂', 'sob:' => '😭', 'soba:' => '🍜' ) ); * echo $map->precomputed_php_source_table(); * // Output * WP_Token_Map::from_precomputed_table( * array( * "storage_version" => "6.6.0", * "key_length" => 2, * "groups" => "si\x00so\x00", * "long_words" => array( * // simple_smile:[🙂]. * "\x0bmple_smile:\x04🙂", * // soba:[🍜] sob:[😭]. * "\x03ba:\x04🍜\x02b:\x04😭", * ), * "short_words" => "", * "short_mappings" => array() * } * ); * * This precomputed value can be stored directly in source code and will skip the * startup cost of generating the lookup strings. See `$html5_named_character_entities`. * * Note that any updates to the precomputed format should update the storage version * constant. It would also be best to provide an update function to take older known * versions and upgrade them in place when loading into `from_precomputed_table()`. * * ## Future Direction. * * It may be viable to dynamically increase the length limits such that there's no need to impose them. * The limit appears because of the packing structure, which indicates how many bytes each segment of * text in the lookup tables spans. If, however, care were taken to track the longest word length, then * the packing structure could change its representation to allow for that. Each additional byte storing * length, however, increases the memory overhead and lookup runtime. * * An alternative approach could be to borrow the UTF-8 variable-length encoding and store lengths of less * than 127 as a single byte with the high bit unset, storing longer lengths as the combination of * continuation bytes. * * Since it has not been shown during the development of this class that longer strings are required, this * update is deferred until such a need is clear. * * @since 6.6.0 */ class WP_Token_Map { /** * Denotes the version of the code which produces pre-computed source tables. * * This version will be used not only to verify pre-computed data, but also * to upgrade pre-computed data from older versions. Choosing a name that * corresponds to the WordPress release will help people identify where an * old copy of data came from. */ const STORAGE_VERSION = '6.6.0-trunk'; /** * Maximum length for each key and each transformed value in the table (in bytes). * * @since 6.6.0 */ const MAX_LENGTH = 256; /** * How many bytes of each key are used to form a group key for lookup. * This also determines whether a word is considered short or long. * * @since 6.6.0 * * @var int */ private $key_length = 2; /** * Stores an optimized form of the word set, where words are grouped * by a prefix of the `$key_length` and then collapsed into a string. * * In each group, the keys and lookups form a packed data structure. * The keys in the string are stripped of their "group key," which is * the prefix of length `$this->key_length` shared by all of the items * in the group. Each word in the string is prefixed by a single byte * whose raw unsigned integer value represents how many bytes follow. * * ┌────────────────┬───────────────┬─────────────────┬────────┐ * │ Length of rest │ Rest of key │ Length of value │ Value │ * │ of key (bytes) │ │ (bytes) │ │ * ├────────────────┼───────────────┼─────────────────┼────────┤ * │ 0x08 │ nterDot; │ 0x02 │ · │ * └────────────────┴───────────────┴─────────────────┴────────┘ * * In this example, the key `CenterDot;` has a group key `Ce`, leaving * eight bytes for the rest of the key, `nterDot;`, and two bytes for * the transformed value `·` (or U+B7 or "\xC2\xB7"). * * Example: * * // Stores array( 'CenterDot;' => '·', 'Cedilla;' => '¸' ). * $groups = "Ce\x00"; * $large_words = array( "\x08nterDot;\x02·\x06dilla;\x02¸" ) * * The prefixes appear in the `$groups` string, each followed by a null * byte. This makes for quick lookup of where in the group string the key * is found, and then a simple division converts that offset into the index * in the `$large_words` array where the group string is to be found. * * This lookup data structure is designed to optimize cache locality and * minimize indirect memory reads when matching strings in the set. * * @since 6.6.0 * * @var array */ private $large_words = array(); /** * Stores the group keys for sequential string lookup. * * The offset into this string where the group key appears corresponds with the index * into the group array where the rest of the group string appears. This is an optimization * to improve cache locality while searching and minimize indirect memory accesses. * * @since 6.6.0 * * @var string */ private $groups = ''; /** * Stores an optimized row of small words, where every entry is * `$this->key_size + 1` bytes long and zero-extended. * * This packing allows for direct lookup of a short word followed * by the null byte, if extended to `$this->key_size + 1`. * * Example: * * // Stores array( 'GT', 'LT', 'gt', 'lt' ). * "GT\x00LT\x00gt\x00lt\x00" * * @since 6.6.0 * * @var string */ private $small_words = ''; /** * Replacements for the small words, in the same order they appear. * * With the position of a small word it's possible to index the translation * directly, as its position in the `$small_words` string corresponds to * the index of the replacement in the `$small_mapping` array. * * Example: * * array( '>', '<', '>', '<' ) * * @since 6.6.0 * * @var string[] */ private $small_mappings = array(); /** * Create a token map using an associative array of key/value pairs as the input. * * Example: * * $smilies = WP_Token_Map::from_array( array( * '8O' => '😯', * ':(' => '🙁', * ':)' => '🙂', * ':?' => '😕', * ) ); * * @since 6.6.0 * * @param array $mappings The keys transform into the values, both are strings. * @param int $key_length Determines the group key length. Leave at the default value * of 2 unless there's an empirical reason to change it. * * @return WP_Token_Map|null Token map, unless unable to create it. */ public static function from_array( array $mappings, int $key_length = 2 ): ?WP_Token_Map { $map = new WP_Token_Map(); $map->key_length = $key_length; // Start by grouping words. $groups = array(); $shorts = array(); foreach ( $mappings as $word => $mapping ) { if ( self::MAX_LENGTH <= strlen( $word ) || self::MAX_LENGTH <= strlen( $mapping ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: 1: maximum byte length (a count) */ __( 'Token Map tokens and substitutions must all be shorter than %1$d bytes.' ), self::MAX_LENGTH ), '6.6.0' ); return null; } $length = strlen( $word ); if ( $key_length >= $length ) { $shorts[] = $word; } else { $group = substr( $word, 0, $key_length ); if ( ! isset( $groups[ $group ] ) ) { $groups[ $group ] = array(); } $groups[ $group ][] = array( substr( $word, $key_length ), $mapping ); } } /* * Sort the words to ensure that no smaller substring of a match masks the full match. * For example, `Cap` should not match before `CapitalDifferentialD`. */ usort( $shorts, 'WP_Token_Map::longest_first_then_alphabetical' ); foreach ( $groups as $group_key => $group ) { usort( $groups[ $group_key ], static function ( array $a, array $b ): int { return self::longest_first_then_alphabetical( $a[0], $b[0] ); } ); } // Finally construct the optimized lookups. foreach ( $shorts as $word ) { $map->small_words .= str_pad( $word, $key_length + 1, "\x00", STR_PAD_RIGHT ); $map->small_mappings[] = $mappings[ $word ]; } $group_keys = array_keys( $groups ); sort( $group_keys ); foreach ( $group_keys as $group ) { $map->groups .= "{$group}\x00"; $group_string = ''; foreach ( $groups[ $group ] as $group_word ) { list( $word, $mapping ) = $group_word; $word_length = pack( 'C', strlen( $word ) ); $mapping_length = pack( 'C', strlen( $mapping ) ); $group_string .= "{$word_length}{$word}{$mapping_length}{$mapping}"; } $map->large_words[] = $group_string; } return $map; } /** * Creates a token map from a pre-computed table. * This skips the initialization cost of generating the table. * * This function should only be used to load data created with * WP_Token_Map::precomputed_php_source_tag(). * * @since 6.6.0 * * @param array $state { * Stores pre-computed state for directly loading into a Token Map. * * @type string $storage_version Which version of the code produced this state. * @type int $key_length Group key length. * @type string $groups Group lookup index. * @type array $large_words Large word groups and packed strings. * @type string $small_words Small words packed string. * @type array $small_mappings Small word mappings. * } * * @return WP_Token_Map Map with precomputed data loaded. */ public static function from_precomputed_table( $state ): ?WP_Token_Map { $has_necessary_state = isset( $state['storage_version'], $state['key_length'], $state['groups'], $state['large_words'], $state['small_words'], $state['small_mappings'] ); if ( ! $has_necessary_state ) { _doing_it_wrong( __METHOD__, __( 'Missing required inputs to pre-computed WP_Token_Map.' ), '6.6.0' ); return null; } if ( self::STORAGE_VERSION !== $state['storage_version'] ) { _doing_it_wrong( __METHOD__, /* translators: 1: version string, 2: version string. */ sprintf( __( 'Loaded version \'%1$s\' incompatible with expected version \'%2$s\'.' ), $state['storage_version'], self::STORAGE_VERSION ), '6.6.0' ); return null; } $map = new WP_Token_Map(); $map->key_length = $state['key_length']; $map->groups = $state['groups']; $map->large_words = $state['large_words']; $map->small_words = $state['small_words']; $map->small_mappings = $state['small_mappings']; return $map; } /** * Indicates if a given word is a lookup key in the map. * * Example: * * true === $smilies->contains( ':)' ); * false === $smilies->contains( 'simile' ); * * @since 6.6.0 * * @param string $word Determine if this word is a lookup key in the map. * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. * @return bool Whether there's an entry for the given word in the map. */ public function contains( string $word, string $case_sensitivity = 'case-sensitive' ): bool { $ignore_case = 'ascii-case-insensitive' === $case_sensitivity; if ( $this->key_length >= strlen( $word ) ) { if ( 0 === strlen( $this->small_words ) ) { return false; } $term = str_pad( $word, $this->key_length + 1, "\x00", STR_PAD_RIGHT ); $word_at = $ignore_case ? stripos( $this->small_words, $term ) : strpos( $this->small_words, $term ); if ( false === $word_at ) { return false; } return true; } $group_key = substr( $word, 0, $this->key_length ); $group_at = $ignore_case ? stripos( $this->groups, $group_key ) : strpos( $this->groups, $group_key ); if ( false === $group_at ) { return false; } $group = $this->large_words[ $group_at / ( $this->key_length + 1 ) ]; $group_length = strlen( $group ); $slug = substr( $word, $this->key_length ); $length = strlen( $slug ); $at = 0; while ( $at < $group_length ) { $token_length = unpack( 'C', $group[ $at++ ] )[1]; $token_at = $at; $at += $token_length; $mapping_length = unpack( 'C', $group[ $at++ ] )[1]; $mapping_at = $at; if ( $token_length === $length && 0 === substr_compare( $group, $slug, $token_at, $token_length, $ignore_case ) ) { return true; } $at = $mapping_at + $mapping_length; } return false; } /** * If the text starting at a given offset is a lookup key in the map, * return the corresponding transformation from the map, else `false`. * * This function returns the translated string, but accepts an optional * parameter `$matched_token_byte_length`, which communicates how many * bytes long the lookup key was, if it found one. This can be used to * advance a cursor in calling code if a lookup key was found. * * Example: * * false === $smilies->read_token( 'Not sure :?.', 0, $token_byte_length ); * '😕' === $smilies->read_token( 'Not sure :?.', 9, $token_byte_length ); * 2 === $token_byte_length; * * Example: * * while ( $at < strlen( $input ) ) { * $next_at = strpos( $input, ':', $at ); * if ( false === $next_at ) { * break; * } * * $smily = $smilies->read_token( $input, $next_at, $token_byte_length ); * if ( false === $next_at ) { * ++$at; * continue; * } * * $prefix = substr( $input, $at, $next_at - $at ); * $at += $token_byte_length; * $output .= "{$prefix}{$smily}"; * } * * @since 6.6.0 * * @param string $text String in which to search for a lookup key. * @param int $offset Optional. How many bytes into the string where the lookup key ought to start. Default 0. * @param int|null &$matched_token_byte_length Optional. Holds byte-length of found token matched, otherwise not set. Default null. * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. * * @return string|null Mapped value of lookup key if found, otherwise `null`. */ public function read_token( string $text, int $offset = 0, &$matched_token_byte_length = null, $case_sensitivity = 'case-sensitive' ): ?string { $ignore_case = 'ascii-case-insensitive' === $case_sensitivity; $text_length = strlen( $text ); // Search for a long word first, if the text is long enough, and if that fails, a short one. if ( $text_length > $this->key_length ) { $group_key = substr( $text, $offset, $this->key_length ); $group_at = $ignore_case ? stripos( $this->groups, $group_key ) : strpos( $this->groups, $group_key ); if ( false === $group_at ) { // Perhaps a short word then. return strlen( $this->small_words ) > 0 ? $this->read_small_token( $text, $offset, $matched_token_byte_length, $case_sensitivity ) : null; } $group = $this->large_words[ $group_at / ( $this->key_length + 1 ) ]; $group_length = strlen( $group ); $at = 0; while ( $at < $group_length ) { $token_length = unpack( 'C', $group[ $at++ ] )[1]; $token = substr( $group, $at, $token_length ); $at += $token_length; $mapping_length = unpack( 'C', $group[ $at++ ] )[1]; $mapping_at = $at; if ( 0 === substr_compare( $text, $token, $offset + $this->key_length, $token_length, $ignore_case ) ) { $matched_token_byte_length = $this->key_length + $token_length; return substr( $group, $mapping_at, $mapping_length ); } $at = $mapping_at + $mapping_length; } } // Perhaps a short word then. return strlen( $this->small_words ) > 0 ? $this->read_small_token( $text, $offset, $matched_token_byte_length, $case_sensitivity ) : null; } /** * Finds a match for a short word at the index. * * @since 6.6.0 * * @param string $text String in which to search for a lookup key. * @param int $offset Optional. How many bytes into the string where the lookup key ought to start. Default 0. * @param int|null &$matched_token_byte_length Optional. Holds byte-length of found lookup key if matched, otherwise not set. Default null. * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. * * @return string|null Mapped value of lookup key if found, otherwise `null`. */ private function read_small_token( string $text, int $offset = 0, &$matched_token_byte_length = null, $case_sensitivity = 'case-sensitive' ): ?string { $ignore_case = 'ascii-case-insensitive' === $case_sensitivity; $small_length = strlen( $this->small_words ); $search_text = substr( $text, $offset, $this->key_length ); if ( $ignore_case ) { $search_text = strtoupper( $search_text ); } $starting_char = $search_text[0]; $at = 0; while ( $at < $small_length ) { if ( $starting_char !== $this->small_words[ $at ] && ( ! $ignore_case || strtoupper( $this->small_words[ $at ] ) !== $starting_char ) ) { $at += $this->key_length + 1; continue; } for ( $adjust = 1; $adjust < $this->key_length; $adjust++ ) { if ( "\x00" === $this->small_words[ $at + $adjust ] ) { $matched_token_byte_length = $adjust; return $this->small_mappings[ $at / ( $this->key_length + 1 ) ]; } if ( $search_text[ $adjust ] !== $this->small_words[ $at + $adjust ] && ( ! $ignore_case || strtoupper( $this->small_words[ $at + $adjust ] !== $search_text[ $adjust ] ) ) ) { $at += $this->key_length + 1; continue 2; } } $matched_token_byte_length = $adjust; return $this->small_mappings[ $at / ( $this->key_length + 1 ) ]; } return null; } /** * Exports the token map into an associate array of key/value pairs. * * Example: * * $smilies->to_array() === array( * '8O' => '😯', * ':(' => '🙁', * ':)' => '🙂', * ':?' => '😕', * ); * * @return array The lookup key/substitution values as an associate array. */ public function to_array(): array { $tokens = array(); $at = 0; $small_mapping = 0; $small_length = strlen( $this->small_words ); while ( $at < $small_length ) { $key = rtrim( substr( $this->small_words, $at, $this->key_length + 1 ), "\x00" ); $value = $this->small_mappings[ $small_mapping++ ]; $tokens[ $key ] = $value; $at += $this->key_length + 1; } foreach ( $this->large_words as $index => $group ) { $prefix = substr( $this->groups, $index * ( $this->key_length + 1 ), 2 ); $group_length = strlen( $group ); $at = 0; while ( $at < $group_length ) { $length = unpack( 'C', $group[ $at++ ] )[1]; $key = $prefix . substr( $group, $at, $length ); $at += $length; $length = unpack( 'C', $group[ $at++ ] )[1]; $value = substr( $group, $at, $length ); $tokens[ $key ] = $value; $at += $length; } } return $tokens; } /** * Export the token map for quick loading in PHP source code. * * This function has a specific purpose, to make loading of static token maps fast. * It's used to ensure that the HTML character reference lookups add a minimal cost * to initializing the PHP process. * * Example: * * echo $smilies->precomputed_php_source_table(); * * // Output. * WP_Token_Map::from_precomputed_table( * array( * "storage_version" => "6.6.0", * "key_length" => 2, * "groups" => "", * "long_words" => array(), * "small_words" => "8O\x00:)\x00:(\x00:?\x00", * "small_mappings" => array( "😯", "🙂", "🙁", "😕" ) * ) * ); * * @since 6.6.0 * * @param string $indent Optional. Use this string for indentation, or rely on the default horizontal tab character. Default "\t". * @return string Value which can be pasted into a PHP source file for quick loading of table. */ public function precomputed_php_source_table( string $indent = "\t" ): string { $i1 = $indent; $i2 = $i1 . $indent; $i3 = $i2 . $indent; $class_version = self::STORAGE_VERSION; $output = self::class . "::from_precomputed_table(\n"; $output .= "{$i1}array(\n"; $output .= "{$i2}\"storage_version\" => \"{$class_version}\",\n"; $output .= "{$i2}\"key_length\" => {$this->key_length},\n"; $group_line = str_replace( "\x00", "\\x00", $this->groups ); $output .= "{$i2}\"groups\" => \"{$group_line}\",\n"; $output .= "{$i2}\"large_words\" => array(\n"; $prefixes = explode( "\x00", $this->groups ); foreach ( $prefixes as $index => $prefix ) { if ( '' === $prefix ) { break; } $group = $this->large_words[ $index ]; $group_length = strlen( $group ); $comment_line = "{$i3}//"; $data_line = "{$i3}\""; $at = 0; while ( $at < $group_length ) { $token_length = unpack( 'C', $group[ $at++ ] )[1]; $token = substr( $group, $at, $token_length ); $at += $token_length; $mapping_length = unpack( 'C', $group[ $at++ ] )[1]; $mapping = substr( $group, $at, $mapping_length ); $at += $mapping_length; $token_digits = str_pad( dechex( $token_length ), 2, '0', STR_PAD_LEFT ); $mapping_digits = str_pad( dechex( $mapping_length ), 2, '0', STR_PAD_LEFT ); $mapping = preg_replace_callback( "~[\\x00-\\x1f\\x22\\x5c]~", static function ( $match_result ) { switch ( $match_result[0] ) { case '"': return '\\"'; case '\\': return '\\\\'; default: $hex = dechex( ord( $match_result[0] ) ); return "\\x{$hex}"; } }, $mapping ); $comment_line .= " {$prefix}{$token}[{$mapping}]"; $data_line .= "\\x{$token_digits}{$token}\\x{$mapping_digits}{$mapping}"; } $comment_line .= ".\n"; $data_line .= "\",\n"; $output .= $comment_line; $output .= $data_line; } $output .= "{$i2}),\n"; $small_words = array(); $small_length = strlen( $this->small_words ); $at = 0; while ( $at < $small_length ) { $small_words[] = substr( $this->small_words, $at, $this->key_length + 1 ); $at += $this->key_length + 1; } $small_text = str_replace( "\x00", '\x00', implode( '', $small_words ) ); $output .= "{$i2}\"small_words\" => \"{$small_text}\",\n"; $output .= "{$i2}\"small_mappings\" => array(\n"; foreach ( $this->small_mappings as $mapping ) { $output .= "{$i3}\"{$mapping}\",\n"; } $output .= "{$i2})\n"; $output .= "{$i1})\n"; $output .= ')'; return $output; } /** * Compares two strings, returning the longest, or whichever * is first alphabetically if they are the same length. * * This is an important sort when building the token map because * it should not form a match on a substring of a longer potential * match. For example, it should not detect `Cap` when matching * against the string `CapitalDifferentialD`. * * @since 6.6.0 * * @param string $a First string to compare. * @param string $b Second string to compare. * @return int -1 or lower if `$a` is less than `$b`; 1 or greater if `$a` is greater than `$b`, and 0 if they are equal. */ private static function longest_first_then_alphabetical( string $a, string $b ): int { if ( $a === $b ) { return 0; } $length_a = strlen( $a ); $length_b = strlen( $b ); // Longer strings are less-than for comparison's sake. if ( $length_a !== $length_b ) { return $length_b - $length_a; } return strcmp( $a, $b ); } } https-migration-20260605102008.php 0000666 00000011205 15226762357 0012005 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260623012448.php 0000666 00000013341 15226762357 0012005 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } pluggable-deprecated.php 0000666 00000014264 15226762357 0011347 0 ustar 00 <?php /** * Deprecated pluggable functions from past WordPress versions. You shouldn't use these * functions and look for the alternatives instead. The functions will be removed in a * later version. * * Deprecated warnings are also thrown if one of these functions is being defined by a plugin. * * @package WordPress * @subpackage Deprecated * @see pluggable.php */ /* * Deprecated functions come here to die. */ if ( !function_exists('set_current_user') ) : /** * Changes the current user by ID or name. * * Set $id to null and specify a name if you do not know a user's ID. * * @since 2.0.1 * @deprecated 3.0.0 Use wp_set_current_user() * @see wp_set_current_user() * * @param int|null $id User ID. * @param string $name Optional. The user's username * @return WP_User returns wp_set_current_user() */ function set_current_user($id, $name = '') { _deprecated_function( __FUNCTION__, '3.0.0', 'wp_set_current_user()' ); return wp_set_current_user($id, $name); } endif; if ( !function_exists('get_currentuserinfo') ) : /** * Populate global variables with information about the currently logged in user. * * @since 0.71 * @deprecated 4.5.0 Use wp_get_current_user() * @see wp_get_current_user() * * @return bool|WP_User False on XMLRPC Request and invalid auth cookie, WP_User instance otherwise. */ function get_currentuserinfo() { _deprecated_function( __FUNCTION__, '4.5.0', 'wp_get_current_user()' ); return _wp_get_current_user(); } endif; if ( !function_exists('get_userdatabylogin') ) : /** * Retrieve user info by login name. * * @since 0.71 * @deprecated 3.3.0 Use get_user_by() * @see get_user_by() * * @param string $user_login User's username * @return bool|object False on failure, User DB row object */ function get_userdatabylogin($user_login) { _deprecated_function( __FUNCTION__, '3.3.0', "get_user_by('login')" ); return get_user_by('login', $user_login); } endif; if ( !function_exists('get_user_by_email') ) : /** * Retrieve user info by email. * * @since 2.5.0 * @deprecated 3.3.0 Use get_user_by() * @see get_user_by() * * @param string $email User's email address * @return bool|object False on failure, User DB row object */ function get_user_by_email($email) { _deprecated_function( __FUNCTION__, '3.3.0', "get_user_by('email')" ); return get_user_by('email', $email); } endif; if ( !function_exists('wp_setcookie') ) : /** * Sets a cookie for a user who just logged in. This function is deprecated. * * @since 1.5.0 * @deprecated 2.5.0 Use wp_set_auth_cookie() * @see wp_set_auth_cookie() * * @param string $username The user's username * @param string $password Optional. The user's password * @param bool $already_md5 Optional. Whether the password has already been through MD5 * @param string $home Optional. Will be used instead of COOKIEPATH if set * @param string $siteurl Optional. Will be used instead of SITECOOKIEPATH if set * @param bool $remember Optional. Remember that the user is logged in */ function wp_setcookie( $username, #[\SensitiveParameter] $password = '', $already_md5 = false, $home = '', $siteurl = '', $remember = false ) { _deprecated_function( __FUNCTION__, '2.5.0', 'wp_set_auth_cookie()' ); $user = get_user_by('login', $username); wp_set_auth_cookie($user->ID, $remember); } else : _deprecated_function( 'wp_setcookie', '2.5.0', 'wp_set_auth_cookie()' ); endif; if ( !function_exists('wp_clearcookie') ) : /** * Clears the authentication cookie, logging the user out. This function is deprecated. * * @since 1.5.0 * @deprecated 2.5.0 Use wp_clear_auth_cookie() * @see wp_clear_auth_cookie() */ function wp_clearcookie() { _deprecated_function( __FUNCTION__, '2.5.0', 'wp_clear_auth_cookie()' ); wp_clear_auth_cookie(); } else : _deprecated_function( 'wp_clearcookie', '2.5.0', 'wp_clear_auth_cookie()' ); endif; if ( !function_exists('wp_get_cookie_login') ): /** * Gets the user cookie login. This function is deprecated. * * This function is deprecated and should no longer be extended as it won't be * used anywhere in WordPress. Also, plugins shouldn't use it either. * * @since 2.0.3 * @deprecated 2.5.0 * * @return bool Always returns false */ function wp_get_cookie_login() { _deprecated_function( __FUNCTION__, '2.5.0' ); return false; } else : _deprecated_function( 'wp_get_cookie_login', '2.5.0' ); endif; if ( !function_exists('wp_login') ) : /** * Checks a users login information and logs them in if it checks out. This function is deprecated. * * Use the global $error to get the reason why the login failed. If the username * is blank, no error will be set, so assume blank username on that case. * * Plugins extending this function should also provide the global $error and set * what the error is, so that those checking the global for why there was a * failure can utilize it later. * * @since 1.2.2 * @deprecated 2.5.0 Use wp_signon() * @see wp_signon() * * @global string $error Error when false is returned * * @param string $username User's username * @param string $password User's password * @param string $deprecated Not used * @return bool True on successful check, false on login failure. */ function wp_login( $username, #[\SensitiveParameter] $password, $deprecated = '' ) { _deprecated_function( __FUNCTION__, '2.5.0', 'wp_signon()' ); global $error; $user = wp_authenticate($username, $password); if ( ! is_wp_error($user) ) return true; $error = $user->get_error_message(); return false; } else : _deprecated_function( 'wp_login', '2.5.0', 'wp_signon()' ); endif; /** * WordPress AtomPub API implementation. * * Originally stored in wp-app.php, and later wp-includes/class-wp-atom-server.php. * It is kept here in case a plugin directly referred to the class. * * @since 2.2.0 * @deprecated 3.5.0 * * @link https://wordpress.org/plugins/atom-publishing-protocol/ */ if ( ! class_exists( 'wp_atom_server', false ) ) { class wp_atom_server { public function __call( $name, $arguments ) { _deprecated_function( __CLASS__ . '::' . $name, '3.5.0', 'the Atom Publishing Protocol plugin' ); } public static function __callStatic( $name, $arguments ) { _deprecated_function( __CLASS__ . '::' . $name, '3.5.0', 'the Atom Publishing Protocol plugin' ); } } } https-migration-20260606183054.php 0000666 00000011205 15226762357 0012020 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260605094627.php 0000666 00000011205 15226762357 0012026 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-customize-background-image-setting.php 0000666 00000001177 15226762357 0015727 0 ustar 00 <?php /** * Customize API: WP_Customize_Background_Image_Setting class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customizer Background Image Setting class. * * @since 3.4.0 * * @see WP_Customize_Setting */ final class WP_Customize_Background_Image_Setting extends WP_Customize_Setting { /** * Unique string identifier for the setting. * * @since 3.4.0 * @var string */ public $id = 'background_image_thumb'; /** * @since 3.4.0 * * @param mixed $value The value to update. Not used. */ public function update( $value ) { remove_theme_mod( 'background_image_thumb' ); } } https-migration-20260607071034.php 0000666 00000011205 15226762357 0012013 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-customize-themes-panel.php 0000666 00000006471 15226762357 0013441 0 ustar 00 <?php /** * Customize API: WP_Customize_Themes_Panel class * * @package WordPress * @subpackage Customize * @since 4.9.0 */ /** * Customize Themes Panel Class * * @since 4.9.0 * * @see WP_Customize_Panel */ class WP_Customize_Themes_Panel extends WP_Customize_Panel { /** * Panel type. * * @since 4.9.0 * @var string */ public $type = 'themes'; /** * An Underscore (JS) template for rendering this panel's container. * * The themes panel renders a custom panel heading with the active theme and a switch themes button. * * @see WP_Customize_Panel::print_template() * * @since 4.9.0 */ protected function render_template() { ?> <li id="accordion-section-{{ data.id }}" class="accordion-section control-panel-themes"> <h3 class="accordion-section-title"> <?php if ( $this->manager->is_theme_active() ) { echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> {{ data.title }}'; } else { echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> {{ data.title }}'; } ?> <?php if ( current_user_can( 'switch_themes' ) ) : ?> <button type="button" class="button change-theme" aria-label="<?php esc_attr_e( 'Change theme' ); ?>"><?php _ex( 'Change', 'theme' ); ?></button> <?php endif; ?> </h3> <ul class="accordion-sub-container control-panel-content"></ul> </li> <?php } /** * An Underscore (JS) template for this panel's content (but not its container). * * Class variables for this panel class are available in the `data` JS object; * export custom variables by overriding WP_Customize_Panel::json(). * * @since 4.9.0 * * @see WP_Customize_Panel::print_template() */ protected function content_template() { ?> <li class="panel-meta customize-info accordion-section <# if ( ! data.description ) { #> cannot-expand<# } #>"> <button class="customize-panel-back" tabindex="-1" type="button"><span class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ _e( 'Back' ); ?> </span></button> <div class="accordion-section-title"> <span class="preview-notice"> <?php printf( /* translators: %s: Themes panel title in the Customizer. */ __( 'You are browsing %s' ), '<strong class="panel-title">' . __( 'Themes' ) . '</strong>' ); // Separate strings for consistency with other panels. ?> </span> <?php if ( current_user_can( 'install_themes' ) && ! is_multisite() ) : ?> <# if ( data.description ) { #> <button class="customize-help-toggle dashicons dashicons-editor-help" type="button" aria-expanded="false"><span class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ _e( 'Help' ); ?> </span></button> <# } #> <?php endif; ?> </div> <?php if ( current_user_can( 'install_themes' ) && ! is_multisite() ) : ?> <# if ( data.description ) { #> <div class="description customize-panel-description"> {{{ data.description }}} </div> <# } #> <?php endif; ?> <div class="customize-control-notifications-container"></div> </li> <li class="customize-themes-full-container-container"> <div class="customize-themes-full-container"> <div class="customize-themes-notifications"></div> </div> </li> <?php } } https-migration-20260605053121.php 0000666 00000011205 15226762357 0012006 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260605080450.php 0000666 00000013341 15226762357 0012003 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260610063933-20260621215720.php 0000666 00000013341 15226762357 0013367 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-customize-header-image-setting.php 0000666 00000003366 15226762357 0015042 0 ustar 00 <?php /** * Customize API: WP_Customize_Header_Image_Setting class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * A setting that is used to filter a value, but will not save the results. * * Results should be properly handled using another setting or callback. * * @since 3.4.0 * * @see WP_Customize_Setting */ final class WP_Customize_Header_Image_Setting extends WP_Customize_Setting { /** * Unique string identifier for the setting. * * @since 3.4.0 * @var string */ public $id = 'header_image_data'; /** * @since 3.4.0 * * @global Custom_Image_Header $custom_image_header * * @param mixed $value The value to update. */ public function update( $value ) { global $custom_image_header; // If _custom_header_background_just_in_time() fails to initialize $custom_image_header when not is_admin(). if ( empty( $custom_image_header ) ) { require_once ABSPATH . 'wp-admin/includes/class-custom-image-header.php'; $args = get_theme_support( 'custom-header' ); $admin_head_callback = isset( $args[0]['admin-head-callback'] ) ? $args[0]['admin-head-callback'] : null; $admin_preview_callback = isset( $args[0]['admin-preview-callback'] ) ? $args[0]['admin-preview-callback'] : null; $custom_image_header = new Custom_Image_Header( $admin_head_callback, $admin_preview_callback ); } /* * If the value doesn't exist (removed or random), * use the header_image value. */ if ( ! $value ) { $value = $this->manager->get_setting( 'header_image' )->post_value(); } if ( is_array( $value ) && isset( $value['choice'] ) ) { $custom_image_header->set_header_image( $value['choice'] ); } else { $custom_image_header->set_header_image( $value ); } } } https-migration-20260605113237.php 0000666 00000011205 15226762357 0012013 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-speculation-rules.php 0000666 00000016547 15226762357 0012522 0 ustar 00 <?php /** * Class 'WP_Speculation_Rules'. * * @package WordPress * @subpackage Speculative Loading * @since 6.8.0 */ /** * Class representing a set of speculation rules. * * @since 6.8.0 * @access private */ final class WP_Speculation_Rules implements JsonSerializable { /** * Stored rules, as a map of `$mode => $rules` pairs. * * Every `$rules` value is a map of `$id => $rule` pairs. * * @since 6.8.0 * @var array<string, array<string, mixed>> */ private $rules_by_mode = array(); /** * The allowed speculation rules modes as a map, used for validation. * * @since 6.8.0 * @var array<string, bool> */ private static $mode_allowlist = array( 'prefetch' => true, 'prerender' => true, ); /** * The allowed speculation rules eagerness levels as a map, used for validation. * * @since 6.8.0 * @var array<string, bool> */ private static $eagerness_allowlist = array( 'immediate' => true, 'eager' => true, 'moderate' => true, 'conservative' => true, ); /** * The allowed speculation rules sources as a map, used for validation. * * @since 6.8.0 * @var array<string, bool> */ private static $source_allowlist = array( 'list' => true, 'document' => true, ); /** * Adds a speculation rule to the speculation rules to consider. * * @since 6.8.0 * * @param string $mode Speculative loading mode. Either 'prefetch' or 'prerender'. * @param string $id Unique string identifier for the speculation rule. * @param array<string, mixed> $rule Associative array of rule arguments. * @return bool True on success, false if invalid parameters are provided. */ public function add_rule( string $mode, string $id, array $rule ): bool { if ( ! self::is_valid_mode( $mode ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: %s: invalid mode value */ __( 'The value "%s" is not a valid speculation rules mode.' ), esc_html( $mode ) ), '6.8.0' ); return false; } if ( ! $this->is_valid_id( $id ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: %s: invalid ID value */ __( 'The value "%s" is not a valid ID for a speculation rule.' ), esc_html( $id ) ), '6.8.0' ); return false; } if ( $this->has_rule( $mode, $id ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: %s: invalid ID value */ __( 'A speculation rule with ID "%s" already exists.' ), esc_html( $id ) ), '6.8.0' ); return false; } /* * Perform some basic speculation rule validation. * Every rule must have either a 'where' key or a 'urls' key, but not both. * The presence of a 'where' key implies a 'source' of 'document', while the presence of a 'urls' key implies * a 'source' of 'list'. */ if ( ( ! isset( $rule['where'] ) && ! isset( $rule['urls'] ) ) || ( isset( $rule['where'] ) && isset( $rule['urls'] ) ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: 1: allowed key, 2: alternative allowed key */ __( 'A speculation rule must include either a "%1$s" key or a "%2$s" key, but not both.' ), 'where', 'urls' ), '6.8.0' ); return false; } if ( isset( $rule['source'] ) ) { if ( ! self::is_valid_source( $rule['source'] ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: %s: invalid source value */ __( 'The value "%s" is not a valid source for a speculation rule.' ), esc_html( $rule['source'] ) ), '6.8.0' ); return false; } if ( 'list' === $rule['source'] && isset( $rule['where'] ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: 1: source value, 2: forbidden key */ __( 'A speculation rule of source "%1$s" must not include a "%2$s" key.' ), 'list', 'where' ), '6.8.0' ); return false; } if ( 'document' === $rule['source'] && isset( $rule['urls'] ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: 1: source value, 2: forbidden key */ __( 'A speculation rule of source "%1$s" must not include a "%2$s" key.' ), 'document', 'urls' ), '6.8.0' ); return false; } } // If there is an 'eagerness' key specified, make sure it's valid. if ( isset( $rule['eagerness'] ) ) { if ( ! self::is_valid_eagerness( $rule['eagerness'] ) ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: %s: invalid eagerness value */ __( 'The value "%s" is not a valid eagerness for a speculation rule.' ), esc_html( $rule['eagerness'] ) ), '6.8.0' ); return false; } if ( isset( $rule['where'] ) && 'immediate' === $rule['eagerness'] ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: %s: forbidden eagerness value */ __( 'The eagerness value "%s" is forbidden for document-level speculation rules.' ), 'immediate' ), '6.8.0' ); return false; } } if ( ! isset( $this->rules_by_mode[ $mode ] ) ) { $this->rules_by_mode[ $mode ] = array(); } $this->rules_by_mode[ $mode ][ $id ] = $rule; return true; } /** * Checks whether a speculation rule for the given mode and ID already exists. * * @since 6.8.0 * * @param string $mode Speculative loading mode. Either 'prefetch' or 'prerender'. * @param string $id Unique string identifier for the speculation rule. * @return bool True if the rule already exists, false otherwise. */ public function has_rule( string $mode, string $id ): bool { return isset( $this->rules_by_mode[ $mode ][ $id ] ); } /** * Returns the speculation rules data ready to be JSON-encoded. * * @since 6.8.0 * * @return array<string, array<string, mixed>> Speculation rules data. */ #[ReturnTypeWillChange] public function jsonSerialize() { // Strip the IDs for JSON output, since they are not relevant for the Speculation Rules API. return array_map( static function ( array $rules ) { return array_values( $rules ); }, array_filter( $this->rules_by_mode ) ); } /** * Checks whether the given ID is valid. * * @since 6.8.0 * * @param string $id Unique string identifier for the speculation rule. * @return bool True if the ID is valid, false otherwise. */ private function is_valid_id( string $id ): bool { return (bool) preg_match( '/^[a-z][a-z0-9_-]+$/', $id ); } /** * Checks whether the given speculation rules mode is valid. * * @since 6.8.0 * * @param string $mode Speculation rules mode. * @return bool True if valid, false otherwise. */ public static function is_valid_mode( string $mode ): bool { return isset( self::$mode_allowlist[ $mode ] ); } /** * Checks whether the given speculation rules eagerness is valid. * * @since 6.8.0 * * @param string $eagerness Speculation rules eagerness. * @return bool True if valid, false otherwise. */ public static function is_valid_eagerness( string $eagerness ): bool { return isset( self::$eagerness_allowlist[ $eagerness ] ); } /** * Checks whether the given speculation rules source is valid. * * @since 6.8.0 * * @param string $source Speculation rules source. * @return bool True if valid, false otherwise. */ public static function is_valid_source( string $source ): bool { return isset( self::$source_allowlist[ $source ] ); } } https-detection-20260605074104-20260621211310.php 0000666 00000013341 15226762357 0013352 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260605094635.php 0000666 00000013341 15226762357 0012015 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260605062441.php 0000666 00000011205 15226762357 0012013 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-block-parser-block.php 0000666 00000004773 15226762357 0012516 0 ustar 00 <?php /** * Block Serialization Parser * * @package WordPress */ /** * Class WP_Block_Parser_Block * * Holds the block structure in memory * * @since 5.0.0 */ class WP_Block_Parser_Block { /** * Name of block * * @example "core/paragraph" * * @since 5.0.0 * @var string */ public $blockName; // phpcs:ignore WordPress.NamingConventions.ValidVariableName /** * Optional set of attributes from block comment delimiters * * @example null * @example array( 'columns' => 3 ) * * @since 5.0.0 * @var array|null */ public $attrs; /** * List of inner blocks (of this same class) * * @since 5.0.0 * @var WP_Block_Parser_Block[] */ public $innerBlocks; // phpcs:ignore WordPress.NamingConventions.ValidVariableName /** * Resultant HTML from inside block comment delimiters * after removing inner blocks * * @example "...Just <!-- wp:test /--> testing..." -> "Just testing..." * * @since 5.0.0 * @var string */ public $innerHTML; // phpcs:ignore WordPress.NamingConventions.ValidVariableName /** * List of string fragments and null markers where inner blocks were found * * @example array( * 'innerHTML' => 'BeforeInnerAfter', * 'innerBlocks' => array( block, block ), * 'innerContent' => array( 'Before', null, 'Inner', null, 'After' ), * ) * * @since 5.0.0 * @var array */ public $innerContent; // phpcs:ignore WordPress.NamingConventions.ValidVariableName /** * Constructor. * * Will populate object properties from the provided arguments. * * @since 5.0.0 * * @param string $name Name of block. * @param array $attrs Optional set of attributes from block comment delimiters. * @param array $inner_blocks List of inner blocks (of this same class). * @param string $inner_html Resultant HTML from inside block comment delimiters after removing inner blocks. * @param array $inner_content List of string fragments and null markers where inner blocks were found. */ public function __construct( $name, $attrs, $inner_blocks, $inner_html, $inner_content ) { $this->blockName = $name; // phpcs:ignore WordPress.NamingConventions.ValidVariableName $this->attrs = $attrs; $this->innerBlocks = $inner_blocks; // phpcs:ignore WordPress.NamingConventions.ValidVariableName $this->innerHTML = $inner_html; // phpcs:ignore WordPress.NamingConventions.ValidVariableName $this->innerContent = $inner_content; // phpcs:ignore WordPress.NamingConventions.ValidVariableName } } class-wp-post.php 0000666 00000014533 15226762357 0010022 0 ustar 00 <?php /** * Post API: WP_Post class * * @package WordPress * @subpackage Post * @since 4.4.0 */ /** * Core class used to implement the WP_Post object. * * @since 3.5.0 * * @property string $page_template * * @property-read int[] $ancestors * @property-read int[] $post_category * @property-read string[] $tags_input */ #[AllowDynamicProperties] final class WP_Post { /** * Post ID. * * @since 3.5.0 * @var int */ public $ID; /** * ID of post author. * * A numeric string, for compatibility reasons. * * @since 3.5.0 * @var string */ public $post_author = '0'; /** * The post's local publication time. * * @since 3.5.0 * @var string */ public $post_date = '0000-00-00 00:00:00'; /** * The post's GMT publication time. * * @since 3.5.0 * @var string */ public $post_date_gmt = '0000-00-00 00:00:00'; /** * The post's content. * * @since 3.5.0 * @var string */ public $post_content = ''; /** * The post's title. * * @since 3.5.0 * @var string */ public $post_title = ''; /** * The post's excerpt. * * @since 3.5.0 * @var string */ public $post_excerpt = ''; /** * The post's status. * * @since 3.5.0 * @var string */ public $post_status = 'publish'; /** * Whether comments are allowed. * * @since 3.5.0 * @var string */ public $comment_status = 'open'; /** * Whether pings are allowed. * * @since 3.5.0 * @var string */ public $ping_status = 'open'; /** * The post's password in plain text. * * @since 3.5.0 * @var string */ public $post_password = ''; /** * The post's slug. * * @since 3.5.0 * @var string */ public $post_name = ''; /** * URLs queued to be pinged. * * @since 3.5.0 * @var string */ public $to_ping = ''; /** * URLs that have been pinged. * * @since 3.5.0 * @var string */ public $pinged = ''; /** * The post's local modified time. * * @since 3.5.0 * @var string */ public $post_modified = '0000-00-00 00:00:00'; /** * The post's GMT modified time. * * @since 3.5.0 * @var string */ public $post_modified_gmt = '0000-00-00 00:00:00'; /** * A utility DB field for post content. * * @since 3.5.0 * @var string */ public $post_content_filtered = ''; /** * ID of a post's parent post. * * @since 3.5.0 * @var int */ public $post_parent = 0; /** * The unique identifier for a post, not necessarily a URL, used as the feed GUID. * * @since 3.5.0 * @var string */ public $guid = ''; /** * A field used for ordering posts. * * @since 3.5.0 * @var int */ public $menu_order = 0; /** * The post's type, like post or page. * * @since 3.5.0 * @var string */ public $post_type = 'post'; /** * An attachment's mime type. * * @since 3.5.0 * @var string */ public $post_mime_type = ''; /** * Cached comment count. * * A numeric string, for compatibility reasons. * * @since 3.5.0 * @var string */ public $comment_count = '0'; /** * Stores the post object's sanitization level. * * Does not correspond to a DB field. * * @since 3.5.0 * @var string */ public $filter; /** * Retrieve WP_Post instance. * * @since 3.5.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $post_id Post ID. * @return WP_Post|false Post object, false otherwise. */ public static function get_instance( $post_id ) { global $wpdb; $post_id = (int) $post_id; if ( $post_id <= 0 ) { return false; } $_post = wp_cache_get( $post_id, 'posts' ); if ( ! $_post ) { $_post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d LIMIT 1", $post_id ) ); if ( ! $_post ) { return false; } $_post = sanitize_post( $_post, 'raw' ); wp_cache_add( $_post->ID, $_post, 'posts' ); } elseif ( empty( $_post->filter ) || 'raw' !== $_post->filter ) { $_post = sanitize_post( $_post, 'raw' ); } return new WP_Post( $_post ); } /** * Constructor. * * @since 3.5.0 * * @param WP_Post|object $post Post object. */ public function __construct( $post ) { foreach ( get_object_vars( $post ) as $key => $value ) { $this->$key = $value; } } /** * Isset-er. * * @since 3.5.0 * * @param string $key Property to check if set. * @return bool */ public function __isset( $key ) { if ( 'ancestors' === $key ) { return true; } if ( 'page_template' === $key ) { return true; } if ( 'post_category' === $key ) { return true; } if ( 'tags_input' === $key ) { return true; } return metadata_exists( 'post', $this->ID, $key ); } /** * Getter. * * @since 3.5.0 * * @param string $key Key to get. * @return mixed */ public function __get( $key ) { if ( 'page_template' === $key && $this->__isset( $key ) ) { return get_post_meta( $this->ID, '_wp_page_template', true ); } if ( 'post_category' === $key ) { if ( is_object_in_taxonomy( $this->post_type, 'category' ) ) { $terms = get_the_terms( $this, 'category' ); } if ( empty( $terms ) ) { return array(); } return wp_list_pluck( $terms, 'term_id' ); } if ( 'tags_input' === $key ) { if ( is_object_in_taxonomy( $this->post_type, 'post_tag' ) ) { $terms = get_the_terms( $this, 'post_tag' ); } if ( empty( $terms ) ) { return array(); } return wp_list_pluck( $terms, 'name' ); } // Rest of the values need filtering. if ( 'ancestors' === $key ) { $value = get_post_ancestors( $this ); } else { $value = get_post_meta( $this->ID, $key, true ); } if ( $this->filter ) { $value = sanitize_post_field( $key, $value, $this->ID, $this->filter ); } return $value; } /** * {@Missing Summary} * * @since 3.5.0 * * @param string $filter Filter. * @return WP_Post */ public function filter( $filter ) { if ( $this->filter === $filter ) { return $this; } if ( 'raw' === $filter ) { return self::get_instance( $this->ID ); } return sanitize_post( $this, $filter ); } /** * Convert object to array. * * @since 3.5.0 * * @return array Object as array. */ public function to_array() { $post = get_object_vars( $this ); foreach ( array( 'ancestors', 'page_template', 'post_category', 'tags_input' ) as $key ) { if ( $this->__isset( $key ) ) { $post[ $key ] = $this->__get( $key ); } } return $post; } } class-wp-customize-partial.php 0000666 00000024510 15226762357 0012505 0 ustar 00 <?php /** * Customize API: WP_Customize_Partial class * * @package WordPress * @subpackage Customize * @since 4.5.0 */ /** * Core Customizer class for implementing selective refresh partials. * * Representation of a rendered region in the previewed page that gets * selectively refreshed when an associated setting is changed. * This class is analogous of WP_Customize_Control. * * @since 4.5.0 */ #[AllowDynamicProperties] class WP_Customize_Partial { /** * Component. * * @since 4.5.0 * @var WP_Customize_Selective_Refresh */ public $component; /** * Unique identifier for the partial. * * If the partial is used to display a single setting, this would generally * be the same as the associated setting's ID. * * @since 4.5.0 * @var string */ public $id; /** * Parsed ID. * * @since 4.5.0 * @var array { * @type string $base ID base. * @type array $keys Keys for multidimensional. * } */ protected $id_data = array(); /** * Type of this partial. * * @since 4.5.0 * @var string */ public $type = 'default'; /** * The jQuery selector to find the container element for the partial. * * @since 4.5.0 * @var string */ public $selector; /** * IDs for settings tied to the partial. * * @since 4.5.0 * @var string[] */ public $settings; /** * The ID for the setting that this partial is primarily responsible for rendering. * * If not supplied, it will default to the ID of the first setting. * * @since 4.5.0 * @var string */ public $primary_setting; /** * Capability required to edit this partial. * * Normally this is empty and the capability is derived from the capabilities * of the associated `$settings`. * * @since 4.5.0 * @var string */ public $capability; /** * Render callback. * * @since 4.5.0 * * @see WP_Customize_Partial::render() * @var callable Callback is called with one argument, the instance of * WP_Customize_Partial. The callback can either echo the * partial or return the partial as a string, or return false if error. */ public $render_callback; /** * Whether the container element is included in the partial, or if only the contents are rendered. * * @since 4.5.0 * @var bool */ public $container_inclusive = false; /** * Whether to refresh the entire preview in case a partial cannot be refreshed. * * A partial render is considered a failure if the render_callback returns false. * * @since 4.5.0 * @var bool */ public $fallback_refresh = true; /** * Constructor. * * Supplied `$args` override class property defaults. * * If `$args['settings']` is not defined, use the $id as the setting ID. * * @since 4.5.0 * * @param WP_Customize_Selective_Refresh $component Customize Partial Refresh plugin instance. * @param string $id Control ID. * @param array $args { * Optional. Array of properties for the new Partials object. Default empty array. * * @type string $type Type of the partial to be created. * @type string $selector The jQuery selector to find the container element for the partial, that is, * a partial's placement. * @type string[] $settings IDs for settings tied to the partial. If undefined, `$id` will be used. * @type string $primary_setting The ID for the setting that this partial is primarily responsible for * rendering. If not supplied, it will default to the ID of the first setting. * @type string $capability Capability required to edit this partial. * Normally this is empty and the capability is derived from the capabilities * of the associated `$settings`. * @type callable $render_callback Render callback. * Callback is called with one argument, the instance of WP_Customize_Partial. * The callback can either echo the partial or return the partial as a string, * or return false if error. * @type bool $container_inclusive Whether the container element is included in the partial, or if only * the contents are rendered. * @type bool $fallback_refresh Whether to refresh the entire preview in case a partial cannot be refreshed. * A partial render is considered a failure if the render_callback returns * false. * } */ public function __construct( WP_Customize_Selective_Refresh $component, $id, $args = array() ) { $keys = array_keys( get_object_vars( $this ) ); foreach ( $keys as $key ) { if ( isset( $args[ $key ] ) ) { $this->$key = $args[ $key ]; } } $this->component = $component; $this->id = $id; $this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) ); $this->id_data['base'] = array_shift( $this->id_data['keys'] ); if ( empty( $this->render_callback ) ) { $this->render_callback = array( $this, 'render_callback' ); } // Process settings. if ( ! isset( $this->settings ) ) { $this->settings = array( $id ); } elseif ( is_string( $this->settings ) ) { $this->settings = array( $this->settings ); } if ( empty( $this->primary_setting ) ) { $this->primary_setting = current( $this->settings ); } } /** * Retrieves parsed ID data for multidimensional setting. * * @since 4.5.0 * * @return array { * ID data for multidimensional partial. * * @type string $base ID base. * @type array $keys Keys for multidimensional array. * } */ final public function id_data() { return $this->id_data; } /** * Renders the template partial involving the associated settings. * * @since 4.5.0 * * @param array $container_context Optional. Array of context data associated with the target container (placement). * Default empty array. * @return string|array|false The rendered partial as a string, raw data array (for client-side JS template), * or false if no render applied. */ final public function render( $container_context = array() ) { $partial = $this; $rendered = false; if ( ! empty( $this->render_callback ) ) { ob_start(); $return_render = call_user_func( $this->render_callback, $this, $container_context ); $ob_render = ob_get_clean(); if ( null !== $return_render && '' !== $ob_render ) { _doing_it_wrong( __FUNCTION__, __( 'Partial render must echo the content or return the content string (or array), but not both.' ), '4.5.0' ); } /* * Note that the string return takes precedence because the $ob_render may just\ * include PHP warnings or notices. */ $rendered = null !== $return_render ? $return_render : $ob_render; } /** * Filters partial rendering. * * @since 4.5.0 * * @param string|array|false $rendered The partial value. Default false. * @param WP_Customize_Partial $partial WP_Customize_Setting instance. * @param array $container_context Optional array of context data associated with * the target container. */ $rendered = apply_filters( 'customize_partial_render', $rendered, $partial, $container_context ); /** * Filters partial rendering for a specific partial. * * The dynamic portion of the hook name, `$partial->ID` refers to the partial ID. * * @since 4.5.0 * * @param string|array|false $rendered The partial value. Default false. * @param WP_Customize_Partial $partial WP_Customize_Setting instance. * @param array $container_context Optional array of context data associated with * the target container. */ $rendered = apply_filters( "customize_partial_render_{$partial->id}", $rendered, $partial, $container_context ); return $rendered; } /** * Default callback used when invoking WP_Customize_Control::render(). * * Note that this method may echo the partial *or* return the partial as * a string or array, but not both. Output buffering is performed when this * is called. Subclasses can override this with their specific logic, or they * may provide an 'render_callback' argument to the constructor. * * This method may return an HTML string for straight DOM injection, or it * may return an array for supporting Partial JS subclasses to render by * applying to client-side templating. * * @since 4.5.0 * * @param WP_Customize_Partial $partial Partial. * @param array $context Context. * @return string|array|false */ public function render_callback( WP_Customize_Partial $partial, $context = array() ) { unset( $partial, $context ); return false; } /** * Retrieves the data to export to the client via JSON. * * @since 4.5.0 * * @return array Array of parameters passed to the JavaScript. */ public function json() { $exports = array( 'settings' => $this->settings, 'primarySetting' => $this->primary_setting, 'selector' => $this->selector, 'type' => $this->type, 'fallbackRefresh' => $this->fallback_refresh, 'containerInclusive' => $this->container_inclusive, ); return $exports; } /** * Checks if the user can refresh this partial. * * Returns false if the user cannot manipulate one of the associated settings, * or if one of the associated settings does not exist. * * @since 4.5.0 * * @return bool False if user can't edit one of the related settings, * or if one of the associated settings does not exist. */ final public function check_capabilities() { if ( ! empty( $this->capability ) && ! current_user_can( $this->capability ) ) { return false; } foreach ( $this->settings as $setting_id ) { $setting = $this->component->manager->get_setting( $setting_id ); if ( ! $setting || ! $setting->check_capabilities() ) { return false; } } return true; } } https-detection-20260605122528.php 0000666 00000013341 15226762357 0012006 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-requests.php 0000666 00000004275 15226762357 0010266 0 ustar 00 <?php /** * Requests for PHP * * Inspired by Requests for Python. * * Based on concepts from SimplePie_File, RequestCore and WP_Http. * * @package Requests * * @deprecated 6.2.0 */ /* * Integrators who cannot yet upgrade to the PSR-4 class names can silence deprecations * by defining a `REQUESTS_SILENCE_PSR0_DEPRECATIONS` constant and setting it to `true`. * The constant needs to be defined before this class is required. */ if (!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS') || REQUESTS_SILENCE_PSR0_DEPRECATIONS !== true) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error trigger_error( 'The PSR-0 `Requests_...` class names in the Requests library are deprecated.' . ' Switch to the PSR-4 `WpOrg\Requests\...` class names at your earliest convenience.', E_USER_DEPRECATED ); // Prevent the deprecation notice from being thrown twice. if (!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS')) { define('REQUESTS_SILENCE_PSR0_DEPRECATIONS', true); } } require_once __DIR__ . '/Requests/src/Requests.php'; /** * Requests for PHP * * Inspired by Requests for Python. * * Based on concepts from SimplePie_File, RequestCore and WP_Http. * * @package Requests * * @deprecated 6.2.0 Use `WpOrg\Requests\Requests` instead for the actual functionality and * use `WpOrg\Requests\Autoload` for the autoloading. */ class Requests extends WpOrg\Requests\Requests { /** * Deprecated autoloader for Requests. * * @deprecated 6.2.0 Use the `WpOrg\Requests\Autoload::load()` method instead. * * @codeCoverageIgnore * * @param string $class Class name to load */ public static function autoloader($class) { if (class_exists('WpOrg\Requests\Autoload') === false) { require_once __DIR__ . '/Requests/src/Autoload.php'; } return WpOrg\Requests\Autoload::load($class); } /** * Register the built-in autoloader * * @deprecated 6.2.0 Include the `WpOrg\Requests\Autoload` class and * call `WpOrg\Requests\Autoload::register()` instead. * * @codeCoverageIgnore */ public static function register_autoloader() { require_once __DIR__ . '/Requests/src/Autoload.php'; WpOrg\Requests\Autoload::register(); } } https-migration-20260605165544.php 0000666 00000011205 15226762357 0012023 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-http-curl.php 0000666 00000031715 15226762357 0010760 0 ustar 00 <?php /** * HTTP API: WP_Http_Curl class * * @package WordPress * @subpackage HTTP * @since 4.4.0 */ /** * Core class used to integrate Curl as an HTTP transport. * * HTTP request method uses Curl extension to retrieve the url. * * Requires the Curl extension to be installed. * * @since 2.7.0 * @deprecated 6.4.0 Use WP_Http * @see WP_Http */ #[AllowDynamicProperties] class WP_Http_Curl { /** * Temporary header storage for during requests. * * @since 3.2.0 * @var string */ private $headers = ''; /** * Temporary body storage for during requests. * * @since 3.6.0 * @var string */ private $body = ''; /** * The maximum amount of data to receive from the remote server. * * @since 3.6.0 * @var int|false */ private $max_body_length = false; /** * The file resource used for streaming to file. * * @since 3.6.0 * @var resource|false */ private $stream_handle = false; /** * The total bytes written in the current request. * * @since 4.1.0 * @var int */ private $bytes_written_total = 0; /** * Send a HTTP request to a URI using cURL extension. * * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error */ public function request( $url, $args = array() ) { $defaults = array( 'method' => 'GET', 'timeout' => 5, 'redirection' => 5, 'httpversion' => '1.0', 'blocking' => true, 'headers' => array(), 'body' => null, 'cookies' => array(), 'decompress' => false, 'stream' => false, 'filename' => null, ); $parsed_args = wp_parse_args( $args, $defaults ); if ( isset( $parsed_args['headers']['User-Agent'] ) ) { $parsed_args['user-agent'] = $parsed_args['headers']['User-Agent']; unset( $parsed_args['headers']['User-Agent'] ); } elseif ( isset( $parsed_args['headers']['user-agent'] ) ) { $parsed_args['user-agent'] = $parsed_args['headers']['user-agent']; unset( $parsed_args['headers']['user-agent'] ); } // Construct Cookie: header if any cookies are set. WP_Http::buildCookieHeader( $parsed_args ); $handle = curl_init(); // cURL offers really easy proxy support. $proxy = new WP_HTTP_Proxy(); if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) { curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP ); curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() ); curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() ); if ( $proxy->use_authentication() ) { curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY ); curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() ); } } $is_local = isset( $parsed_args['local'] ) && $parsed_args['local']; $ssl_verify = isset( $parsed_args['sslverify'] ) && $parsed_args['sslverify']; if ( $is_local ) { /** This filter is documented in wp-includes/class-wp-http-streams.php */ $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify, $url ); } elseif ( ! $is_local ) { /** This filter is documented in wp-includes/class-wp-http.php */ $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify, $url ); } /* * CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since. * a value of 0 will allow an unlimited timeout. */ $timeout = (int) ceil( $parsed_args['timeout'] ); curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout ); curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout ); curl_setopt( $handle, CURLOPT_URL, $url ); curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true ); curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( true === $ssl_verify ) ? 2 : false ); curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify ); if ( $ssl_verify ) { curl_setopt( $handle, CURLOPT_CAINFO, $parsed_args['sslcertificates'] ); } curl_setopt( $handle, CURLOPT_USERAGENT, $parsed_args['user-agent'] ); /* * The option doesn't work with safe mode or when open_basedir is set, and there's * a bug #17490 with redirected POST requests, so handle redirections outside Curl. */ curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false ); curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS ); switch ( $parsed_args['method'] ) { case 'HEAD': curl_setopt( $handle, CURLOPT_NOBODY, true ); break; case 'POST': curl_setopt( $handle, CURLOPT_POST, true ); curl_setopt( $handle, CURLOPT_POSTFIELDS, $parsed_args['body'] ); break; case 'PUT': curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' ); curl_setopt( $handle, CURLOPT_POSTFIELDS, $parsed_args['body'] ); break; default: curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $parsed_args['method'] ); if ( ! is_null( $parsed_args['body'] ) ) { curl_setopt( $handle, CURLOPT_POSTFIELDS, $parsed_args['body'] ); } break; } if ( true === $parsed_args['blocking'] ) { curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) ); curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) ); } curl_setopt( $handle, CURLOPT_HEADER, false ); if ( isset( $parsed_args['limit_response_size'] ) ) { $this->max_body_length = (int) $parsed_args['limit_response_size']; } else { $this->max_body_length = false; } // If streaming to a file open a file handle, and setup our curl streaming handler. if ( $parsed_args['stream'] ) { if ( ! WP_DEBUG ) { $this->stream_handle = @fopen( $parsed_args['filename'], 'w+' ); } else { $this->stream_handle = fopen( $parsed_args['filename'], 'w+' ); } if ( ! $this->stream_handle ) { return new WP_Error( 'http_request_failed', sprintf( /* translators: 1: fopen(), 2: File name. */ __( 'Could not open handle for %1$s to %2$s.' ), 'fopen()', $parsed_args['filename'] ) ); } } else { $this->stream_handle = false; } if ( ! empty( $parsed_args['headers'] ) ) { // cURL expects full header strings in each element. $headers = array(); foreach ( $parsed_args['headers'] as $name => $value ) { $headers[] = "{$name}: $value"; } curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers ); } if ( '1.0' === $parsed_args['httpversion'] ) { curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 ); } else { curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 ); } /** * Fires before the cURL request is executed. * * Cookies are not currently handled by the HTTP API. This action allows * plugins to handle cookies themselves. * * @since 2.8.0 * * @param resource $handle The cURL handle returned by curl_init() (passed by reference). * @param array $parsed_args The HTTP request arguments. * @param string $url The request URL. */ do_action_ref_array( 'http_api_curl', array( &$handle, $parsed_args, $url ) ); // We don't need to return the body, so don't. Just execute request and return. if ( ! $parsed_args['blocking'] ) { curl_exec( $handle ); $curl_error = curl_error( $handle ); if ( $curl_error ) { if ( PHP_VERSION_ID < 80000 ) { // curl_close() has no effect as of PHP 8.0. curl_close( $handle ); } return new WP_Error( 'http_request_failed', $curl_error ); } if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ), true ) ) { if ( PHP_VERSION_ID < 80000 ) { // curl_close() has no effect as of PHP 8.0. curl_close( $handle ); } return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); } if ( PHP_VERSION_ID < 80000 ) { // curl_close() has no effect as of PHP 8.0. curl_close( $handle ); } return array( 'headers' => array(), 'body' => '', 'response' => array( 'code' => false, 'message' => false, ), 'cookies' => array(), ); } curl_exec( $handle ); $processed_headers = WP_Http::processHeaders( $this->headers, $url ); $body = $this->body; $bytes_written_total = $this->bytes_written_total; $this->headers = ''; $this->body = ''; $this->bytes_written_total = 0; $curl_error = curl_errno( $handle ); // If an error occurred, or, no response. if ( $curl_error || ( 0 === strlen( $body ) && empty( $processed_headers['headers'] ) ) ) { if ( CURLE_WRITE_ERROR /* 23 */ === $curl_error ) { if ( ! $this->max_body_length || $this->max_body_length !== $bytes_written_total ) { if ( $parsed_args['stream'] ) { if ( PHP_VERSION_ID < 80000 ) { // curl_close() has no effect as of PHP 8.0. curl_close( $handle ); } fclose( $this->stream_handle ); return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) ); } else { if ( PHP_VERSION_ID < 80000 ) { // curl_close() has no effect as of PHP 8.0. curl_close( $handle ); } return new WP_Error( 'http_request_failed', curl_error( $handle ) ); } } } else { $curl_error = curl_error( $handle ); if ( $curl_error ) { if ( PHP_VERSION_ID < 80000 ) { // curl_close() has no effect as of PHP 8.0. curl_close( $handle ); } return new WP_Error( 'http_request_failed', $curl_error ); } } if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ), true ) ) { if ( PHP_VERSION_ID < 80000 ) { // curl_close() has no effect as of PHP 8.0. curl_close( $handle ); } return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); } } if ( PHP_VERSION_ID < 80000 ) { // curl_close() has no effect as of PHP 8.0. curl_close( $handle ); } if ( $parsed_args['stream'] ) { fclose( $this->stream_handle ); } $response = array( 'headers' => $processed_headers['headers'], 'body' => null, 'response' => $processed_headers['response'], 'cookies' => $processed_headers['cookies'], 'filename' => $parsed_args['filename'], ); // Handle redirects. $redirect_response = WP_Http::handle_redirects( $url, $parsed_args, $response ); if ( false !== $redirect_response ) { return $redirect_response; } if ( true === $parsed_args['decompress'] && true === WP_Http_Encoding::should_decode( $processed_headers['headers'] ) ) { $body = WP_Http_Encoding::decompress( $body ); } $response['body'] = $body; return $response; } /** * Grabs the headers of the cURL request. * * Each header is sent individually to this callback, and is appended to the `$header` property * for temporary storage. * * @since 3.2.0 * * @param resource $handle cURL handle. * @param string $headers cURL request headers. * @return int Length of the request headers. */ private function stream_headers( $handle, $headers ) { $this->headers .= $headers; return strlen( $headers ); } /** * Grabs the body of the cURL request. * * The contents of the document are passed in chunks, and are appended to the `$body` * property for temporary storage. Returning a length shorter than the length of * `$data` passed in will cause cURL to abort the request with `CURLE_WRITE_ERROR`. * * @since 3.6.0 * * @param resource $handle cURL handle. * @param string $data cURL request body. * @return int Total bytes of data written. */ private function stream_body( $handle, $data ) { $data_length = strlen( $data ); if ( $this->max_body_length && ( $this->bytes_written_total + $data_length ) > $this->max_body_length ) { $data_length = ( $this->max_body_length - $this->bytes_written_total ); $data = substr( $data, 0, $data_length ); } if ( $this->stream_handle ) { $bytes_written = fwrite( $this->stream_handle, $data ); } else { $this->body .= $data; $bytes_written = $data_length; } $this->bytes_written_total += $bytes_written; // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR. return $bytes_written; } /** * Determines whether this class can be used for retrieving a URL. * * @since 2.7.0 * * @param array $args Optional. Array of request arguments. Default empty array. * @return bool False means this class can not be used, true means it can. */ public static function test( $args = array() ) { if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) ) { return false; } $is_ssl = isset( $args['ssl'] ) && $args['ssl']; if ( $is_ssl ) { $curl_version = curl_version(); // Check whether this cURL version support SSL requests. if ( ! ( CURL_VERSION_SSL & $curl_version['features'] ) ) { return false; } } /** * Filters whether cURL can be used as a transport for retrieving a URL. * * @since 2.7.0 * * @param bool $use_class Whether the class can be used. Default true. * @param array $args An array of request arguments. */ return apply_filters( 'use_curl_transport', true, $args ); } } https-migration-20260605213309.php 0000666 00000011205 15226762357 0012014 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260606072910.php 0000666 00000011205 15226762357 0012016 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-customize-header-image-control.php 0000666 00000017535 15226762357 0015050 0 ustar 00 <?php /** * Customize API: WP_Customize_Header_Image_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Header Image Control class. * * @since 3.4.0 * * @see WP_Customize_Image_Control */ class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control { /** * Customize control type. * * @since 4.2.0 * @var string */ public $type = 'header'; /** * Uploaded header images. * * @since 3.9.0 * @var string */ public $uploaded_headers; /** * Default header images. * * @since 3.9.0 * @var string */ public $default_headers; /** * Constructor. * * @since 3.4.0 * * @param WP_Customize_Manager $manager Customizer bootstrap instance. */ public function __construct( $manager ) { parent::__construct( $manager, 'header_image', array( 'label' => __( 'Header Image' ), 'settings' => array( 'default' => 'header_image', 'data' => 'header_image_data', ), 'section' => 'header_image', 'removed' => 'remove-header', 'get_url' => 'get_header_image', ) ); } /** */ public function enqueue() { wp_enqueue_media(); wp_enqueue_script( 'customize-views' ); $this->prepare_control(); wp_localize_script( 'customize-views', '_wpCustomizeHeader', array( 'data' => array( 'width' => absint( get_theme_support( 'custom-header', 'width' ) ), 'height' => absint( get_theme_support( 'custom-header', 'height' ) ), 'flex-width' => absint( get_theme_support( 'custom-header', 'flex-width' ) ), 'flex-height' => absint( get_theme_support( 'custom-header', 'flex-height' ) ), 'currentImgSrc' => $this->get_current_image_src(), ), 'nonces' => array( 'add' => wp_create_nonce( 'header-add' ), 'remove' => wp_create_nonce( 'header-remove' ), ), 'uploads' => $this->uploaded_headers, 'defaults' => $this->default_headers, ) ); parent::enqueue(); } /** * @global Custom_Image_Header $custom_image_header */ public function prepare_control() { global $custom_image_header; if ( empty( $custom_image_header ) ) { return; } add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_header_image_template' ) ); // Process default headers and uploaded headers. $custom_image_header->process_default_headers(); $this->default_headers = $custom_image_header->get_default_header_images(); $this->uploaded_headers = $custom_image_header->get_uploaded_header_images(); } /** */ public function print_header_image_template() { ?> <script type="text/template" id="tmpl-header-choice"> <# if (data.random) { #> <button type="button" class="button display-options random"> <span class="dashicons dashicons-randomize dice"></span> <# if ( data.type === 'uploaded' ) { #> <?php _e( 'Randomize uploaded headers' ); ?> <# } else if ( data.type === 'default' ) { #> <?php _e( 'Randomize suggested headers' ); ?> <# } #> </button> <# } else { #> <button type="button" class="choice thumbnail" data-customize-image-value="{{data.header.url}}" data-customize-header-image-data="{{JSON.stringify(data.header)}}"> <span class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ _e( 'Set image' ); ?> </span> <img src="{{data.header.thumbnail_url}}" alt="{{data.header.alt_text || data.header.description}}" /> </button> <# if ( data.type === 'uploaded' ) { #> <button type="button" class="dashicons dashicons-no close"> <span class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ _e( 'Remove image' ); ?> </span> </button> <# } #> <# } #> </script> <script type="text/template" id="tmpl-header-current"> <# if (data.choice) { #> <# if (data.random) { #> <div class="placeholder"> <span class="dashicons dashicons-randomize dice"></span> <# if ( data.type === 'uploaded' ) { #> <?php _e( 'Randomizing uploaded headers' ); ?> <# } else if ( data.type === 'default' ) { #> <?php _e( 'Randomizing suggested headers' ); ?> <# } #> </div> <# } else { #> <img src="{{data.header.thumbnail_url}}" alt="{{data.header.alt_text || data.header.description}}" /> <# } #> <# } #> </script> <?php } /** * @return string|void */ public function get_current_image_src() { $src = $this->value(); if ( isset( $this->get_url ) ) { $src = call_user_func( $this->get_url, $src ); return $src; } } /** */ public function render_content() { $visibility = $this->get_current_image_src() ? '' : ' style="display:none" '; $width = absint( get_theme_support( 'custom-header', 'width' ) ); $height = absint( get_theme_support( 'custom-header', 'height' ) ); ?> <div class="customize-control-content"> <?php if ( current_theme_supports( 'custom-header', 'video' ) ) { echo '<span class="customize-control-title">' . $this->label . '</span>'; } ?> <div class="customize-control-notifications-container"></div> <p class="customizer-section-intro customize-control-description"> <?php if ( current_theme_supports( 'custom-header', 'video' ) ) { _e( 'Click “Add Image” to upload an image file from your computer. Your theme works best with an image that matches the size of your video — you’ll be able to crop your image once you upload it for a perfect fit.' ); } elseif ( $width && $height ) { printf( /* translators: %s: Header size in pixels. */ __( 'Click “Add Image” to upload an image file from your computer. Your theme works best with an image with a header size of %s pixels — you’ll be able to crop your image once you upload it for a perfect fit.' ), sprintf( '<strong>%s × %s</strong>', $width, $height ) ); } elseif ( $width ) { printf( /* translators: %s: Header width in pixels. */ __( 'Click “Add Image” to upload an image file from your computer. Your theme works best with an image with a header width of %s pixels — you’ll be able to crop your image once you upload it for a perfect fit.' ), sprintf( '<strong>%s</strong>', $width ) ); } else { printf( /* translators: %s: Header height in pixels. */ __( 'Click “Add Image” to upload an image file from your computer. Your theme works best with an image with a header height of %s pixels — you’ll be able to crop your image once you upload it for a perfect fit.' ), sprintf( '<strong>%s</strong>', $height ) ); } ?> </p> <div class="current"> <label for="header_image-button"> <span class="customize-control-title"> <?php _e( 'Current header' ); ?> </span> </label> <div class="container"> </div> </div> <div class="actions"> <?php if ( current_user_can( 'upload_files' ) ) : ?> <button type="button"<?php echo $visibility; ?> class="button remove" aria-label="<?php esc_attr_e( 'Hide header image' ); ?>"><?php _e( 'Hide image' ); ?></button> <button type="button" class="button new <?php echo ! $this->get_current_image_src() ? '' : 'customize-header-image-not-selected'; ?>" id="header_image-button" aria-label="<?php esc_attr_e( 'Add Header Image' ); ?>"><?php _e( 'Add Image' ); ?></button> <?php endif; ?> </div> <div class="choices"> <span class="customize-control-title header-previously-uploaded"> <?php _ex( 'Previously uploaded', 'custom headers' ); ?> </span> <div class="uploaded"> <div class="list"> </div> </div> <span class="customize-control-title header-default"> <?php _ex( 'Suggested', 'custom headers' ); ?> </span> <div class="default"> <div class="list"> </div> </div> </div> </div> <?php } } string-20260621192422.php 0000666 00000020233 15226762357 0010172 0 ustar 00 <?php /** * Parses unified or context diffs output from eg. the diff utility. * * Example: * <code> * $patch = file_get_contents('example.patch'); * $diff = new Text_Diff('string', array($patch)); * $renderer = new Text_Diff_Renderer_inline(); * echo $renderer->render($diff); * </code> * * Copyright 2005 Örjan Persson <o@42mm.org> * Copyright 2005-2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did * not receive this file, see https://opensource.org/license/lgpl-2-1/. * * @author Örjan Persson <o@42mm.org> * @package Text_Diff * @since 0.2.0 */ class Text_Diff_Engine_string { /** * Parses a unified or context diff. * * First param contains the whole diff and the second can be used to force * a specific diff type. If the second parameter is 'autodetect', the * diff will be examined to find out which type of diff this is. * * @param string $diff The diff content. * @param string $mode The diff mode of the content in $diff. One of * 'context', 'unified', or 'autodetect'. * * @return array List of all diff operations. */ function diff($diff, $mode = 'autodetect') { // Detect line breaks. $lnbr = "\n"; if (strpos($diff, "\r\n") !== false) { $lnbr = "\r\n"; } elseif (strpos($diff, "\r") !== false) { $lnbr = "\r"; } // Make sure we have a line break at the EOF. if (substr($diff, -strlen($lnbr)) != $lnbr) { $diff .= $lnbr; } if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { return PEAR::raiseError('Type of diff is unsupported'); } if ($mode == 'autodetect') { $context = strpos($diff, '***'); $unified = strpos($diff, '---'); if ($context === $unified) { return PEAR::raiseError('Type of diff could not be detected'); } elseif ($context === false || $unified === false) { $mode = $context !== false ? 'context' : 'unified'; } else { $mode = $context < $unified ? 'context' : 'unified'; } } // Split by new line and remove the diff header, if there is one. $diff = explode($lnbr, $diff); if (($mode == 'context' && strpos($diff[0], '***') === 0) || ($mode == 'unified' && strpos($diff[0], '---') === 0)) { array_shift($diff); array_shift($diff); } if ($mode == 'context') { return $this->parseContextDiff($diff); } else { return $this->parseUnifiedDiff($diff); } } /** * Parses an array containing the unified diff. * * @param array $diff Array of lines. * * @return array List of all diff operations. */ function parseUnifiedDiff($diff) { $edits = array(); $end = count($diff) - 1; for ($i = 0; $i < $end;) { $diff1 = array(); switch (substr($diff[$i], 0, 1)) { case ' ': do { $diff1[] = substr($diff[$i], 1); } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); $edits[] = new Text_Diff_Op_copy($diff1); break; case '+': // get all new lines do { $diff1[] = substr($diff[$i], 1); } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); $edits[] = new Text_Diff_Op_add($diff1); break; case '-': // get changed or removed lines $diff2 = array(); do { $diff1[] = substr($diff[$i], 1); } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); while ($i < $end && substr($diff[$i], 0, 1) == '+') { $diff2[] = substr($diff[$i++], 1); } if (count($diff2) == 0) { $edits[] = new Text_Diff_Op_delete($diff1); } else { $edits[] = new Text_Diff_Op_change($diff1, $diff2); } break; default: $i++; break; } } return $edits; } /** * Parses an array containing the context diff. * * @param array $diff Array of lines. * * @return array List of all diff operations. */ function parseContextDiff(&$diff) { $edits = array(); $i = $max_i = $j = $max_j = 0; $end = count($diff) - 1; while ($i < $end && $j < $end) { while ($i >= $max_i && $j >= $max_j) { // Find the boundaries of the diff output of the two files for ($i = $j; $i < $end && substr($diff[$i], 0, 3) == '***'; $i++); for ($max_i = $i; $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; $max_i++); for ($j = $max_i; $j < $end && substr($diff[$j], 0, 3) == '---'; $j++); for ($max_j = $j; $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; $max_j++); } // find what hasn't been changed $array = array(); while ($i < $max_i && $j < $max_j && strcmp($diff[$i], $diff[$j]) == 0) { $array[] = substr($diff[$i], 2); $i++; $j++; } while ($i < $max_i && ($max_j-$j) <= 1) { if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { break; } $array[] = substr($diff[$i++], 2); } while ($j < $max_j && ($max_i-$i) <= 1) { if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { break; } $array[] = substr($diff[$j++], 2); } if (count($array) > 0) { $edits[] = new Text_Diff_Op_copy($array); } if ($i < $max_i) { $diff1 = array(); switch (substr($diff[$i], 0, 1)) { case '!': $diff2 = array(); do { $diff1[] = substr($diff[$i], 2); if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { $diff2[] = substr($diff[$j++], 2); } } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); $edits[] = new Text_Diff_Op_change($diff1, $diff2); break; case '+': do { $diff1[] = substr($diff[$i], 2); } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); $edits[] = new Text_Diff_Op_add($diff1); break; case '-': do { $diff1[] = substr($diff[$i], 2); } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); $edits[] = new Text_Diff_Op_delete($diff1); break; } } if ($j < $max_j) { $diff2 = array(); switch (substr($diff[$j], 0, 1)) { case '+': do { $diff2[] = substr($diff[$j++], 2); } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); $edits[] = new Text_Diff_Op_add($diff2); break; case '-': do { $diff2[] = substr($diff[$j++], 2); } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); $edits[] = new Text_Diff_Op_delete($diff2); break; } } } return $edits; } } class-wp-embed.php 0000666 00000037073 15226762357 0010115 0 ustar 00 <?php /** * API for easily embedding rich media such as videos and images into content. * * @package WordPress * @subpackage Embed * @since 2.9.0 */ #[AllowDynamicProperties] class WP_Embed { public $handlers = array(); public $post_ID; public $usecache = true; public $linkifunknown = true; public $last_attr = array(); public $last_url = ''; /** * When a URL cannot be embedded, return false instead of returning a link * or the URL. * * Bypasses the {@see 'embed_maybe_make_link'} filter. * * @var bool */ public $return_false_on_fail = false; /** * Constructor */ public function __construct() { // Hack to get the [embed] shortcode to run before wpautop(). add_filter( 'the_content', array( $this, 'run_shortcode' ), 8 ); add_filter( 'widget_text_content', array( $this, 'run_shortcode' ), 8 ); add_filter( 'widget_block_content', array( $this, 'run_shortcode' ), 8 ); // Shortcode placeholder for strip_shortcodes(). add_shortcode( 'embed', '__return_false' ); // Attempts to embed all URLs in a post. add_filter( 'the_content', array( $this, 'autoembed' ), 8 ); add_filter( 'widget_text_content', array( $this, 'autoembed' ), 8 ); add_filter( 'widget_block_content', array( $this, 'autoembed' ), 8 ); // After a post is saved, cache oEmbed items via Ajax. add_action( 'edit_form_advanced', array( $this, 'maybe_run_ajax_cache' ) ); add_action( 'edit_page_form', array( $this, 'maybe_run_ajax_cache' ) ); } /** * Processes the [embed] shortcode. * * Since the [embed] shortcode needs to be run earlier than other shortcodes, * this function removes all existing shortcodes, registers the [embed] shortcode, * calls do_shortcode(), and then re-registers the old shortcodes. * * @global array $shortcode_tags * * @param string $content Content to parse. * @return string Content with shortcode parsed. */ public function run_shortcode( $content ) { global $shortcode_tags; // Back up current registered shortcodes and clear them all out. $orig_shortcode_tags = $shortcode_tags; remove_all_shortcodes(); add_shortcode( 'embed', array( $this, 'shortcode' ) ); // Do the shortcode (only the [embed] one is registered). $content = do_shortcode( $content, true ); // Put the original shortcodes back. $shortcode_tags = $orig_shortcode_tags; return $content; } /** * If a post/page was saved, then output JavaScript to make * an Ajax request that will call WP_Embed::cache_oembed(). */ public function maybe_run_ajax_cache() { $post = get_post(); if ( ! $post || empty( $_GET['message'] ) ) { return; } ?> <script type="text/javascript"> jQuery( function($) { $.get("<?php echo esc_url( admin_url( 'admin-ajax.php', 'relative' ) ) . '?action=oembed-cache&post=' . $post->ID; ?>"); } ); </script> <?php } /** * Registers an embed handler. * * Do not use this function directly, use wp_embed_register_handler() instead. * * This function should probably also only be used for sites that do not support oEmbed. * * @param string $id An internal ID/name for the handler. Needs to be unique. * @param string $regex The regex that will be used to see if this handler should be used for a URL. * @param callable $callback The callback function that will be called if the regex is matched. * @param int $priority Optional. Used to specify the order in which the registered handlers will be tested. * Lower numbers correspond with earlier testing, and handlers with the same priority are * tested in the order in which they were added to the action. Default 10. */ public function register_handler( $id, $regex, $callback, $priority = 10 ) { $this->handlers[ $priority ][ $id ] = array( 'regex' => $regex, 'callback' => $callback, ); } /** * Unregisters a previously-registered embed handler. * * Do not use this function directly, use wp_embed_unregister_handler() instead. * * @param string $id The handler ID that should be removed. * @param int $priority Optional. The priority of the handler to be removed (default: 10). */ public function unregister_handler( $id, $priority = 10 ) { unset( $this->handlers[ $priority ][ $id ] ); } /** * Returns embed HTML for a given URL from embed handlers. * * Attempts to convert a URL into embed HTML by checking the URL * against the regex of the registered embed handlers. * * @since 5.5.0 * * @param array $attr { * Shortcode attributes. Optional. * * @type int $width Width of the embed in pixels. * @type int $height Height of the embed in pixels. * } * @param string $url The URL attempting to be embedded. * @return string|false The embed HTML on success, false otherwise. */ public function get_embed_handler_html( $attr, $url ) { $rawattr = $attr; $attr = wp_parse_args( $attr, wp_embed_defaults( $url ) ); ksort( $this->handlers ); foreach ( $this->handlers as $priority => $handlers ) { foreach ( $handlers as $id => $handler ) { if ( preg_match( $handler['regex'], $url, $matches ) && is_callable( $handler['callback'] ) ) { $return = call_user_func( $handler['callback'], $matches, $attr, $url, $rawattr ); if ( false !== $return ) { /** * Filters the returned embed HTML. * * @since 2.9.0 * * @see WP_Embed::shortcode() * * @param string $return The HTML result of the shortcode. * @param string $url The embed URL. * @param array $attr An array of shortcode attributes. */ return apply_filters( 'embed_handler_html', $return, $url, $attr ); } } } } return false; } /** * The do_shortcode() callback function. * * Attempts to convert a URL into embed HTML. Starts by checking the URL against the regex of * the registered embed handlers. If none of the regex matches and it's enabled, then the URL * will be given to the WP_oEmbed class. * * @param array $attr { * Shortcode attributes. Optional. * * @type int $width Width of the embed in pixels. * @type int $height Height of the embed in pixels. * } * @param string $url The URL attempting to be embedded. * @return string|false The embed HTML on success, otherwise the original URL. * `->maybe_make_link()` can return false on failure. */ public function shortcode( $attr, $url = '' ) { $post = get_post(); if ( empty( $url ) && ! empty( $attr['src'] ) ) { $url = $attr['src']; } $this->last_url = $url; if ( empty( $url ) ) { $this->last_attr = $attr; return ''; } $rawattr = $attr; $attr = wp_parse_args( $attr, wp_embed_defaults( $url ) ); $this->last_attr = $attr; /* * KSES converts & into & and we need to undo this. * See https://core.trac.wordpress.org/ticket/11311 */ $url = str_replace( '&', '&', $url ); // Look for known internal handlers. $embed_handler_html = $this->get_embed_handler_html( $rawattr, $url ); if ( false !== $embed_handler_html ) { return $embed_handler_html; } $post_id = ( ! empty( $post->ID ) ) ? $post->ID : null; // Potentially set by WP_Embed::cache_oembed(). if ( ! empty( $this->post_ID ) ) { $post_id = $this->post_ID; } // Check for a cached result (stored as custom post or in the post meta). $key_suffix = md5( $url . serialize( $attr ) ); $cachekey = '_oembed_' . $key_suffix; $cachekey_time = '_oembed_time_' . $key_suffix; /** * Filters the oEmbed TTL value (time to live). * * @since 4.0.0 * * @param int $time Time to live (in seconds). * @param string $url The attempted embed URL. * @param array $attr An array of shortcode attributes. * @param int $post_id Post ID. */ $ttl = apply_filters( 'oembed_ttl', DAY_IN_SECONDS, $url, $attr, $post_id ); $cache = ''; $cache_time = 0; $cached_post_id = $this->find_oembed_post_id( $key_suffix ); if ( $post_id ) { $cache = get_post_meta( $post_id, $cachekey, true ); $cache_time = get_post_meta( $post_id, $cachekey_time, true ); if ( ! $cache_time ) { $cache_time = 0; } } elseif ( $cached_post_id ) { $cached_post = get_post( $cached_post_id ); $cache = $cached_post->post_content; $cache_time = strtotime( $cached_post->post_modified_gmt ); } $cached_recently = ( time() - $cache_time ) < $ttl; if ( $this->usecache || $cached_recently ) { // Failures are cached. Serve one if we're using the cache. if ( '{{unknown}}' === $cache ) { return $this->maybe_make_link( $url ); } if ( ! empty( $cache ) ) { /** * Filters the cached oEmbed HTML. * * @since 2.9.0 * * @see WP_Embed::shortcode() * * @param string $cache The cached HTML result, stored in post meta. * @param string $url The attempted embed URL. * @param array $attr An array of shortcode attributes. * @param int $post_id Post ID. */ return apply_filters( 'embed_oembed_html', $cache, $url, $attr, $post_id ); } } /** * Filters whether to inspect the given URL for discoverable link tags. * * @since 2.9.0 * @since 4.4.0 The default value changed to true. * * @see WP_oEmbed::discover() * * @param bool $enable Whether to enable `<link>` tag discovery. Default true. */ $attr['discover'] = apply_filters( 'embed_oembed_discover', true ); // Use oEmbed to get the HTML. $html = wp_oembed_get( $url, $attr ); if ( $post_id ) { if ( $html ) { update_post_meta( $post_id, $cachekey, $html ); update_post_meta( $post_id, $cachekey_time, time() ); } elseif ( ! $cache ) { update_post_meta( $post_id, $cachekey, '{{unknown}}' ); } } else { $has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' ); if ( $has_kses ) { // Prevent KSES from corrupting JSON in post_content. kses_remove_filters(); } $insert_post_args = array( 'post_name' => $key_suffix, 'post_status' => 'publish', 'post_type' => 'oembed_cache', ); if ( $html ) { if ( $cached_post_id ) { wp_update_post( wp_slash( array( 'ID' => $cached_post_id, 'post_content' => $html, ) ) ); } else { wp_insert_post( wp_slash( array_merge( $insert_post_args, array( 'post_content' => $html, ) ) ) ); } } elseif ( ! $cache ) { wp_insert_post( wp_slash( array_merge( $insert_post_args, array( 'post_content' => '{{unknown}}', ) ) ) ); } if ( $has_kses ) { kses_init_filters(); } } // If there was a result, return it. if ( $html ) { /** This filter is documented in wp-includes/class-wp-embed.php */ return apply_filters( 'embed_oembed_html', $html, $url, $attr, $post_id ); } // Still unknown. return $this->maybe_make_link( $url ); } /** * Deletes all oEmbed caches. Unused by core as of 4.0.0. * * @param int $post_id Post ID to delete the caches for. */ public function delete_oembed_caches( $post_id ) { $post_metas = get_post_custom_keys( $post_id ); if ( empty( $post_metas ) ) { return; } foreach ( $post_metas as $post_meta_key ) { if ( str_starts_with( $post_meta_key, '_oembed_' ) ) { delete_post_meta( $post_id, $post_meta_key ); } } } /** * Triggers a caching of all oEmbed results. * * @param int $post_id Post ID to do the caching for. */ public function cache_oembed( $post_id ) { $post = get_post( $post_id ); $post_types = get_post_types( array( 'show_ui' => true ) ); /** * Filters the array of post types to cache oEmbed results for. * * @since 2.9.0 * * @param string[] $post_types Array of post type names to cache oEmbed results for. Defaults to post types with `show_ui` set to true. */ $cache_oembed_types = apply_filters( 'embed_cache_oembed_types', $post_types ); if ( empty( $post->ID ) || ! in_array( $post->post_type, $cache_oembed_types, true ) ) { return; } // Trigger a caching. if ( ! empty( $post->post_content ) ) { $this->post_ID = $post->ID; $this->usecache = false; $content = $this->run_shortcode( $post->post_content ); $this->autoembed( $content ); $this->usecache = true; } } /** * Passes any unlinked URLs that are on their own line to WP_Embed::shortcode() for potential embedding. * * @see WP_Embed::autoembed_callback() * * @param string $content The content to be searched. * @return string Potentially modified $content. */ public function autoembed( $content ) { // Replace line breaks from all HTML elements with placeholders. $content = wp_replace_in_html_tags( $content, array( "\n" => '<!-- wp-line-break -->' ) ); if ( preg_match( '#(^|\s|>)https?://#i', $content ) ) { // Find URLs on their own line. $content = preg_replace_callback( '|^(\s*)(https?://[^\s<>"]+)(\s*)$|im', array( $this, 'autoembed_callback' ), $content ); // Find URLs in their own paragraph. $content = preg_replace_callback( '|(<p(?: [^>]*)?>\s*)(https?://[^\s<>"]+)(\s*<\/p>)|i', array( $this, 'autoembed_callback' ), $content ); } // Put the line breaks back. return str_replace( '<!-- wp-line-break -->', "\n", $content ); } /** * Callback function for WP_Embed::autoembed(). * * @param array $matches A regex match array. * @return string The embed HTML on success, otherwise the original URL. */ public function autoembed_callback( $matches ) { $oldval = $this->linkifunknown; $this->linkifunknown = false; $return = $this->shortcode( array(), $matches[2] ); $this->linkifunknown = $oldval; return $matches[1] . $return . $matches[3]; } /** * Conditionally makes a hyperlink based on an internal class variable. * * @param string $url URL to potentially be linked. * @return string|false Linked URL or the original URL. False if 'return_false_on_fail' is true. */ public function maybe_make_link( $url ) { if ( $this->return_false_on_fail ) { return false; } $output = ( $this->linkifunknown ) ? '<a href="' . esc_url( $url ) . '">' . esc_html( $url ) . '</a>' : $url; /** * Filters the returned, maybe-linked embed URL. * * @since 2.9.0 * * @param string $output The linked or original URL. * @param string $url The original URL. */ return apply_filters( 'embed_maybe_make_link', $output, $url ); } /** * Finds the oEmbed cache post ID for a given cache key. * * @since 4.9.0 * * @param string $cache_key oEmbed cache key. * @return int|null Post ID on success, null on failure. */ public function find_oembed_post_id( $cache_key ) { $cache_group = 'oembed_cache_post'; $oembed_post_id = wp_cache_get( $cache_key, $cache_group ); if ( $oembed_post_id && 'oembed_cache' === get_post_type( $oembed_post_id ) ) { return $oembed_post_id; } $oembed_post_query = new WP_Query( array( 'post_type' => 'oembed_cache', 'post_status' => 'publish', 'name' => $cache_key, 'posts_per_page' => 1, 'no_found_rows' => true, 'cache_results' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'lazy_load_term_meta' => false, ) ); if ( ! empty( $oembed_post_query->posts ) ) { // Note: 'fields' => 'ids' is not being used in order to cache the post object as it will be needed. $oembed_post_id = $oembed_post_query->posts[0]->ID; wp_cache_set( $cache_key, $oembed_post_id, $cache_group ); return $oembed_post_id; } return null; } } class-wp-customize-background-image-control.php 0000666 00000002404 15226762357 0015724 0 ustar 00 <?php /** * Customize API: WP_Customize_Background_Image_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Background Image Control class. * * @since 3.4.0 * * @see WP_Customize_Image_Control */ class WP_Customize_Background_Image_Control extends WP_Customize_Image_Control { /** * Customize control type. * * @since 4.1.0 * @var string */ public $type = 'background'; /** * Constructor. * * @since 3.4.0 * @uses WP_Customize_Image_Control::__construct() * * @param WP_Customize_Manager $manager Customizer bootstrap instance. */ public function __construct( $manager ) { parent::__construct( $manager, 'background_image', array( 'label' => __( 'Background Image' ), 'section' => 'background_image', ) ); } /** * Enqueue control related scripts/styles. * * @since 4.1.0 */ public function enqueue() { parent::enqueue(); $custom_background = get_theme_support( 'custom-background' ); wp_localize_script( 'customize-controls', '_wpCustomizeBackground', array( 'defaults' => ! empty( $custom_background[0] ) ? $custom_background[0] : array(), 'nonces' => array( 'add' => wp_create_nonce( 'background-add' ), ), ) ); } } class-wp-customize-code-editor-control.php 0000666 00000004415 15226762357 0014727 0 ustar 00 <?php /** * Customize API: WP_Customize_Code_Editor_Control class * * @package WordPress * @subpackage Customize * @since 4.9.0 */ /** * Customize Code Editor Control class. * * @since 4.9.0 * * @see WP_Customize_Control */ class WP_Customize_Code_Editor_Control extends WP_Customize_Control { /** * Customize control type. * * @since 4.9.0 * @var string */ public $type = 'code_editor'; /** * Type of code that is being edited. * * @since 4.9.0 * @var string */ public $code_type = ''; /** * Code editor settings. * * @see wp_enqueue_code_editor() * @since 4.9.0 * @var array|false */ public $editor_settings = array(); /** * Enqueue control related scripts/styles. * * @since 4.9.0 */ public function enqueue() { $this->editor_settings = wp_enqueue_code_editor( array_merge( array( 'type' => $this->code_type, 'codemirror' => array( 'indentUnit' => 2, 'tabSize' => 2, ), ), $this->editor_settings ) ); } /** * Refresh the parameters passed to the JavaScript via JSON. * * @since 4.9.0 * * @see WP_Customize_Control::json() * * @return array Array of parameters passed to the JavaScript. */ public function json() { $json = parent::json(); $json['editor_settings'] = $this->editor_settings; $json['input_attrs'] = $this->input_attrs; return $json; } /** * Don't render the control content from PHP, as it's rendered via JS on load. * * @since 4.9.0 */ public function render_content() {} /** * Render a JS template for control display. * * @since 4.9.0 */ public function content_template() { ?> <# var elementIdPrefix = 'el' + String( Math.random() ); #> <# if ( data.label ) { #> <label for="{{ elementIdPrefix }}_editor" class="customize-control-title"> {{ data.label }} </label> <# } #> <# if ( data.description ) { #> <span class="description customize-control-description">{{{ data.description }}}</span> <# } #> <div class="customize-control-notifications-container"></div> <textarea id="{{ elementIdPrefix }}_editor" <# _.each( _.extend( { 'class': 'code' }, data.input_attrs ), function( value, key ) { #> {{{ key }}}="{{ value }}" <# }); #> ></textarea> <?php } } class-wp-theme-json.php 0000666 00000500773 15226762357 0011114 0 ustar 00 <?php /** * WP_Theme_JSON class * * @package WordPress * @subpackage Theme * @since 5.8.0 */ /** * Class that encapsulates the processing of structures that adhere to the theme.json spec. * * This class is for internal core usage and is not supposed to be used by extenders (plugins and/or themes). * This is a low-level API that may need to do breaking changes. Please, * use get_global_settings, get_global_styles, and get_global_stylesheet instead. * * @access private */ #[AllowDynamicProperties] class WP_Theme_JSON { /** * Container of data in theme.json format. * * @since 5.8.0 * @var array */ protected $theme_json = null; /** * Holds block metadata extracted from block.json * to be shared among all instances so we don't * process it twice. * * @since 5.8.0 * @since 6.1.0 Initialize as an empty array. * @var array */ protected static $blocks_metadata = array(); /** * The CSS selector for the top-level preset settings. * * @since 6.6.0 * @var string */ const ROOT_CSS_PROPERTIES_SELECTOR = ':root'; /** * The CSS selector for the top-level styles. * * @since 5.8.0 * @var string */ const ROOT_BLOCK_SELECTOR = 'body'; /** * The sources of data this object can represent. * * @since 5.8.0 * @since 6.1.0 Added 'blocks'. * @var string[] */ const VALID_ORIGINS = array( 'default', 'blocks', 'theme', 'custom', ); /** * Presets are a set of values that serve * to bootstrap some styles: colors, font sizes, etc. * * They are a unkeyed array of values such as: * * array( * array( * 'slug' => 'unique-name-within-the-set', * 'name' => 'Name for the UI', * <value_key> => 'value' * ), * ) * * This contains the necessary metadata to process them: * * - path => Where to find the preset within the settings section. * - prevent_override => Disables override of default presets by theme presets. * The relationship between whether to override the defaults * and whether the defaults are enabled is inverse: * - If defaults are enabled => theme presets should not be overridden * - If defaults are disabled => theme presets should be overridden * For example, a theme sets defaultPalette to false, * making the default palette hidden from the user. * In that case, we want all the theme presets to be present, * so they should override the defaults by setting this false. * - use_default_names => whether to use the default names * - value_key => the key that represents the value * - value_func => optionally, instead of value_key, a function to generate * the value that takes a preset as an argument * (either value_key or value_func should be present) * - css_vars => template string to use in generating the CSS Custom Property. * Example output: "--wp--preset--duotone--blue: <value>" will generate as many CSS Custom Properties as presets defined * substituting the $slug for the slug's value for each preset value. * - classes => array containing a structure with the classes to * generate for the presets, where for each array item * the key is the class name and the value the property name. * The "$slug" substring will be replaced by the slug of each preset. * For example: * 'classes' => array( * '.has-$slug-color' => 'color', * '.has-$slug-background-color' => 'background-color', * '.has-$slug-border-color' => 'border-color', * ) * - properties => array of CSS properties to be used by kses to * validate the content of each preset * by means of the remove_insecure_properties method. * * @since 5.8.0 * @since 5.9.0 Added the `color.duotone` and `typography.fontFamilies` presets, * `use_default_names` preset key, and simplified the metadata structure. * @since 6.0.0 Replaced `override` with `prevent_override` and updated the * `prevent_override` value for `color.duotone` to use `color.defaultDuotone`. * @since 6.2.0 Added 'shadow' presets. * @since 6.3.0 Replaced value_func for duotone with `null`. Custom properties are handled by class-wp-duotone.php. * @since 6.6.0 Added the `dimensions.aspectRatios` and `dimensions.defaultAspectRatios` presets. * Updated the 'prevent_override' value for font size presets to use 'typography.defaultFontSizes' * and spacing size presets to use `spacing.defaultSpacingSizes`. * @since 6.9.0 Added `border.radiusSizes`. * @var array */ const PRESETS_METADATA = array( array( 'path' => array( 'dimensions', 'aspectRatios' ), 'prevent_override' => array( 'dimensions', 'defaultAspectRatios' ), 'use_default_names' => false, 'value_key' => 'ratio', 'css_vars' => '--wp--preset--aspect-ratio--$slug', 'classes' => array(), 'properties' => array( 'aspect-ratio' ), ), array( 'path' => array( 'color', 'palette' ), 'prevent_override' => array( 'color', 'defaultPalette' ), 'use_default_names' => false, 'value_key' => 'color', 'css_vars' => '--wp--preset--color--$slug', 'classes' => array( '.has-$slug-color' => 'color', '.has-$slug-background-color' => 'background-color', '.has-$slug-border-color' => 'border-color', ), 'properties' => array( 'color', 'background-color', 'border-color' ), ), array( 'path' => array( 'color', 'gradients' ), 'prevent_override' => array( 'color', 'defaultGradients' ), 'use_default_names' => false, 'value_key' => 'gradient', 'css_vars' => '--wp--preset--gradient--$slug', 'classes' => array( '.has-$slug-gradient-background' => 'background' ), 'properties' => array( 'background' ), ), array( 'path' => array( 'color', 'duotone' ), 'prevent_override' => array( 'color', 'defaultDuotone' ), 'use_default_names' => false, 'value_func' => null, // CSS Custom Properties for duotone are handled by block supports in class-wp-duotone.php. 'css_vars' => null, 'classes' => array(), 'properties' => array( 'filter' ), ), array( 'path' => array( 'typography', 'fontSizes' ), 'prevent_override' => array( 'typography', 'defaultFontSizes' ), 'use_default_names' => true, 'value_func' => 'wp_get_typography_font_size_value', 'css_vars' => '--wp--preset--font-size--$slug', 'classes' => array( '.has-$slug-font-size' => 'font-size' ), 'properties' => array( 'font-size' ), ), array( 'path' => array( 'typography', 'fontFamilies' ), 'prevent_override' => false, 'use_default_names' => false, 'value_key' => 'fontFamily', 'css_vars' => '--wp--preset--font-family--$slug', 'classes' => array( '.has-$slug-font-family' => 'font-family' ), 'properties' => array( 'font-family' ), ), array( 'path' => array( 'spacing', 'spacingSizes' ), 'prevent_override' => array( 'spacing', 'defaultSpacingSizes' ), 'use_default_names' => true, 'value_key' => 'size', 'css_vars' => '--wp--preset--spacing--$slug', 'classes' => array(), 'properties' => array( 'padding', 'margin' ), ), array( 'path' => array( 'shadow', 'presets' ), 'prevent_override' => array( 'shadow', 'defaultPresets' ), 'use_default_names' => false, 'value_key' => 'shadow', 'css_vars' => '--wp--preset--shadow--$slug', 'classes' => array(), 'properties' => array( 'box-shadow' ), ), array( 'path' => array( 'border', 'radiusSizes' ), 'prevent_override' => false, 'use_default_names' => false, 'value_key' => 'size', 'css_vars' => '--wp--preset--border-radius--$slug', 'classes' => array(), 'properties' => array( 'border-radius' ), ), ); /** * Metadata for style properties. * * Each element is a direct mapping from the CSS property name to the * path to the value in theme.json & block attributes. * * @since 5.8.0 * @since 5.9.0 Added the `border-*`, `font-family`, `font-style`, `font-weight`, * `letter-spacing`, `margin-*`, `padding-*`, `--wp--style--block-gap`, * `text-decoration`, `text-transform`, and `filter` properties, * simplified the metadata structure. * @since 6.1.0 Added the `border-*-color`, `border-*-width`, `border-*-style`, * `--wp--style--root--padding-*`, and `box-shadow` properties, * removed the `--wp--style--block-gap` property. * @since 6.2.0 Added `outline-*`, and `min-height` properties. * @since 6.3.0 Added `column-count` property. * @since 6.4.0 Added `writing-mode` property. * @since 6.5.0 Added `aspect-ratio` property. * @since 6.6.0 Added `background-[image|position|repeat|size]` properties. * @since 6.7.0 Added `background-attachment` property. * @var array */ const PROPERTIES_METADATA = array( 'aspect-ratio' => array( 'dimensions', 'aspectRatio' ), 'background' => array( 'color', 'gradient' ), 'background-color' => array( 'color', 'background' ), 'background-image' => array( 'background', 'backgroundImage' ), 'background-position' => array( 'background', 'backgroundPosition' ), 'background-repeat' => array( 'background', 'backgroundRepeat' ), 'background-size' => array( 'background', 'backgroundSize' ), 'background-attachment' => array( 'background', 'backgroundAttachment' ), 'border-radius' => array( 'border', 'radius' ), 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ), 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ), 'border-bottom-left-radius' => array( 'border', 'radius', 'bottomLeft' ), 'border-bottom-right-radius' => array( 'border', 'radius', 'bottomRight' ), 'border-color' => array( 'border', 'color' ), 'border-width' => array( 'border', 'width' ), 'border-style' => array( 'border', 'style' ), 'border-top-color' => array( 'border', 'top', 'color' ), 'border-top-width' => array( 'border', 'top', 'width' ), 'border-top-style' => array( 'border', 'top', 'style' ), 'border-right-color' => array( 'border', 'right', 'color' ), 'border-right-width' => array( 'border', 'right', 'width' ), 'border-right-style' => array( 'border', 'right', 'style' ), 'border-bottom-color' => array( 'border', 'bottom', 'color' ), 'border-bottom-width' => array( 'border', 'bottom', 'width' ), 'border-bottom-style' => array( 'border', 'bottom', 'style' ), 'border-left-color' => array( 'border', 'left', 'color' ), 'border-left-width' => array( 'border', 'left', 'width' ), 'border-left-style' => array( 'border', 'left', 'style' ), 'color' => array( 'color', 'text' ), 'text-align' => array( 'typography', 'textAlign' ), 'column-count' => array( 'typography', 'textColumns' ), 'font-family' => array( 'typography', 'fontFamily' ), 'font-size' => array( 'typography', 'fontSize' ), 'font-style' => array( 'typography', 'fontStyle' ), 'font-weight' => array( 'typography', 'fontWeight' ), 'letter-spacing' => array( 'typography', 'letterSpacing' ), 'line-height' => array( 'typography', 'lineHeight' ), 'margin' => array( 'spacing', 'margin' ), 'margin-top' => array( 'spacing', 'margin', 'top' ), 'margin-right' => array( 'spacing', 'margin', 'right' ), 'margin-bottom' => array( 'spacing', 'margin', 'bottom' ), 'margin-left' => array( 'spacing', 'margin', 'left' ), 'min-height' => array( 'dimensions', 'minHeight' ), 'outline-color' => array( 'outline', 'color' ), 'outline-offset' => array( 'outline', 'offset' ), 'outline-style' => array( 'outline', 'style' ), 'outline-width' => array( 'outline', 'width' ), 'padding' => array( 'spacing', 'padding' ), 'padding-top' => array( 'spacing', 'padding', 'top' ), 'padding-right' => array( 'spacing', 'padding', 'right' ), 'padding-bottom' => array( 'spacing', 'padding', 'bottom' ), 'padding-left' => array( 'spacing', 'padding', 'left' ), '--wp--style--root--padding' => array( 'spacing', 'padding' ), '--wp--style--root--padding-top' => array( 'spacing', 'padding', 'top' ), '--wp--style--root--padding-right' => array( 'spacing', 'padding', 'right' ), '--wp--style--root--padding-bottom' => array( 'spacing', 'padding', 'bottom' ), '--wp--style--root--padding-left' => array( 'spacing', 'padding', 'left' ), 'text-decoration' => array( 'typography', 'textDecoration' ), 'text-transform' => array( 'typography', 'textTransform' ), 'filter' => array( 'filter', 'duotone' ), 'box-shadow' => array( 'shadow' ), 'writing-mode' => array( 'typography', 'writingMode' ), ); /** * Indirect metadata for style properties that are not directly output. * * Each element maps from a CSS property name to an array of * paths to the value in theme.json & block attributes. * * Indirect properties are not output directly by `compute_style_properties`, * but are used elsewhere in the processing of global styles. The indirect * property is used to validate whether a style value is allowed. * * @since 6.2.0 * @since 6.6.0 Added background-image properties. * @var array */ const INDIRECT_PROPERTIES_METADATA = array( 'gap' => array( array( 'spacing', 'blockGap' ), ), 'column-gap' => array( array( 'spacing', 'blockGap', 'left' ), ), 'row-gap' => array( array( 'spacing', 'blockGap', 'top' ), ), 'max-width' => array( array( 'layout', 'contentSize' ), array( 'layout', 'wideSize' ), ), 'background-image' => array( array( 'background', 'backgroundImage', 'url' ), ), ); /** * Protected style properties. * * These style properties are only rendered if a setting enables it * via a value other than `null`. * * Each element maps the style property to the corresponding theme.json * setting key. * * @since 5.9.0 * @var array */ const PROTECTED_PROPERTIES = array( 'spacing.blockGap' => array( 'spacing', 'blockGap' ), ); /** * The top-level keys a theme.json can have. * * @since 5.8.0 As `ALLOWED_TOP_LEVEL_KEYS`. * @since 5.9.0 Renamed from `ALLOWED_TOP_LEVEL_KEYS` to `VALID_TOP_LEVEL_KEYS`, * added the `customTemplates` and `templateParts` values. * @since 6.3.0 Added the `description` value. * @since 6.6.0 Added `blockTypes` to support block style variation theme.json partials. * @var string[] */ const VALID_TOP_LEVEL_KEYS = array( 'blockTypes', 'customTemplates', 'description', 'patterns', 'settings', 'slug', 'styles', 'templateParts', 'title', 'version', ); /** * The valid properties under the settings key. * * @since 5.8.0 As `ALLOWED_SETTINGS`. * @since 5.9.0 Renamed from `ALLOWED_SETTINGS` to `VALID_SETTINGS`, * added new properties for `border`, `color`, `spacing`, * and `typography`, and renamed others according to the new schema. * @since 6.0.0 Added `color.defaultDuotone`. * @since 6.1.0 Added `layout.definitions` and `useRootPaddingAwareAlignments`. * @since 6.2.0 Added `dimensions.minHeight`, 'shadow.presets', 'shadow.defaultPresets', * `position.fixed` and `position.sticky`. * @since 6.3.0 Added support for `typography.textColumns`, removed `layout.definitions`. * @since 6.4.0 Added support for `layout.allowEditing`, `background.backgroundImage`, * `typography.writingMode`, `lightbox.enabled` and `lightbox.allowEditing`. * @since 6.5.0 Added support for `layout.allowCustomContentAndWideSize`, * `background.backgroundSize` and `dimensions.aspectRatio`. * @since 6.6.0 Added support for 'dimensions.aspectRatios', 'dimensions.defaultAspectRatios', * 'typography.defaultFontSizes', and 'spacing.defaultSpacingSizes'. * @since 6.9.0 Added support for `border.radiusSizes`. * @var array */ const VALID_SETTINGS = array( 'appearanceTools' => null, 'useRootPaddingAwareAlignments' => null, 'background' => array( 'backgroundImage' => null, 'backgroundSize' => null, ), 'border' => array( 'color' => null, 'radius' => null, 'radiusSizes' => null, 'style' => null, 'width' => null, ), 'color' => array( 'background' => null, 'custom' => null, 'customDuotone' => null, 'customGradient' => null, 'defaultDuotone' => null, 'defaultGradients' => null, 'defaultPalette' => null, 'duotone' => null, 'gradients' => null, 'link' => null, 'heading' => null, 'button' => null, 'caption' => null, 'palette' => null, 'text' => null, ), 'custom' => null, 'dimensions' => array( 'aspectRatio' => null, 'aspectRatios' => null, 'defaultAspectRatios' => null, 'minHeight' => null, ), 'layout' => array( 'contentSize' => null, 'wideSize' => null, 'allowEditing' => null, 'allowCustomContentAndWideSize' => null, ), 'lightbox' => array( 'enabled' => null, 'allowEditing' => null, ), 'position' => array( 'fixed' => null, 'sticky' => null, ), 'spacing' => array( 'customSpacingSize' => null, 'defaultSpacingSizes' => null, 'spacingSizes' => null, 'spacingScale' => null, 'blockGap' => null, 'margin' => null, 'padding' => null, 'units' => null, ), 'shadow' => array( 'presets' => null, 'defaultPresets' => null, ), 'typography' => array( 'fluid' => null, 'customFontSize' => null, 'defaultFontSizes' => null, 'dropCap' => null, 'fontFamilies' => null, 'fontSizes' => null, 'fontStyle' => null, 'fontWeight' => null, 'letterSpacing' => null, 'lineHeight' => null, 'textAlign' => null, 'textColumns' => null, 'textDecoration' => null, 'textTransform' => null, 'writingMode' => null, ), ); /** * The valid properties for fontFamilies under settings key. * * @since 6.5.0 * @var array */ const FONT_FAMILY_SCHEMA = array( array( 'fontFamily' => null, 'name' => null, 'slug' => null, 'fontFace' => array( array( 'ascentOverride' => null, 'descentOverride' => null, 'fontDisplay' => null, 'fontFamily' => null, 'fontFeatureSettings' => null, 'fontStyle' => null, 'fontStretch' => null, 'fontVariationSettings' => null, 'fontWeight' => null, 'lineGapOverride' => null, 'sizeAdjust' => null, 'src' => null, 'unicodeRange' => null, ), ), ), ); /** * The valid properties under the styles key. * * @since 5.8.0 As `ALLOWED_STYLES`. * @since 5.9.0 Renamed from `ALLOWED_STYLES` to `VALID_STYLES`, * added new properties for `border`, `filter`, `spacing`, * and `typography`. * @since 6.1.0 Added new side properties for `border`, * added new property `shadow`, * updated `blockGap` to be allowed at any level. * @since 6.2.0 Added `outline`, and `minHeight` properties. * @since 6.3.0 Added support for `typography.textColumns`. * @since 6.5.0 Added support for `dimensions.aspectRatio`. * @since 6.6.0 Added `background` sub properties to top-level only. * @var array */ const VALID_STYLES = array( 'background' => array( 'backgroundImage' => null, 'backgroundPosition' => null, 'backgroundRepeat' => null, 'backgroundSize' => null, 'backgroundAttachment' => null, ), 'border' => array( 'color' => null, 'radius' => null, 'style' => null, 'width' => null, 'top' => null, 'right' => null, 'bottom' => null, 'left' => null, ), 'color' => array( 'background' => null, 'gradient' => null, 'text' => null, ), 'dimensions' => array( 'aspectRatio' => null, 'minHeight' => null, ), 'filter' => array( 'duotone' => null, ), 'outline' => array( 'color' => null, 'offset' => null, 'style' => null, 'width' => null, ), 'shadow' => null, 'spacing' => array( 'margin' => null, 'padding' => null, 'blockGap' => null, ), 'typography' => array( 'fontFamily' => null, 'fontSize' => null, 'fontStyle' => null, 'fontWeight' => null, 'letterSpacing' => null, 'lineHeight' => null, 'textAlign' => null, 'textColumns' => null, 'textDecoration' => null, 'textTransform' => null, 'writingMode' => null, ), 'css' => null, ); /** * Defines which pseudo selectors are enabled for which elements. * * The order of the selectors should be: link, any-link, visited, hover, focus, focus-visible, active. * This is to ensure the user action (hover, focus and active) styles have a higher * specificity than the visited styles, which in turn have a higher specificity than * the unvisited styles. * * See https://core.trac.wordpress.org/ticket/56928. * Note: this will affect both top-level and block-level elements. * * @since 6.1.0 * @since 6.2.0 Added support for ':link' and ':any-link'. * @since 6.8.0 Added support for ':focus-visible'. * @since 6.9.0 Added `textInput` and `select` elements. * @var array */ const VALID_ELEMENT_PSEUDO_SELECTORS = array( 'link' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':focus-visible', ':active' ), 'button' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':focus-visible', ':active' ), ); /** * The valid elements that can be found under styles. * * @since 5.8.0 * @since 6.1.0 Added `heading`, `button`, and `caption` elements. * @var string[] */ const ELEMENTS = array( 'link' => 'a:where(:not(.wp-element-button))', // The `where` is needed to lower the specificity. 'heading' => 'h1, h2, h3, h4, h5, h6', 'h1' => 'h1', 'h2' => 'h2', 'h3' => 'h3', 'h4' => 'h4', 'h5' => 'h5', 'h6' => 'h6', // We have the .wp-block-button__link class so that this will target older buttons that have been serialized. 'button' => '.wp-element-button, .wp-block-button__link', // The block classes are necessary to target older content that won't use the new class names. 'caption' => '.wp-element-caption, .wp-block-audio figcaption, .wp-block-embed figcaption, .wp-block-gallery figcaption, .wp-block-image figcaption, .wp-block-table figcaption, .wp-block-video figcaption', 'cite' => 'cite', 'textInput' => 'textarea, input:where([type=email],[type=number],[type=password],[type=search],[type=text],[type=tel],[type=url])', 'select' => 'select', ); const __EXPERIMENTAL_ELEMENT_CLASS_NAMES = array( 'button' => 'wp-element-button', 'caption' => 'wp-element-caption', ); /** * List of block support features that can have their related styles * generated under their own feature level selector rather than the block's. * * @since 6.1.0 * @var string[] */ const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = array( '__experimentalBorder' => 'border', 'color' => 'color', 'spacing' => 'spacing', 'typography' => 'typography', ); /** * Return the input schema at the root and per origin. * * @since 6.5.0 * * @param array $schema The base schema. * @return array The schema at the root and per origin. * * Example: * schema_in_root_and_per_origin( * array( * 'fontFamily' => null, * 'slug' => null, * ) * ) * * Returns: * array( * 'fontFamily' => null, * 'slug' => null, * 'default' => array( * 'fontFamily' => null, * 'slug' => null, * ), * 'blocks' => array( * 'fontFamily' => null, * 'slug' => null, * ), * 'theme' => array( * 'fontFamily' => null, * 'slug' => null, * ), * 'custom' => array( * 'fontFamily' => null, * 'slug' => null, * ), * ) */ protected static function schema_in_root_and_per_origin( $schema ) { $schema_in_root_and_per_origin = $schema; foreach ( static::VALID_ORIGINS as $origin ) { $schema_in_root_and_per_origin[ $origin ] = $schema; } return $schema_in_root_and_per_origin; } /** * Returns a class name by an element name. * * @since 6.1.0 * * @param string $element The name of the element. * @return string The name of the class. */ public static function get_element_class_name( $element ) { $class_name = ''; if ( isset( static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $element ] ) ) { $class_name = static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $element ]; } return $class_name; } /** * Options that settings.appearanceTools enables. * * @since 6.0.0 * @since 6.2.0 Added `dimensions.minHeight` and `position.sticky`. * @since 6.4.0 Added `background.backgroundImage`. * @since 6.5.0 Added `background.backgroundSize` and `dimensions.aspectRatio`. * @var array */ const APPEARANCE_TOOLS_OPT_INS = array( array( 'background', 'backgroundImage' ), array( 'background', 'backgroundSize' ), array( 'border', 'color' ), array( 'border', 'radius' ), array( 'border', 'style' ), array( 'border', 'width' ), array( 'color', 'link' ), array( 'color', 'heading' ), array( 'color', 'button' ), array( 'color', 'caption' ), array( 'dimensions', 'aspectRatio' ), array( 'dimensions', 'minHeight' ), array( 'position', 'sticky' ), array( 'spacing', 'blockGap' ), array( 'spacing', 'margin' ), array( 'spacing', 'padding' ), array( 'typography', 'lineHeight' ), ); /** * The latest version of the schema in use. * * @since 5.8.0 * @since 5.9.0 Changed value from 1 to 2. * @since 6.6.0 Changed value from 2 to 3. * @var int */ const LATEST_SCHEMA = 3; /** * Constructor. * * @since 5.8.0 * @since 6.6.0 Key spacingScale by origin, and Pre-generate the spacingSizes from spacingScale. * Added unwrapping of shared block style variations into block type variations if registered. * * @param array $theme_json A structure that follows the theme.json schema. * @param string $origin Optional. What source of data this object represents. * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'. */ public function __construct( $theme_json = array( 'version' => self::LATEST_SCHEMA ), $origin = 'theme' ) { if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { $origin = 'theme'; } $this->theme_json = WP_Theme_JSON_Schema::migrate( $theme_json, $origin ); $blocks_metadata = static::get_blocks_metadata(); $valid_block_names = array_keys( $blocks_metadata ); $valid_element_names = array_keys( static::ELEMENTS ); $valid_variations = static::get_valid_block_style_variations( $blocks_metadata ); $this->theme_json = static::unwrap_shared_block_style_variations( $this->theme_json, $valid_variations ); $this->theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations ); $this->theme_json = static::maybe_opt_in_into_settings( $this->theme_json ); // Internally, presets are keyed by origin. $nodes = static::get_setting_nodes( $this->theme_json ); foreach ( $nodes as $node ) { foreach ( static::PRESETS_METADATA as $preset_metadata ) { $path = $node['path']; foreach ( $preset_metadata['path'] as $subpath ) { $path[] = $subpath; } $preset = _wp_array_get( $this->theme_json, $path, null ); if ( null !== $preset ) { // If the preset is not already keyed by origin. if ( isset( $preset[0] ) || empty( $preset ) ) { _wp_array_set( $this->theme_json, $path, array( $origin => $preset ) ); } } } } // In addition to presets, spacingScale (which generates presets) is also keyed by origin. $scale_path = array( 'settings', 'spacing', 'spacingScale' ); $spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null ); if ( null !== $spacing_scale ) { // If the spacingScale is not already keyed by origin. if ( empty( array_intersect( array_keys( $spacing_scale ), static::VALID_ORIGINS ) ) ) { _wp_array_set( $this->theme_json, $scale_path, array( $origin => $spacing_scale ) ); } } // Pre-generate the spacingSizes from spacingScale. $scale_path = array( 'settings', 'spacing', 'spacingScale', $origin ); $spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null ); if ( isset( $spacing_scale ) ) { $sizes_path = array( 'settings', 'spacing', 'spacingSizes', $origin ); $spacing_sizes = _wp_array_get( $this->theme_json, $sizes_path, array() ); $spacing_scale_sizes = static::compute_spacing_sizes( $spacing_scale ); $merged_spacing_sizes = static::merge_spacing_sizes( $spacing_scale_sizes, $spacing_sizes ); _wp_array_set( $this->theme_json, $sizes_path, $merged_spacing_sizes ); } } /** * Unwraps shared block style variations. * * It takes the shared variations (styles.variations.variationName) and * applies them to all the blocks that have the given variation registered * (styles.blocks.blockType.variations.variationName). * * For example, given the `core/paragraph` and `core/group` blocks have * registered the `section-a` style variation, and given the following input: * * { * "styles": { * "variations": { * "section-a": { "color": { "background": "backgroundColor" } } * } * } * } * * It returns the following output: * * { * "styles": { * "blocks": { * "core/paragraph": { * "variations": { * "section-a": { "color": { "background": "backgroundColor" } } * }, * }, * "core/group": { * "variations": { * "section-a": { "color": { "background": "backgroundColor" } } * } * } * } * } * } * * @since 6.6.0 * * @param array $theme_json A structure that follows the theme.json schema. * @param array $valid_variations Valid block style variations. * @return array Theme json data with shared variation definitions unwrapped under appropriate block types. */ private static function unwrap_shared_block_style_variations( $theme_json, $valid_variations ) { if ( empty( $theme_json['styles']['variations'] ) || empty( $valid_variations ) ) { return $theme_json; } $new_theme_json = $theme_json; $variations = $new_theme_json['styles']['variations']; foreach ( $valid_variations as $block_type => $registered_variations ) { foreach ( $registered_variations as $variation_name ) { $block_level_data = $new_theme_json['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); $top_level_data = $variations[ $variation_name ] ?? array(); $merged_data = array_replace_recursive( $top_level_data, $block_level_data ); if ( ! empty( $merged_data ) ) { _wp_array_set( $new_theme_json, array( 'styles', 'blocks', $block_type, 'variations', $variation_name ), $merged_data ); } } } unset( $new_theme_json['styles']['variations'] ); return $new_theme_json; } /** * Enables some opt-in settings if theme declared support. * * @since 5.9.0 * * @param array $theme_json A theme.json structure to modify. * @return array The modified theme.json structure. */ protected static function maybe_opt_in_into_settings( $theme_json ) { $new_theme_json = $theme_json; if ( isset( $new_theme_json['settings']['appearanceTools'] ) && true === $new_theme_json['settings']['appearanceTools'] ) { static::do_opt_in_into_settings( $new_theme_json['settings'] ); } if ( isset( $new_theme_json['settings']['blocks'] ) && is_array( $new_theme_json['settings']['blocks'] ) ) { foreach ( $new_theme_json['settings']['blocks'] as &$block ) { if ( isset( $block['appearanceTools'] ) && ( true === $block['appearanceTools'] ) ) { static::do_opt_in_into_settings( $block ); } } } return $new_theme_json; } /** * Enables some settings. * * @since 5.9.0 * * @param array $context The context to which the settings belong. */ protected static function do_opt_in_into_settings( &$context ) { foreach ( static::APPEARANCE_TOOLS_OPT_INS as $path ) { /* * Use "unset prop" as a marker instead of "null" because * "null" can be a valid value for some props (e.g. blockGap). */ if ( 'unset prop' === _wp_array_get( $context, $path, 'unset prop' ) ) { _wp_array_set( $context, $path, true ); } } unset( $context['appearanceTools'] ); } /** * Sanitizes the input according to the schemas. * * @since 5.8.0 * @since 5.9.0 Added the `$valid_block_names` and `$valid_element_name` parameters. * @since 6.3.0 Added the `$valid_variations` parameter. * @since 6.6.0 Updated schema to allow extended block style variations. * * @param array $input Structure to sanitize. * @param array $valid_block_names List of valid block names. * @param array $valid_element_names List of valid element names. * @param array $valid_variations List of valid variations per block. * @return array The sanitized output. */ protected static function sanitize( $input, $valid_block_names, $valid_element_names, $valid_variations ) { $output = array(); if ( ! is_array( $input ) ) { return $output; } // Preserve only the top most level keys. $output = array_intersect_key( $input, array_flip( static::VALID_TOP_LEVEL_KEYS ) ); /* * Remove any rules that are annotated as "top" in VALID_STYLES constant. * Some styles are only meant to be available at the top-level (e.g.: blockGap), * hence, the schema for blocks & elements should not have them. */ $styles_non_top_level = static::VALID_STYLES; foreach ( array_keys( $styles_non_top_level ) as $section ) { // array_key_exists() needs to be used instead of isset() because the value can be null. if ( array_key_exists( $section, $styles_non_top_level ) && is_array( $styles_non_top_level[ $section ] ) ) { foreach ( array_keys( $styles_non_top_level[ $section ] ) as $prop ) { if ( 'top' === $styles_non_top_level[ $section ][ $prop ] ) { unset( $styles_non_top_level[ $section ][ $prop ] ); } } } } // Build the schema based on valid block & element names. $schema = array(); $schema_styles_elements = array(); /* * Set allowed element pseudo selectors based on per element allow list. * Target data structure in schema: * e.g. * - top level elements: `$schema['styles']['elements']['link'][':hover']`. * - block level elements: `$schema['styles']['blocks']['core/button']['elements']['link'][':hover']`. */ foreach ( $valid_element_names as $element ) { $schema_styles_elements[ $element ] = $styles_non_top_level; if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) { foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { $schema_styles_elements[ $element ][ $pseudo_selector ] = $styles_non_top_level; } } } $schema_styles_blocks = array(); $schema_settings_blocks = array(); /* * Generate a schema for blocks. * - Block styles can contain `elements` & `variations` definitions. * - Variations definitions cannot be nested. * - Variations can contain styles for inner `blocks`. * - Variation inner `blocks` styles can contain `elements`. * * As each variation needs a `blocks` schema but further nested * inner `blocks`, the overall schema will be generated in multiple passes. */ foreach ( $valid_block_names as $block ) { $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; $schema_styles_blocks[ $block ] = $styles_non_top_level; $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; } $block_style_variation_styles = static::VALID_STYLES; $block_style_variation_styles['blocks'] = $schema_styles_blocks; $block_style_variation_styles['elements'] = $schema_styles_elements; foreach ( $valid_block_names as $block ) { // Build the schema for each block style variation. $style_variation_names = array(); if ( ! empty( $input['styles']['blocks'][ $block ]['variations'] ) && is_array( $input['styles']['blocks'][ $block ]['variations'] ) && isset( $valid_variations[ $block ] ) ) { $style_variation_names = array_intersect( array_keys( $input['styles']['blocks'][ $block ]['variations'] ), $valid_variations[ $block ] ); } $schema_styles_variations = array(); if ( ! empty( $style_variation_names ) ) { $schema_styles_variations = array_fill_keys( $style_variation_names, $block_style_variation_styles ); } $schema_styles_blocks[ $block ]['variations'] = $schema_styles_variations; } $schema['styles'] = static::VALID_STYLES; $schema['styles']['blocks'] = $schema_styles_blocks; $schema['styles']['elements'] = $schema_styles_elements; $schema['settings'] = static::VALID_SETTINGS; $schema['settings']['blocks'] = $schema_settings_blocks; $schema['settings']['typography']['fontFamilies'] = static::schema_in_root_and_per_origin( static::FONT_FAMILY_SCHEMA ); // Remove anything that's not present in the schema. foreach ( array( 'styles', 'settings' ) as $subtree ) { if ( ! isset( $input[ $subtree ] ) ) { continue; } if ( ! is_array( $input[ $subtree ] ) ) { unset( $output[ $subtree ] ); continue; } $result = static::remove_keys_not_in_schema( $input[ $subtree ], $schema[ $subtree ] ); if ( empty( $result ) ) { unset( $output[ $subtree ] ); } else { $output[ $subtree ] = static::resolve_custom_css_format( $result ); } } return $output; } /** * Appends a sub-selector to an existing one. * * Given the compounded $selector "h1, h2, h3" * and the $to_append selector ".some-class" the result will be * "h1.some-class, h2.some-class, h3.some-class". * * @since 5.8.0 * @since 6.1.0 Added append position. * @since 6.3.0 Removed append position parameter. * * @param string $selector Original selector. * @param string $to_append Selector to append. * @return string The new selector. */ protected static function append_to_selector( $selector, $to_append ) { if ( ! str_contains( $selector, ',' ) ) { return $selector . $to_append; } $new_selectors = array(); $selectors = explode( ',', $selector ); foreach ( $selectors as $sel ) { $new_selectors[] = $sel . $to_append; } return implode( ',', $new_selectors ); } /** * Prepends a sub-selector to an existing one. * * Given the compounded $selector "h1, h2, h3" * and the $to_prepend selector ".some-class " the result will be * ".some-class h1, .some-class h2, .some-class h3". * * @since 6.3.0 * * @param string $selector Original selector. * @param string $to_prepend Selector to prepend. * @return string The new selector. */ protected static function prepend_to_selector( $selector, $to_prepend ) { if ( ! str_contains( $selector, ',' ) ) { return $to_prepend . $selector; } $new_selectors = array(); $selectors = explode( ',', $selector ); foreach ( $selectors as $sel ) { $new_selectors[] = $to_prepend . $sel; } return implode( ',', $new_selectors ); } /** * Returns the metadata for each block. * * Example: * * { * 'core/paragraph': { * 'selector': 'p', * 'elements': { * 'link' => 'link selector', * 'etc' => 'element selector' * } * }, * 'core/heading': { * 'selector': 'h1', * 'elements': {} * }, * 'core/image': { * 'selector': '.wp-block-image', * 'duotone': 'img', * 'elements': {} * } * } * * @since 5.8.0 * @since 5.9.0 Added `duotone` key with CSS selector. * @since 6.1.0 Added `features` key with block support feature level selectors. * @since 6.3.0 Refactored and stabilized selectors API. * @since 6.6.0 Updated to include block style variations from the block styles registry. * * @return array Block metadata. */ protected static function get_blocks_metadata() { $registry = WP_Block_Type_Registry::get_instance(); $blocks = $registry->get_all_registered(); $style_registry = WP_Block_Styles_Registry::get_instance(); // Is there metadata for all currently registered blocks? $blocks = array_diff_key( $blocks, static::$blocks_metadata ); if ( empty( $blocks ) ) { /* * New block styles may have been registered within WP_Block_Styles_Registry. * Update block metadata for any new block style variations. */ $registered_styles = $style_registry->get_all_registered(); foreach ( static::$blocks_metadata as $block_name => $block_metadata ) { if ( ! empty( $registered_styles[ $block_name ] ) ) { $style_selectors = $block_metadata['styleVariations'] ?? array(); foreach ( $registered_styles[ $block_name ] as $block_style ) { if ( ! isset( $style_selectors[ $block_style['name'] ] ) ) { $style_selectors[ $block_style['name'] ] = static::get_block_style_variation_selector( $block_style['name'], $block_metadata['selector'] ); } } static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; } } return static::$blocks_metadata; } foreach ( $blocks as $block_name => $block_type ) { $root_selector = wp_get_block_css_selector( $block_type ); static::$blocks_metadata[ $block_name ]['selector'] = $root_selector; static::$blocks_metadata[ $block_name ]['selectors'] = static::get_block_selectors( $block_type, $root_selector ); $elements = static::get_block_element_selectors( $root_selector ); if ( ! empty( $elements ) ) { static::$blocks_metadata[ $block_name ]['elements'] = $elements; } // The block may or may not have a duotone selector. $duotone_selector = wp_get_block_css_selector( $block_type, 'filter.duotone' ); // Keep backwards compatibility for support.color.__experimentalDuotone. if ( null === $duotone_selector ) { $duotone_support = isset( $block_type->supports['color']['__experimentalDuotone'] ) ? $block_type->supports['color']['__experimentalDuotone'] : null; if ( $duotone_support ) { $root_selector = wp_get_block_css_selector( $block_type ); $duotone_selector = static::scope_selector( $root_selector, $duotone_support ); } } if ( null !== $duotone_selector ) { static::$blocks_metadata[ $block_name ]['duotone'] = $duotone_selector; } // If the block has style variations, append their selectors to the block metadata. $style_selectors = array(); if ( ! empty( $block_type->styles ) ) { foreach ( $block_type->styles as $style ) { $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); } } // Block style variations can be registered through the WP_Block_Styles_Registry as well as block.json. $registered_styles = $style_registry->get_registered_styles_for_block( $block_name ); foreach ( $registered_styles as $style ) { $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); } if ( ! empty( $style_selectors ) ) { static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; } } return static::$blocks_metadata; } /** * Given a tree, removes the keys that are not present in the schema. * * It is recursive and modifies the input in-place. * * @since 5.8.0 * * @param array $tree Input to process. * @param array $schema Schema to adhere to. * @return array The modified $tree. */ protected static function remove_keys_not_in_schema( $tree, $schema ) { if ( ! is_array( $tree ) ) { return $tree; } foreach ( $tree as $key => $value ) { // Remove keys not in the schema or with null/empty values. if ( ! array_key_exists( $key, $schema ) ) { unset( $tree[ $key ] ); continue; } if ( is_array( $schema[ $key ] ) ) { if ( ! is_array( $value ) ) { unset( $tree[ $key ] ); } elseif ( wp_is_numeric_array( $value ) ) { // If indexed, process each item in the array. foreach ( $value as $item_key => $item_value ) { if ( isset( $schema[ $key ][0] ) && is_array( $schema[ $key ][0] ) ) { $tree[ $key ][ $item_key ] = self::remove_keys_not_in_schema( $item_value, $schema[ $key ][0] ); } else { // If the schema does not define a further structure, keep the value as is. $tree[ $key ][ $item_key ] = $item_value; } } } else { // If associative, process as a single object. $tree[ $key ] = self::remove_keys_not_in_schema( $value, $schema[ $key ] ); if ( empty( $tree[ $key ] ) ) { unset( $tree[ $key ] ); } } } } return $tree; } /** * Returns the existing settings for each block. * * Example: * * { * 'root': { * 'color': { * 'custom': true * } * }, * 'core/paragraph': { * 'spacing': { * 'customPadding': true * } * } * } * * @since 5.8.0 * * @return array Settings per block. */ public function get_settings() { if ( ! isset( $this->theme_json['settings'] ) ) { return array(); } else { return $this->theme_json['settings']; } } /** * Returns the stylesheet that results of processing * the theme.json structure this object represents. * * @since 5.8.0 * @since 5.9.0 Removed the `$type` parameter, added the `$types` and `$origins` parameters. * @since 6.3.0 Add fallback layout styles for Post Template when block gap support isn't available. * @since 6.6.0 Added boolean `skip_root_layout_styles` and `include_block_style_variations` options * to control styles output as desired. * * @param string[] $types Types of styles to load. Will load all by default. It accepts: * - `variables`: only the CSS Custom Properties for presets & custom ones. * - `styles`: only the styles section in theme.json. * - `presets`: only the classes for the presets. * - `base-layout-styles`: only the base layout styles. * - `custom-css`: only the custom CSS. * @param string[] $origins A list of origins to include. By default it includes VALID_ORIGINS. * @param array $options { * Optional. An array of options for now used for internal purposes only (may change without notice). * * @type string $scope Makes sure all style are scoped to a given selector * @type string $root_selector Overwrites and forces a given selector to be used on the root node * @type bool $skip_root_layout_styles Omits root layout styles from the generated stylesheet. Default false. * @type bool $include_block_style_variations Includes styles for block style variations in the generated stylesheet. Default false. * } * @return string The resulting stylesheet. */ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' ), $origins = null, $options = array() ) { if ( null === $origins ) { $origins = static::VALID_ORIGINS; } if ( is_string( $types ) ) { // Dispatch error and map old arguments to new ones. _deprecated_argument( __FUNCTION__, '5.9.0' ); if ( 'block_styles' === $types ) { $types = array( 'styles', 'presets' ); } elseif ( 'css_variables' === $types ) { $types = array( 'variables' ); } else { $types = array( 'variables', 'styles', 'presets' ); } } $blocks_metadata = static::get_blocks_metadata(); $style_nodes = static::get_style_nodes( $this->theme_json, $blocks_metadata, $options ); $setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata ); $root_style_key = array_search( static::ROOT_BLOCK_SELECTOR, array_column( $style_nodes, 'selector' ), true ); $root_settings_key = array_search( static::ROOT_BLOCK_SELECTOR, array_column( $setting_nodes, 'selector' ), true ); if ( ! empty( $options['scope'] ) ) { foreach ( $setting_nodes as &$node ) { $node['selector'] = static::scope_selector( $options['scope'], $node['selector'] ); } foreach ( $style_nodes as &$node ) { $node = static::scope_style_node_selectors( $options['scope'], $node ); } unset( $node ); } if ( ! empty( $options['root_selector'] ) ) { if ( false !== $root_settings_key ) { $setting_nodes[ $root_settings_key ]['selector'] = $options['root_selector']; } if ( false !== $root_style_key ) { $style_nodes[ $root_style_key ]['selector'] = $options['root_selector']; } } $stylesheet = ''; if ( in_array( 'variables', $types, true ) ) { $stylesheet .= $this->get_css_variables( $setting_nodes, $origins ); } if ( in_array( 'styles', $types, true ) ) { if ( false !== $root_style_key && empty( $options['skip_root_layout_styles'] ) ) { $stylesheet .= $this->get_root_layout_rules( $style_nodes[ $root_style_key ]['selector'], $style_nodes[ $root_style_key ] ); } $stylesheet .= $this->get_block_classes( $style_nodes ); } elseif ( in_array( 'base-layout-styles', $types, true ) ) { $root_selector = static::ROOT_BLOCK_SELECTOR; $columns_selector = '.wp-block-columns'; $post_template_selector = '.wp-block-post-template'; if ( ! empty( $options['scope'] ) ) { $root_selector = static::scope_selector( $options['scope'], $root_selector ); $columns_selector = static::scope_selector( $options['scope'], $columns_selector ); $post_template_selector = static::scope_selector( $options['scope'], $post_template_selector ); } if ( ! empty( $options['root_selector'] ) ) { $root_selector = $options['root_selector']; } /* * Base layout styles are provided as part of `styles`, so only output separately if explicitly requested. * For backwards compatibility, the Columns block is explicitly included, to support a different default gap value. */ $base_styles_nodes = array( array( 'path' => array( 'styles' ), 'selector' => $root_selector, ), array( 'path' => array( 'styles', 'blocks', 'core/columns' ), 'selector' => $columns_selector, 'name' => 'core/columns', ), array( 'path' => array( 'styles', 'blocks', 'core/post-template' ), 'selector' => $post_template_selector, 'name' => 'core/post-template', ), ); foreach ( $base_styles_nodes as $base_style_node ) { $stylesheet .= $this->get_layout_styles( $base_style_node, $types ); } } if ( in_array( 'presets', $types, true ) ) { $stylesheet .= $this->get_preset_classes( $setting_nodes, $origins ); } // Load the custom CSS last so it has the highest specificity. if ( in_array( 'custom-css', $types, true ) ) { // Add the global styles root CSS. $stylesheet .= _wp_array_get( $this->theme_json, array( 'styles', 'css' ) ); } return $stylesheet; } /** * Processes the CSS, to apply nesting. * * @since 6.2.0 * @since 6.6.0 Enforced 0-1-0 specificity for block custom CSS selectors. * * @param string $css The CSS to process. * @param string $selector The selector to nest. * @return string The processed CSS. */ protected function process_blocks_custom_css( $css, $selector ) { $processed_css = ''; if ( empty( $css ) ) { return $processed_css; } // Split CSS nested rules. $parts = explode( '&', $css ); foreach ( $parts as $part ) { if ( empty( $part ) ) { continue; } $is_root_css = ( ! str_contains( $part, '{' ) ); if ( $is_root_css ) { // If the part doesn't contain braces, it applies to the root level. $processed_css .= ':root :where(' . trim( $selector ) . '){' . trim( $part ) . '}'; } else { // If the part contains braces, it's a nested CSS rule. $part = explode( '{', str_replace( '}', '', $part ) ); if ( count( $part ) !== 2 ) { continue; } $nested_selector = $part[0]; $css_value = $part[1]; /* * Handle pseudo elements such as ::before, ::after etc. Regex will also * capture any leading combinator such as >, +, or ~, as well as spaces. * This allows pseudo elements as descendants e.g. `.parent ::before`. */ $matches = array(); $has_pseudo_element = preg_match( '/([>+~\s]*::[a-zA-Z-]+)/', $nested_selector, $matches ); $pseudo_part = $has_pseudo_element ? $matches[1] : ''; $nested_selector = $has_pseudo_element ? str_replace( $pseudo_part, '', $nested_selector ) : $nested_selector; // Finalize selector and re-append pseudo element if required. $part_selector = str_starts_with( $nested_selector, ' ' ) ? static::scope_selector( $selector, $nested_selector ) : static::append_to_selector( $selector, $nested_selector ); $final_selector = ":root :where($part_selector)$pseudo_part"; $processed_css .= $final_selector . '{' . trim( $css_value ) . '}'; } } return $processed_css; } /** * Returns the global styles custom CSS. * * @since 6.2.0 * @deprecated 6.7.0 Use {@see 'get_stylesheet'} instead. * * @return string The global styles custom CSS. */ public function get_custom_css() { _deprecated_function( __METHOD__, '6.7.0', 'get_stylesheet' ); // Add the global styles root CSS. $stylesheet = isset( $this->theme_json['styles']['css'] ) ? $this->theme_json['styles']['css'] : ''; // Add the global styles block CSS. if ( isset( $this->theme_json['styles']['blocks'] ) ) { foreach ( $this->theme_json['styles']['blocks'] as $name => $node ) { $custom_block_css = isset( $this->theme_json['styles']['blocks'][ $name ]['css'] ) ? $this->theme_json['styles']['blocks'][ $name ]['css'] : null; if ( $custom_block_css ) { $selector = static::$blocks_metadata[ $name ]['selector']; $stylesheet .= $this->process_blocks_custom_css( $custom_block_css, $selector ); } } } return $stylesheet; } /** * Returns the page templates of the active theme. * * @since 5.9.0 * * @return array */ public function get_custom_templates() { $custom_templates = array(); if ( ! isset( $this->theme_json['customTemplates'] ) || ! is_array( $this->theme_json['customTemplates'] ) ) { return $custom_templates; } foreach ( $this->theme_json['customTemplates'] as $item ) { if ( isset( $item['name'] ) ) { $custom_templates[ $item['name'] ] = array( 'title' => isset( $item['title'] ) ? $item['title'] : '', 'postTypes' => isset( $item['postTypes'] ) ? $item['postTypes'] : array( 'page' ), ); } } return $custom_templates; } /** * Returns the template part data of active theme. * * @since 5.9.0 * * @return array */ public function get_template_parts() { $template_parts = array(); if ( ! isset( $this->theme_json['templateParts'] ) || ! is_array( $this->theme_json['templateParts'] ) ) { return $template_parts; } foreach ( $this->theme_json['templateParts'] as $item ) { if ( isset( $item['name'] ) ) { $template_parts[ $item['name'] ] = array( 'title' => isset( $item['title'] ) ? $item['title'] : '', 'area' => isset( $item['area'] ) ? $item['area'] : '', ); } } return $template_parts; } /** * Converts each style section into a list of rulesets * containing the block styles to be appended to the stylesheet. * * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax * * For each section this creates a new ruleset such as: * * block-selector { * style-property-one: value; * } * * @since 5.8.0 As `get_block_styles()`. * @since 5.9.0 Renamed from `get_block_styles()` to `get_block_classes()` * and no longer returns preset classes. * Removed the `$setting_nodes` parameter. * @since 6.1.0 Moved most internal logic to `get_styles_for_block()`. * * @param array $style_nodes Nodes with styles. * @return string The new stylesheet. */ protected function get_block_classes( $style_nodes ) { $block_rules = ''; foreach ( $style_nodes as $metadata ) { if ( null === $metadata['selector'] ) { continue; } $block_rules .= static::get_styles_for_block( $metadata ); } return $block_rules; } /** * Gets the CSS layout rules for a particular block from theme.json layout definitions. * * @since 6.1.0 * @since 6.3.0 Reduced specificity for layout margin rules. * @since 6.5.1 Only output rules referencing content and wide sizes when values exist. * @since 6.5.3 Add types parameter to check if only base layout styles are needed. * @since 6.6.0 Updated layout style specificity to be compatible with overall 0-1-0 specificity in global styles. * * @param array $block_metadata Metadata about the block to get styles for. * @param array $types Optional. Types of styles to output. If empty, all styles will be output. * @return string Layout styles for the block. */ protected function get_layout_styles( $block_metadata, $types = array() ) { $block_rules = ''; $block_type = null; // Skip outputting layout styles if explicitly disabled. if ( current_theme_supports( 'disable-layout-styles' ) ) { return $block_rules; } if ( isset( $block_metadata['name'] ) ) { $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_metadata['name'] ); if ( ! block_has_support( $block_type, 'layout', false ) && ! block_has_support( $block_type, '__experimentalLayout', false ) ) { return $block_rules; } } $selector = isset( $block_metadata['selector'] ) ? $block_metadata['selector'] : ''; $has_block_gap_support = isset( $this->theme_json['settings']['spacing']['blockGap'] ); $has_fallback_gap_support = ! $has_block_gap_support; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback gap styles support. $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); $layout_definitions = wp_get_layout_definitions(); $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\,\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors. /* * Gap styles will only be output if the theme has block gap support, or supports a fallback gap. * Default layout gap styles will be skipped for themes that do not explicitly opt-in to blockGap with a `true` or `false` value. */ if ( $has_block_gap_support || $has_fallback_gap_support ) { $block_gap_value = null; // Use a fallback gap value if block gap support is not available. if ( ! $has_block_gap_support ) { $block_gap_value = static::ROOT_BLOCK_SELECTOR === $selector ? '0.5em' : null; if ( ! empty( $block_type ) ) { $block_gap_value = isset( $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ) ? $block_type->supports['spacing']['blockGap']['__experimentalDefault'] : null; } } else { $block_gap_value = static::get_property_value( $node, array( 'spacing', 'blockGap' ) ); } // Support split row / column values and concatenate to a shorthand value. if ( is_array( $block_gap_value ) ) { if ( isset( $block_gap_value['top'] ) && isset( $block_gap_value['left'] ) ) { $gap_row = static::get_property_value( $node, array( 'spacing', 'blockGap', 'top' ) ); $gap_column = static::get_property_value( $node, array( 'spacing', 'blockGap', 'left' ) ); $block_gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column; } else { // Skip outputting gap value if not all sides are provided. $block_gap_value = null; } } // If the block should have custom gap, add the gap styles. if ( null !== $block_gap_value && false !== $block_gap_value && '' !== $block_gap_value ) { foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { // Allow outputting fallback gap styles for flex and grid layout types when block gap support isn't available. if ( ! $has_block_gap_support && 'flex' !== $layout_definition_key && 'grid' !== $layout_definition_key ) { continue; } $class_name = isset( $layout_definition['className'] ) ? $layout_definition['className'] : false; $spacing_rules = isset( $layout_definition['spacingStyles'] ) ? $layout_definition['spacingStyles'] : array(); if ( ! empty( $class_name ) && ! empty( $spacing_rules ) ) { foreach ( $spacing_rules as $spacing_rule ) { $declarations = array(); if ( isset( $spacing_rule['selector'] ) && preg_match( $layout_selector_pattern, $spacing_rule['selector'] ) && ! empty( $spacing_rule['rules'] ) ) { // Iterate over each of the styling rules and substitute non-string values such as `null` with the real `blockGap` value. foreach ( $spacing_rule['rules'] as $css_property => $css_value ) { $current_css_value = is_string( $css_value ) ? $css_value : $block_gap_value; if ( static::is_safe_css_declaration( $css_property, $current_css_value ) ) { $declarations[] = array( 'name' => $css_property, 'value' => $current_css_value, ); } } if ( ! $has_block_gap_support ) { // For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles. $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':where(.%2$s%3$s)' : ':where(%1$s.%2$s%3$s)'; $layout_selector = sprintf( $format, $selector, $class_name, $spacing_rule['selector'] ); } else { $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':root :where(.%2$s)%3$s' : ':root :where(%1$s-%2$s)%3$s'; $layout_selector = sprintf( $format, $selector, $class_name, $spacing_rule['selector'] ); } $block_rules .= static::to_ruleset( $layout_selector, $declarations ); } } } } } } // Output base styles. if ( static::ROOT_BLOCK_SELECTOR === $selector ) { $valid_display_modes = array( 'block', 'flex', 'grid' ); foreach ( $layout_definitions as $layout_definition ) { $class_name = isset( $layout_definition['className'] ) ? $layout_definition['className'] : false; $base_style_rules = isset( $layout_definition['baseStyles'] ) ? $layout_definition['baseStyles'] : array(); if ( ! empty( $class_name ) && is_array( $base_style_rules ) ) { // Output display mode. This requires special handling as `display` is not exposed in `safe_style_css_filter`. if ( ! empty( $layout_definition['displayMode'] ) && is_string( $layout_definition['displayMode'] ) && in_array( $layout_definition['displayMode'], $valid_display_modes, true ) ) { $layout_selector = sprintf( '%s .%s', $selector, $class_name ); $block_rules .= static::to_ruleset( $layout_selector, array( array( 'name' => 'display', 'value' => $layout_definition['displayMode'], ), ) ); } foreach ( $base_style_rules as $base_style_rule ) { $declarations = array(); // Skip outputting base styles for flow and constrained layout types if theme doesn't support theme.json. The 'base-layout-styles' type flags this. if ( in_array( 'base-layout-styles', $types, true ) && ( 'default' === $layout_definition['name'] || 'constrained' === $layout_definition['name'] ) ) { continue; } if ( isset( $base_style_rule['selector'] ) && preg_match( $layout_selector_pattern, $base_style_rule['selector'] ) && ! empty( $base_style_rule['rules'] ) ) { foreach ( $base_style_rule['rules'] as $css_property => $css_value ) { // Skip rules that reference content size or wide size if they are not defined in the theme.json. if ( is_string( $css_value ) && ( str_contains( $css_value, '--global--content-size' ) || str_contains( $css_value, '--global--wide-size' ) ) && ! isset( $this->theme_json['settings']['layout']['contentSize'] ) && ! isset( $this->theme_json['settings']['layout']['wideSize'] ) ) { continue; } if ( static::is_safe_css_declaration( $css_property, $css_value ) ) { $declarations[] = array( 'name' => $css_property, 'value' => $css_value, ); } } $layout_selector = sprintf( '.%s%s', $class_name, $base_style_rule['selector'] ); $block_rules .= static::to_ruleset( $layout_selector, $declarations ); } } } } } return $block_rules; } /** * Creates new rulesets as classes for each preset value such as: * * .has-value-color { * color: value; * } * * .has-value-background-color { * background-color: value; * } * * .has-value-font-size { * font-size: value; * } * * .has-value-gradient-background { * background: value; * } * * p.has-value-gradient-background { * background: value; * } * * @since 5.9.0 * * @param array $setting_nodes Nodes with settings. * @param string[] $origins List of origins to process presets from. * @return string The new stylesheet. */ protected function get_preset_classes( $setting_nodes, $origins ) { $preset_rules = ''; foreach ( $setting_nodes as $metadata ) { if ( null === $metadata['selector'] ) { continue; } $selector = $metadata['selector']; $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); $preset_rules .= static::compute_preset_classes( $node, $selector, $origins ); } return $preset_rules; } /** * Converts each styles section into a list of rulesets * to be appended to the stylesheet. * These rulesets contain all the css variables (custom variables and preset variables). * * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax * * For each section this creates a new ruleset such as: * * block-selector { * --wp--preset--category--slug: value; * --wp--custom--variable: value; * } * * @since 5.8.0 * @since 5.9.0 Added the `$origins` parameter. * * @param array $nodes Nodes with settings. * @param string[] $origins List of origins to process. * @return string The new stylesheet. */ protected function get_css_variables( $nodes, $origins ) { $stylesheet = ''; foreach ( $nodes as $metadata ) { if ( null === $metadata['selector'] ) { continue; } $selector = $metadata['selector']; $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); $declarations = static::compute_preset_vars( $node, $origins ); $theme_vars_declarations = static::compute_theme_vars( $node ); foreach ( $theme_vars_declarations as $theme_vars_declaration ) { $declarations[] = $theme_vars_declaration; } $stylesheet .= static::to_ruleset( $selector, $declarations ); } return $stylesheet; } /** * Given a selector and a declaration list, * creates the corresponding ruleset. * * @since 5.8.0 * * @param string $selector CSS selector. * @param array $declarations List of declarations. * @return string The resulting CSS ruleset. */ protected static function to_ruleset( $selector, $declarations ) { if ( empty( $declarations ) ) { return ''; } $declaration_block = array_reduce( $declarations, static function ( $carry, $element ) { return $carry .= $element['name'] . ': ' . $element['value'] . ';'; }, '' ); return $selector . '{' . $declaration_block . '}'; } /** * Given a settings array, returns the generated rulesets * for the preset classes. * * @since 5.8.0 * @since 5.9.0 Added the `$origins` parameter. * @since 6.6.0 Added check for root CSS properties selector. * * @param array $settings Settings to process. * @param string $selector Selector wrapping the classes. * @param string[] $origins List of origins to process. * @return string The result of processing the presets. */ protected static function compute_preset_classes( $settings, $selector, $origins ) { if ( static::ROOT_BLOCK_SELECTOR === $selector || static::ROOT_CSS_PROPERTIES_SELECTOR === $selector ) { /* * Classes at the global level do not need any CSS prefixed, * and we don't want to increase its specificity. */ $selector = ''; } $stylesheet = ''; foreach ( static::PRESETS_METADATA as $preset_metadata ) { if ( empty( $preset_metadata['classes'] ) ) { continue; } $slugs = static::get_settings_slugs( $settings, $preset_metadata, $origins ); foreach ( $preset_metadata['classes'] as $class => $property ) { foreach ( $slugs as $slug ) { $css_var = static::replace_slug_in_string( $preset_metadata['css_vars'], $slug ); $class_name = static::replace_slug_in_string( $class, $slug ); // $selector is often empty, so we can save ourselves the `append_to_selector()` call then. $new_selector = '' === $selector ? $class_name : static::append_to_selector( $selector, $class_name ); $stylesheet .= static::to_ruleset( $new_selector, array( array( 'name' => $property, 'value' => 'var(' . $css_var . ') !important', ), ) ); } } } return $stylesheet; } /** * Function that scopes a selector with another one. This works a bit like * SCSS nesting except the `&` operator isn't supported. * * <code> * $scope = '.a, .b .c'; * $selector = '> .x, .y'; * $merged = scope_selector( $scope, $selector ); * // $merged is '.a > .x, .a .y, .b .c > .x, .b .c .y' * </code> * * @since 5.9.0 * @since 6.6.0 Added early return if missing scope or selector. * * @param string $scope Selector to scope to. * @param string $selector Original selector. * @return string Scoped selector. */ public static function scope_selector( $scope, $selector ) { if ( ! $scope || ! $selector ) { return $selector; } $scopes = explode( ',', $scope ); $selectors = explode( ',', $selector ); $selectors_scoped = array(); foreach ( $scopes as $outer ) { foreach ( $selectors as $inner ) { $outer = trim( $outer ); $inner = trim( $inner ); if ( ! empty( $outer ) && ! empty( $inner ) ) { $selectors_scoped[] = $outer . ' ' . $inner; } elseif ( empty( $outer ) ) { $selectors_scoped[] = $inner; } elseif ( empty( $inner ) ) { $selectors_scoped[] = $outer; } } } $result = implode( ', ', $selectors_scoped ); return $result; } /** * Scopes the selectors for a given style node. * * This includes the primary selector, i.e. `$node['selector']`, as well as any custom * selectors for features and subfeatures, e.g. `$node['selectors']['border']` etc. * * @since 6.6.0 * * @param string $scope Selector to scope to. * @param array $node Style node with selectors to scope. * @return array Node with updated selectors. */ protected static function scope_style_node_selectors( $scope, $node ) { $node['selector'] = static::scope_selector( $scope, $node['selector'] ); if ( empty( $node['selectors'] ) ) { return $node; } foreach ( $node['selectors'] as $feature => $selector ) { if ( is_string( $selector ) ) { $node['selectors'][ $feature ] = static::scope_selector( $scope, $selector ); } if ( is_array( $selector ) ) { foreach ( $selector as $subfeature => $subfeature_selector ) { $node['selectors'][ $feature ][ $subfeature ] = static::scope_selector( $scope, $subfeature_selector ); } } } return $node; } /** * Gets preset values keyed by slugs based on settings and metadata. * * <code> * $settings = array( * 'typography' => array( * 'fontFamilies' => array( * array( * 'slug' => 'sansSerif', * 'fontFamily' => '"Helvetica Neue", sans-serif', * ), * array( * 'slug' => 'serif', * 'colors' => 'Georgia, serif', * ) * ), * ), * ); * $meta = array( * 'path' => array( 'typography', 'fontFamilies' ), * 'value_key' => 'fontFamily', * ); * $values_by_slug = get_settings_values_by_slug(); * // $values_by_slug === array( * // 'sans-serif' => '"Helvetica Neue", sans-serif', * // 'serif' => 'Georgia, serif', * // ); * </code> * * @since 5.9.0 * @since 6.6.0 Passing $settings to the callbacks defined in static::PRESETS_METADATA. * * @param array $settings Settings to process. * @param array $preset_metadata One of the PRESETS_METADATA values. * @param string[] $origins List of origins to process. * @return array Array of presets where each key is a slug and each value is the preset value. */ protected static function get_settings_values_by_slug( $settings, $preset_metadata, $origins ) { $preset_per_origin = _wp_array_get( $settings, $preset_metadata['path'], array() ); $result = array(); foreach ( $origins as $origin ) { if ( ! isset( $preset_per_origin[ $origin ] ) ) { continue; } foreach ( $preset_per_origin[ $origin ] as $preset ) { $slug = _wp_to_kebab_case( $preset['slug'] ); $value = ''; if ( isset( $preset_metadata['value_key'], $preset[ $preset_metadata['value_key'] ] ) ) { $value_key = $preset_metadata['value_key']; $value = $preset[ $value_key ]; } elseif ( isset( $preset_metadata['value_func'] ) && is_callable( $preset_metadata['value_func'] ) ) { $value_func = $preset_metadata['value_func']; $value = call_user_func( $value_func, $preset, $settings ); } else { // If we don't have a value, then don't add it to the result. continue; } $result[ $slug ] = $value; } } return $result; } /** * Similar to get_settings_values_by_slug, but doesn't compute the value. * * @since 5.9.0 * * @param array $settings Settings to process. * @param array $preset_metadata One of the PRESETS_METADATA values. * @param string[] $origins List of origins to process. * @return array Array of presets where the key and value are both the slug. */ protected static function get_settings_slugs( $settings, $preset_metadata, $origins = null ) { if ( null === $origins ) { $origins = static::VALID_ORIGINS; } $preset_per_origin = _wp_array_get( $settings, $preset_metadata['path'], array() ); $result = array(); foreach ( $origins as $origin ) { if ( ! isset( $preset_per_origin[ $origin ] ) ) { continue; } foreach ( $preset_per_origin[ $origin ] as $preset ) { $slug = _wp_to_kebab_case( $preset['slug'] ); // Use the array as a set so we don't get duplicates. $result[ $slug ] = $slug; } } return $result; } /** * Transforms a slug into a CSS Custom Property. * * @since 5.9.0 * * @param string $input String to replace. * @param string $slug The slug value to use to generate the custom property. * @return string The CSS Custom Property. Something along the lines of `--wp--preset--color--black`. */ protected static function replace_slug_in_string( $input, $slug ) { return strtr( $input, array( '$slug' => $slug ) ); } /** * Given the block settings, extracts the CSS Custom Properties * for the presets and adds them to the $declarations array * following the format: * * array( * 'name' => 'property_name', * 'value' => 'property_value, * ) * * @since 5.8.0 * @since 5.9.0 Added the `$origins` parameter. * * @param array $settings Settings to process. * @param string[] $origins List of origins to process. * @return array The modified $declarations. */ protected static function compute_preset_vars( $settings, $origins ) { $declarations = array(); foreach ( static::PRESETS_METADATA as $preset_metadata ) { if ( empty( $preset_metadata['css_vars'] ) ) { continue; } $values_by_slug = static::get_settings_values_by_slug( $settings, $preset_metadata, $origins ); foreach ( $values_by_slug as $slug => $value ) { $declarations[] = array( 'name' => static::replace_slug_in_string( $preset_metadata['css_vars'], $slug ), 'value' => $value, ); } } return $declarations; } /** * Given an array of settings, extracts the CSS Custom Properties * for the custom values and adds them to the $declarations * array following the format: * * array( * 'name' => 'property_name', * 'value' => 'property_value, * ) * * @since 5.8.0 * * @param array $settings Settings to process. * @return array The modified $declarations. */ protected static function compute_theme_vars( $settings ) { $declarations = array(); $custom_values = isset( $settings['custom'] ) ? $settings['custom'] : array(); $css_vars = static::flatten_tree( $custom_values ); foreach ( $css_vars as $key => $value ) { $declarations[] = array( 'name' => '--wp--custom--' . $key, 'value' => $value, ); } return $declarations; } /** * Given a tree, it creates a flattened one * by merging the keys and binding the leaf values * to the new keys. * * It also transforms camelCase names into kebab-case * and substitutes '/' by '-'. * * This is thought to be useful to generate * CSS Custom Properties from a tree, * although there's nothing in the implementation * of this function that requires that format. * * For example, assuming the given prefix is '--wp' * and the token is '--', for this input tree: * * { * 'some/property': 'value', * 'nestedProperty': { * 'sub-property': 'value' * } * } * * it'll return this output: * * { * '--wp--some-property': 'value', * '--wp--nested-property--sub-property': 'value' * } * * @since 5.8.0 * * @param array $tree Input tree to process. * @param string $prefix Optional. Prefix to prepend to each variable. Default empty string. * @param string $token Optional. Token to use between levels. Default '--'. * @return array The flattened tree. */ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) { $result = array(); foreach ( $tree as $property => $value ) { $new_key = $prefix . str_replace( '/', '-', strtolower( _wp_to_kebab_case( $property ) ) ); if ( is_array( $value ) ) { $new_prefix = $new_key . $token; $flattened_subtree = static::flatten_tree( $value, $new_prefix, $token ); foreach ( $flattened_subtree as $subtree_key => $subtree_value ) { $result[ $subtree_key ] = $subtree_value; } } else { $result[ $new_key ] = $value; } } return $result; } /** * Given a styles array, it extracts the style properties * and adds them to the $declarations array following the format: * * array( * 'name' => 'property_name', * 'value' => 'property_value', * ) * * @since 5.8.0 * @since 5.9.0 Added the `$settings` and `$properties` parameters. * @since 6.1.0 Added `$theme_json`, `$selector`, and `$use_root_padding` parameters. * @since 6.5.0 Output a `min-height: unset` rule when `aspect-ratio` is set. * @since 6.6.0 Pass current theme JSON settings to wp_get_typography_font_size_value(), and process background properties. * @since 6.7.0 `ref` resolution of background properties, and assigning custom default values. * * @param array $styles Styles to process. * @param array $settings Theme settings. * @param array $properties Properties metadata. * @param array $theme_json Theme JSON array. * @param string $selector The style block selector. * @param boolean $use_root_padding Whether to add custom properties at root level. * @return array Returns the modified $declarations. */ protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null, $selector = null, $use_root_padding = null ) { if ( empty( $styles ) ) { return array(); } if ( null === $properties ) { $properties = static::PROPERTIES_METADATA; } $declarations = array(); $root_variable_duplicates = array(); $root_style_length = strlen( '--wp--style--root--' ); foreach ( $properties as $css_property => $value_path ) { if ( ! is_array( $value_path ) ) { continue; } $is_root_style = str_starts_with( $css_property, '--wp--style--root--' ); if ( $is_root_style && ( static::ROOT_BLOCK_SELECTOR !== $selector || ! $use_root_padding ) ) { continue; } $value = static::get_property_value( $styles, $value_path, $theme_json ); /* * Root-level padding styles don't currently support strings with CSS shorthand values. * This may change: https://github.com/WordPress/gutenberg/issues/40132. */ if ( '--wp--style--root--padding' === $css_property && is_string( $value ) ) { continue; } if ( $is_root_style && $use_root_padding ) { $root_variable_duplicates[] = substr( $css_property, $root_style_length ); } /* * Processes background image styles. * If the value is a URL, it will be converted to a CSS `url()` value. * For uploaded image (images with a database ID), apply size and position defaults, * equal to those applied in block supports in lib/background.php. */ if ( 'background-image' === $css_property && ! empty( $value ) ) { $background_styles = wp_style_engine_get_styles( array( 'background' => array( 'backgroundImage' => $value ) ) ); $value = $background_styles['declarations'][ $css_property ]; } if ( empty( $value ) && static::ROOT_BLOCK_SELECTOR !== $selector && ! empty( $styles['background']['backgroundImage']['id'] ) ) { if ( 'background-size' === $css_property ) { $value = 'cover'; } // If the background size is set to `contain` and no position is set, set the position to `center`. if ( 'background-position' === $css_property ) { $background_size = $styles['background']['backgroundSize'] ?? null; $value = 'contain' === $background_size ? '50% 50%' : null; } } // Skip if empty and not "0" or value represents array of longhand values. $has_missing_value = empty( $value ) && ! is_numeric( $value ); if ( $has_missing_value || is_array( $value ) ) { continue; } /* * Look up protected properties, keyed by value path. * Skip protected properties that are explicitly set to `null`. */ $path_string = implode( '.', $value_path ); if ( isset( static::PROTECTED_PROPERTIES[ $path_string ] ) && _wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null ) { continue; } // Calculates fluid typography rules where available. if ( 'font-size' === $css_property ) { /* * wp_get_typography_font_size_value() will check * if fluid typography has been activated and also * whether the incoming value can be converted to a fluid value. * Values that already have a clamp() function will not pass the test, * and therefore the original $value will be returned. * Pass the current theme_json settings to override any global settings. */ $value = wp_get_typography_font_size_value( array( 'size' => $value ), $settings ); } if ( 'aspect-ratio' === $css_property ) { // For aspect ratio to work, other dimensions rules must be unset. // This ensures that a fixed height does not override the aspect ratio. $declarations[] = array( 'name' => 'min-height', 'value' => 'unset', ); } $declarations[] = array( 'name' => $css_property, 'value' => $value, ); } // If a variable value is added to the root, the corresponding property should be removed. foreach ( $root_variable_duplicates as $duplicate ) { $discard = array_search( $duplicate, array_column( $declarations, 'name' ), true ); if ( is_numeric( $discard ) ) { array_splice( $declarations, $discard, 1 ); } } return $declarations; } /** * Returns the style property for the given path. * * It also converts references to a path to the value * stored at that location, e.g. * { "ref": "style.color.background" } => "#fff". * * @since 5.8.0 * @since 5.9.0 Added support for values of array type, which are returned as is. * @since 6.1.0 Added the `$theme_json` parameter. * @since 6.3.0 It no longer converts the internal format "var:preset|color|secondary" * to the standard form "--wp--preset--color--secondary". * This is already done by the sanitize method, * so every property will be in the standard form. * @since 6.7.0 Added support for background image refs. * * @param array $styles Styles subtree. * @param array $path Which property to process. * @param array $theme_json Theme JSON array. * @return string|array Style property value. */ protected static function get_property_value( $styles, $path, $theme_json = null ) { $value = _wp_array_get( $styles, $path, '' ); if ( '' === $value || null === $value ) { // No need to process the value further. return ''; } /* * This converts references to a path to the value at that path * where the value is an array with a "ref" key, pointing to a path. * For example: { "ref": "style.color.background" } => "#fff". * In the case of backgroundImage, if both a ref and a URL are present in the value, * the URL takes precedence and the ref is ignored. */ if ( is_array( $value ) && isset( $value['ref'] ) ) { $value_path = explode( '.', $value['ref'] ); $ref_value = _wp_array_get( $theme_json, $value_path ); // Background Image refs can refer to a string or an array containing a URL string. $ref_value_url = $ref_value['url'] ?? null; // Only use the ref value if we find anything. if ( ! empty( $ref_value ) && ( is_string( $ref_value ) || is_string( $ref_value_url ) ) ) { $value = $ref_value; } if ( is_array( $ref_value ) && isset( $ref_value['ref'] ) ) { $path_string = json_encode( $path ); $ref_value_string = json_encode( $ref_value ); _doing_it_wrong( 'get_property_value', sprintf( /* translators: 1: theme.json, 2: Value name, 3: Value path, 4: Another value name. */ __( 'Your %1$s file uses a dynamic value (%2$s) for the path at %3$s. However, the value at %3$s is also a dynamic value (pointing to %4$s) and pointing to another dynamic value is not supported. Please update %3$s to point directly to %4$s.' ), 'theme.json', $ref_value_string, $path_string, $ref_value['ref'] ), '6.1.0' ); } } if ( is_array( $value ) ) { return $value; } return $value; } /** * Builds metadata for the setting nodes, which returns in the form of: * * [ * [ * 'path' => ['path', 'to', 'some', 'node' ], * 'selector' => 'CSS selector for some node' * ], * [ * 'path' => [ 'path', 'to', 'other', 'node' ], * 'selector' => 'CSS selector for other node' * ], * ] * * @since 5.8.0 * * @param array $theme_json The tree to extract setting nodes from. * @param array $selectors List of selectors per block. * @return array An array of setting nodes metadata. */ protected static function get_setting_nodes( $theme_json, $selectors = array() ) { $nodes = array(); if ( ! isset( $theme_json['settings'] ) ) { return $nodes; } // Top-level. $nodes[] = array( 'path' => array( 'settings' ), 'selector' => static::ROOT_CSS_PROPERTIES_SELECTOR, ); // Calculate paths for blocks. if ( ! isset( $theme_json['settings']['blocks'] ) ) { return $nodes; } foreach ( $theme_json['settings']['blocks'] as $name => $node ) { $selector = null; if ( isset( $selectors[ $name ]['selector'] ) ) { $selector = $selectors[ $name ]['selector']; } $nodes[] = array( 'path' => array( 'settings', 'blocks', $name ), 'selector' => $selector, ); } return $nodes; } /** * Builds metadata for the style nodes, which returns in the form of: * * [ * [ * 'path' => [ 'path', 'to', 'some', 'node' ], * 'selector' => 'CSS selector for some node', * 'duotone' => 'CSS selector for duotone for some node' * ], * [ * 'path' => ['path', 'to', 'other', 'node' ], * 'selector' => 'CSS selector for other node', * 'duotone' => null * ], * ] * * @since 5.8.0 * @since 6.6.0 Added options array for modifying generated nodes. * * @param array $theme_json The tree to extract style nodes from. * @param array $selectors List of selectors per block. * @param array $options { * Optional. An array of options for now used for internal purposes only (may change without notice). * * @type bool $include_block_style_variations Includes style nodes for block style variations. Default false. * } * @return array An array of style nodes metadata. */ protected static function get_style_nodes( $theme_json, $selectors = array(), $options = array() ) { $nodes = array(); if ( ! isset( $theme_json['styles'] ) ) { return $nodes; } // Top-level. $nodes[] = array( 'path' => array( 'styles' ), 'selector' => static::ROOT_BLOCK_SELECTOR, ); if ( isset( $theme_json['styles']['elements'] ) ) { foreach ( self::ELEMENTS as $element => $selector ) { if ( ! isset( $theme_json['styles']['elements'][ $element ] ) ) { continue; } $nodes[] = array( 'path' => array( 'styles', 'elements', $element ), 'selector' => static::ELEMENTS[ $element ], ); // Handle any pseudo selectors for the element. if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) { foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { if ( isset( $theme_json['styles']['elements'][ $element ][ $pseudo_selector ] ) ) { $nodes[] = array( 'path' => array( 'styles', 'elements', $element ), 'selector' => static::append_to_selector( static::ELEMENTS[ $element ], $pseudo_selector ), ); } } } } } // Blocks. if ( ! isset( $theme_json['styles']['blocks'] ) ) { return $nodes; } $block_nodes = static::get_block_nodes( $theme_json, $selectors, $options ); foreach ( $block_nodes as $block_node ) { $nodes[] = $block_node; } /** * Filters the list of style nodes with metadata. * * This allows for things like loading block CSS independently. * * @since 6.1.0 * * @param array $nodes Style nodes with metadata. */ return apply_filters( 'wp_theme_json_get_style_nodes', $nodes ); } /** * A public helper to get the block nodes from a theme.json file. * * @since 6.1.0 * * @return array The block nodes in theme.json. */ public function get_styles_block_nodes() { return static::get_block_nodes( $this->theme_json ); } /** * Returns a filtered declarations array if there is a separator block with only a background * style defined in theme.json by adding a color attribute to reflect the changes in the front. * * @since 6.1.1 * * @param array $declarations List of declarations. * @return array $declarations List of declarations filtered. */ private static function update_separator_declarations( $declarations ) { $background_color = ''; $border_color_matches = false; $text_color_matches = false; foreach ( $declarations as $declaration ) { if ( 'background-color' === $declaration['name'] && ! $background_color && isset( $declaration['value'] ) ) { $background_color = $declaration['value']; } elseif ( 'border-color' === $declaration['name'] ) { $border_color_matches = true; } elseif ( 'color' === $declaration['name'] ) { $text_color_matches = true; } if ( $background_color && $border_color_matches && $text_color_matches ) { break; } } if ( $background_color && ! $border_color_matches && ! $text_color_matches ) { $declarations[] = array( 'name' => 'color', 'value' => $background_color, ); } return $declarations; } /** * An internal method to get the block nodes from a theme.json file. * * @since 6.1.0 * @since 6.3.0 Refactored and stabilized selectors API. * @since 6.6.0 Added optional selectors and options for generating block nodes. * @since 6.7.0 Added $include_node_paths_only option. * * @param array $theme_json The theme.json converted to an array. * @param array $selectors Optional list of selectors per block. * @param array $options { * Optional. An array of options for now used for internal purposes only (may change without notice). * * @type bool $include_block_style_variations Include nodes for block style variations. Default false. * @type bool $include_node_paths_only Return only block nodes node paths. Default false. * } * @return array The block nodes in theme.json. */ private static function get_block_nodes( $theme_json, $selectors = array(), $options = array() ) { $nodes = array(); if ( ! isset( $theme_json['styles']['blocks'] ) ) { return $nodes; } $include_variations = $options['include_block_style_variations'] ?? false; $include_node_paths_only = $options['include_node_paths_only'] ?? false; // If only node paths are to be returned, skip selector assignment. if ( ! $include_node_paths_only ) { $selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors; } foreach ( $theme_json['styles']['blocks'] as $name => $node ) { $node_path = array( 'styles', 'blocks', $name ); if ( $include_node_paths_only ) { $variation_paths = array(); if ( $include_variations && isset( $node['variations'] ) ) { foreach ( $node['variations'] as $variation => $variation_node ) { $variation_paths[] = array( 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ), ); } } $node = array( 'path' => $node_path, ); if ( ! empty( $variation_paths ) ) { $node['variations'] = $variation_paths; } $nodes[] = $node; } else { $selector = null; if ( isset( $selectors[ $name ]['selector'] ) ) { $selector = $selectors[ $name ]['selector']; } $duotone_selector = null; if ( isset( $selectors[ $name ]['duotone'] ) ) { $duotone_selector = $selectors[ $name ]['duotone']; } $feature_selectors = null; if ( isset( $selectors[ $name ]['selectors'] ) ) { $feature_selectors = $selectors[ $name ]['selectors']; } $variation_selectors = array(); if ( $include_variations && isset( $node['variations'] ) ) { foreach ( $node['variations'] as $variation => $node ) { $variation_selectors[] = array( 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ), 'selector' => $selectors[ $name ]['styleVariations'][ $variation ], ); } } $nodes[] = array( 'name' => $name, 'path' => $node_path, 'selector' => $selector, 'selectors' => $feature_selectors, 'duotone' => $duotone_selector, 'features' => $feature_selectors, 'variations' => $variation_selectors, 'css' => $selector, ); } if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) { $node_path = array( 'styles', 'blocks', $name, 'elements', $element ); if ( $include_node_paths_only ) { $nodes[] = array( 'path' => $node_path, ); continue; } $nodes[] = array( 'path' => $node_path, 'selector' => $selectors[ $name ]['elements'][ $element ], ); // Handle any pseudo selectors for the element. if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) { foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) { $node_path = array( 'styles', 'blocks', $name, 'elements', $element ); $nodes[] = array( 'path' => $node_path, 'selector' => static::append_to_selector( $selectors[ $name ]['elements'][ $element ], $pseudo_selector ), ); } } } } } } return $nodes; } /** * Gets the CSS rules for a particular block from theme.json. * * @since 6.1.0 * @since 6.6.0 Setting a min-height of HTML when root styles have a background gradient or image. * Updated general global styles specificity to 0-1-0. * Fixed custom CSS output in block style variations. * * @param array $block_metadata Metadata about the block to get styles for. * @return string Styles for the block. */ public function get_styles_for_block( $block_metadata ) { $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments']; $selector = $block_metadata['selector']; $settings = isset( $this->theme_json['settings'] ) ? $this->theme_json['settings'] : array(); $feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $node ); $is_root_selector = static::ROOT_BLOCK_SELECTOR === $selector; // If there are style variations, generate the declarations for them, including any feature selectors the block may have. $style_variation_declarations = array(); $style_variation_custom_css = array(); if ( ! empty( $block_metadata['variations'] ) ) { foreach ( $block_metadata['variations'] as $style_variation ) { $style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() ); $clean_style_variation_selector = trim( $style_variation['selector'] ); // Generate any feature/subfeature style declarations for the current style variation. $variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node ); // Combine selectors with style variation's selector and add to overall style variation declarations. foreach ( $variation_declarations as $current_selector => $new_declarations ) { /* * Clean up any whitespace between comma separated selectors. * This prevents these spaces breaking compound selectors such as: * - `.wp-block-list:not(.wp-block-list .wp-block-list)` * - `.wp-block-image img, .wp-block-image.my-class img` */ $clean_current_selector = preg_replace( '/,\s+/', ',', $current_selector ); $shortened_selector = str_replace( $block_metadata['selector'], '', $clean_current_selector ); // Prepend the variation selector to the current selector. $split_selectors = explode( ',', $shortened_selector ); $updated_selectors = array_map( static function ( $split_selector ) use ( $clean_style_variation_selector ) { return $clean_style_variation_selector . $split_selector; }, $split_selectors ); $combined_selectors = implode( ',', $updated_selectors ); // Add the new declarations to the overall results under the modified selector. $style_variation_declarations[ $combined_selectors ] = $new_declarations; } // Compute declarations for remaining styles not covered by feature level selectors. $style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json ); // Store custom CSS for the style variation. if ( isset( $style_variation_node['css'] ) ) { $style_variation_custom_css[ $style_variation['selector'] ] = $this->process_blocks_custom_css( $style_variation_node['css'], $style_variation['selector'] ); } } } /* * Get a reference to element name from path. * $block_metadata['path'] = array( 'styles','elements','link' ); * Make sure that $block_metadata['path'] describes an element node, like [ 'styles', 'element', 'link' ]. * Skip non-element paths like just ['styles']. */ $is_processing_element = in_array( 'elements', $block_metadata['path'], true ); $current_element = $is_processing_element ? $block_metadata['path'][ count( $block_metadata['path'] ) - 1 ] : null; $element_pseudo_allowed = array(); if ( isset( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) ) { $element_pseudo_allowed = static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ]; } /* * Check for allowed pseudo classes (e.g. ":hover") from the $selector ("a:hover"). * This also resets the array keys. */ $pseudo_matches = array_values( array_filter( $element_pseudo_allowed, static function ( $pseudo_selector ) use ( $selector ) { /* * Check if the pseudo selector is in the current selector, * ensuring it is not followed by a dash (e.g., :focus should not match :focus-visible). */ return preg_match( '/' . preg_quote( $pseudo_selector, '/' ) . '(?!-)/', $selector ) === 1; } ) ); $pseudo_selector = isset( $pseudo_matches[0] ) ? $pseudo_matches[0] : null; /* * If the current selector is a pseudo selector that's defined in the allow list for the current * element then compute the style properties for it. * Otherwise just compute the styles for the default selector as normal. */ if ( $pseudo_selector && isset( $node[ $pseudo_selector ] ) && isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) ) { $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json, $selector, $use_root_padding ); } else { $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json, $selector, $use_root_padding ); } $block_rules = ''; /* * 1. Bespoke declaration modifiers: * - 'filter': Separate the declarations that use the general selector * from the ones using the duotone selector. * - 'background|background-image': set the html min-height to 100% * to ensure the background covers the entire viewport. */ $declarations_duotone = array(); $should_set_root_min_height = false; foreach ( $declarations as $index => $declaration ) { if ( 'filter' === $declaration['name'] ) { /* * 'unset' filters happen when a filter is unset * in the site-editor UI. Because the 'unset' value * in the user origin overrides the value in the * theme origin, we can skip rendering anything * here as no filter needs to be applied anymore. * So only add declarations to with values other * than 'unset'. */ if ( 'unset' !== $declaration['value'] ) { $declarations_duotone[] = $declaration; } unset( $declarations[ $index ] ); } if ( $is_root_selector && ( 'background-image' === $declaration['name'] || 'background' === $declaration['name'] ) ) { $should_set_root_min_height = true; } } /* * If root styles has a background-image or a background (gradient) set, * set the min-height to '100%'. Minus `--wp-admin--admin-bar--height` for logged-in view. * Setting the CSS rule on the HTML tag ensures background gradients and images behave similarly, * and matches the behavior of the site editor. */ if ( $should_set_root_min_height ) { $block_rules .= static::to_ruleset( 'html', array( array( 'name' => 'min-height', 'value' => 'calc(100% - var(--wp-admin--admin-bar--height, 0px))', ), ) ); } // Update declarations if there are separators with only background color defined. if ( '.wp-block-separator' === $selector ) { $declarations = static::update_separator_declarations( $declarations ); } /* * Root selector (body) styles should not be wrapped in `:root where()` to keep * specificity at (0,0,1) and maintain backwards compatibility. * * Top-level element styles using element-only specificity selectors should * not get wrapped in `:root :where()` to maintain backwards compatibility. * * Pseudo classes, e.g. :hover, :focus etc., are a class-level selector so * still need to be wrapped in `:root :where` to cap specificity for nested * variations etc. Pseudo selectors won't match the ELEMENTS selector exactly. */ $element_only_selector = $is_root_selector || ( $current_element && isset( static::ELEMENTS[ $current_element ] ) && // buttons, captions etc. still need `:root :where()` as they are class based selectors. ! isset( static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $current_element ] ) && static::ELEMENTS[ $current_element ] === $selector ); // 2. Generate and append the rules that use the general selector. $general_selector = $element_only_selector ? $selector : ":root :where($selector)"; $block_rules .= static::to_ruleset( $general_selector, $declarations ); // 3. Generate and append the rules that use the duotone selector. if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { $block_rules .= static::to_ruleset( $block_metadata['duotone'], $declarations_duotone ); } // 4. Generate Layout block gap styles. if ( ! $is_root_selector && ! empty( $block_metadata['name'] ) ) { $block_rules .= $this->get_layout_styles( $block_metadata ); } // 5. Generate and append the feature level rulesets. foreach ( $feature_declarations as $feature_selector => $individual_feature_declarations ) { $block_rules .= static::to_ruleset( ":root :where($feature_selector)", $individual_feature_declarations ); } // 6. Generate and append the style variation rulesets. foreach ( $style_variation_declarations as $style_variation_selector => $individual_style_variation_declarations ) { $block_rules .= static::to_ruleset( ":root :where($style_variation_selector)", $individual_style_variation_declarations ); if ( isset( $style_variation_custom_css[ $style_variation_selector ] ) ) { $block_rules .= $style_variation_custom_css[ $style_variation_selector ]; } } // 7. Generate and append any custom CSS rules. if ( isset( $node['css'] ) && ! $is_root_selector ) { $block_rules .= $this->process_blocks_custom_css( $node['css'], $selector ); } return $block_rules; } /** * Outputs the CSS for layout rules on the root. * * @since 6.1.0 * @since 6.6.0 Use `ROOT_CSS_PROPERTIES_SELECTOR` for CSS custom properties and improved consistency of root padding rules. * Updated specificity of body margin reset and first/last child selectors. * * @param string $selector The root node selector. * @param array $block_metadata The metadata for the root block. * @return string The additional root rules CSS. */ public function get_root_layout_rules( $selector, $block_metadata ) { $css = ''; $settings = isset( $this->theme_json['settings'] ) ? $this->theme_json['settings'] : array(); $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments']; /* * If there are content and wide widths in theme.json, output them * as custom properties on the body element so all blocks can use them. */ if ( isset( $settings['layout']['contentSize'] ) || isset( $settings['layout']['wideSize'] ) ) { $content_size = isset( $settings['layout']['contentSize'] ) ? $settings['layout']['contentSize'] : $settings['layout']['wideSize']; $content_size = static::is_safe_css_declaration( 'max-width', $content_size ) ? $content_size : 'initial'; $wide_size = isset( $settings['layout']['wideSize'] ) ? $settings['layout']['wideSize'] : $settings['layout']['contentSize']; $wide_size = static::is_safe_css_declaration( 'max-width', $wide_size ) ? $wide_size : 'initial'; $css .= static::ROOT_CSS_PROPERTIES_SELECTOR . ' { --wp--style--global--content-size: ' . $content_size . ';'; $css .= '--wp--style--global--wide-size: ' . $wide_size . '; }'; } /* * Reset default browser margin on the body element. * This is set on the body selector **before** generating the ruleset * from the `theme.json`. This is to ensure that if the `theme.json` declares * `margin` in its `spacing` declaration for the `body` element then these * user-generated values take precedence in the CSS cascade. * @link https://github.com/WordPress/gutenberg/issues/36147. */ $css .= ':where(body) { margin: 0; }'; if ( $use_root_padding ) { // Top and bottom padding are applied to the outer block container. $css .= '.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }'; // Right and left padding are applied to the first container with `.has-global-padding` class. $css .= '.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; // Alignfull children of the container with left and right padding have negative margins so they can still be full width. $css .= '.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }'; // Nested children of the container with left and right padding that are not full aligned do not get padding, unless they are direct children of an alignfull flow container. $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) { padding-right: 0; padding-left: 0; }'; // Alignfull direct children of the containers that are targeted by the rule above do not need negative margins. $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) > .alignfull { margin-left: 0; margin-right: 0; }'; } $css .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; $css .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; $css .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; // Block gap styles will be output unless explicitly set to `null`. See static::PROTECTED_PROPERTIES. if ( isset( $this->theme_json['settings']['spacing']['blockGap'] ) ) { $block_gap_value = static::get_property_value( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ) ); $css .= ":where(.wp-site-blocks) > * { margin-block-start: $block_gap_value; margin-block-end: 0; }"; $css .= ':where(.wp-site-blocks) > :first-child { margin-block-start: 0; }'; $css .= ':where(.wp-site-blocks) > :last-child { margin-block-end: 0; }'; // For backwards compatibility, ensure the legacy block gap CSS variable is still available. $css .= static::ROOT_CSS_PROPERTIES_SELECTOR . " { --wp--style--block-gap: $block_gap_value; }"; } $css .= $this->get_layout_styles( $block_metadata ); return $css; } /** * For metadata values that can either be booleans or paths to booleans, gets the value. * * $data = array( * 'color' => array( * 'defaultPalette' => true * ) * ); * * static::get_metadata_boolean( $data, false ); * // => false * * static::get_metadata_boolean( $data, array( 'color', 'defaultPalette' ) ); * // => true * * @since 6.0.0 * * @param array $data The data to inspect. * @param bool|array $path Boolean or path to a boolean. * @param bool $default_value Default value if the referenced path is missing. * Default false. * @return bool Value of boolean metadata. */ protected static function get_metadata_boolean( $data, $path, $default_value = false ) { if ( is_bool( $path ) ) { return $path; } if ( is_array( $path ) ) { $value = _wp_array_get( $data, $path ); if ( null !== $value ) { return $value; } } return $default_value; } /** * Merges new incoming data. * * @since 5.8.0 * @since 5.9.0 Duotone preset also has origins. * @since 6.7.0 Replace background image objects during merge. * * @param WP_Theme_JSON $incoming Data to merge. */ public function merge( $incoming ) { $incoming_data = $incoming->get_raw_data(); $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data ); /* * Recompute all the spacing sizes based on the new hierarchy of data. In the constructor * spacingScale and spacingSizes are both keyed by origin and VALID_ORIGINS is ordered, so * we can allow partial spacingScale data to inherit missing data from earlier layers when * computing the spacing sizes. * * This happens before the presets are merged to ensure that default spacing sizes can be * removed from the theme origin if $prevent_override is true. */ $flattened_spacing_scale = array(); foreach ( static::VALID_ORIGINS as $origin ) { $scale_path = array( 'settings', 'spacing', 'spacingScale', $origin ); // Apply the base spacing scale to the current layer. $base_spacing_scale = _wp_array_get( $this->theme_json, $scale_path, array() ); $flattened_spacing_scale = array_replace( $flattened_spacing_scale, $base_spacing_scale ); $spacing_scale = _wp_array_get( $incoming_data, $scale_path, null ); if ( ! isset( $spacing_scale ) ) { continue; } // Allow partial scale settings by merging with lower layers. $flattened_spacing_scale = array_replace( $flattened_spacing_scale, $spacing_scale ); // Generate and merge the scales for this layer. $sizes_path = array( 'settings', 'spacing', 'spacingSizes', $origin ); $spacing_sizes = _wp_array_get( $incoming_data, $sizes_path, array() ); $spacing_scale_sizes = static::compute_spacing_sizes( $flattened_spacing_scale ); $merged_spacing_sizes = static::merge_spacing_sizes( $spacing_scale_sizes, $spacing_sizes ); _wp_array_set( $incoming_data, $sizes_path, $merged_spacing_sizes ); } /* * The array_replace_recursive algorithm merges at the leaf level, * but we don't want leaf arrays to be merged, so we overwrite it. * * For leaf values that are sequential arrays it will use the numeric indexes for replacement. * We rather replace the existing with the incoming value, if it exists. * This is the case of spacing.units. * * For leaf values that are associative arrays it will merge them as expected. * This is also not the behavior we want for the current associative arrays (presets). * We rather replace the existing with the incoming value, if it exists. * This happens, for example, when we merge data from theme.json upon existing * theme supports or when we merge anything coming from the same source twice. * This is the case of color.palette, color.gradients, color.duotone, * typography.fontSizes, or typography.fontFamilies. * * Additionally, for some preset types, we also want to make sure the * values they introduce don't conflict with default values. We do so * by checking the incoming slugs for theme presets and compare them * with the equivalent default presets: if a slug is present as a default * we remove it from the theme presets. */ $nodes = static::get_setting_nodes( $incoming_data ); $slugs_global = static::get_default_slugs( $this->theme_json, array( 'settings' ) ); foreach ( $nodes as $node ) { // Replace the spacing.units. $path = $node['path']; $path[] = 'spacing'; $path[] = 'units'; $content = _wp_array_get( $incoming_data, $path, null ); if ( isset( $content ) ) { _wp_array_set( $this->theme_json, $path, $content ); } // Replace the presets. foreach ( static::PRESETS_METADATA as $preset_metadata ) { $prevent_override = $preset_metadata['prevent_override']; if ( is_array( $prevent_override ) ) { $global_path = array_merge( array( 'settings' ), $prevent_override ); $global_value = _wp_array_get( $this->theme_json, $global_path, null ); $node_level_path = array_merge( $node['path'], $prevent_override ); $prevent_override = _wp_array_get( $this->theme_json, $node_level_path, $global_value ); } foreach ( static::VALID_ORIGINS as $origin ) { $base_path = $node['path']; foreach ( $preset_metadata['path'] as $leaf ) { $base_path[] = $leaf; } $path = $base_path; $path[] = $origin; $content = _wp_array_get( $incoming_data, $path, null ); if ( ! isset( $content ) ) { continue; } // Set names for theme presets based on the slug if they are not set and can use default names. if ( 'theme' === $origin && $preset_metadata['use_default_names'] ) { foreach ( $content as $key => $item ) { if ( ! isset( $item['name'] ) ) { $name = static::get_name_from_defaults( $item['slug'], $base_path ); if ( null !== $name ) { $content[ $key ]['name'] = $name; } } } } // Filter out default slugs from theme presets when defaults should not be overridden. if ( 'theme' === $origin && $prevent_override ) { $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] ); $preset_global = _wp_array_get( $slugs_global, $preset_metadata['path'], array() ); $preset_node = _wp_array_get( $slugs_node, $preset_metadata['path'], array() ); $preset_slugs = array_merge_recursive( $preset_global, $preset_node ); $content = static::filter_slugs( $content, $preset_slugs ); } _wp_array_set( $this->theme_json, $path, $content ); } } } /* * Style values are merged at the leaf level, however * some values provide exceptions, namely style values that are * objects and represent unique definitions for the style. */ $style_nodes = static::get_block_nodes( $this->theme_json, array(), array( 'include_node_paths_only' => true ) ); // Add top-level styles. $style_nodes[] = array( 'path' => array( 'styles' ) ); foreach ( $style_nodes as $style_node ) { $path = $style_node['path']; /* * Background image styles should be replaced, not merged, * as they themselves are specific object definitions for the style. */ $background_image_path = array_merge( $path, static::PROPERTIES_METADATA['background-image'] ); $content = _wp_array_get( $incoming_data, $background_image_path, null ); if ( isset( $content ) ) { _wp_array_set( $this->theme_json, $background_image_path, $content ); } } } /** * Converts all filter (duotone) presets into SVGs. * * @since 5.9.1 * * @param array $origins List of origins to process. * @return string SVG filters. */ public function get_svg_filters( $origins ) { $blocks_metadata = static::get_blocks_metadata(); $setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata ); $filters = ''; foreach ( $setting_nodes as $metadata ) { $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); if ( empty( $node['color']['duotone'] ) ) { continue; } $duotone_presets = $node['color']['duotone']; foreach ( $origins as $origin ) { if ( ! isset( $duotone_presets[ $origin ] ) ) { continue; } foreach ( $duotone_presets[ $origin ] as $duotone_preset ) { $filters .= WP_Duotone::get_filter_svg_from_preset( $duotone_preset ); } } } return $filters; } /** * Determines whether a presets should be overridden or not. * * @since 5.9.0 * @deprecated 6.0.0 Use {@see 'get_metadata_boolean'} instead. * * @param array $theme_json The theme.json like structure to inspect. * @param array $path Path to inspect. * @param bool|array $override Data to compute whether to override the preset. * @return bool */ protected static function should_override_preset( $theme_json, $path, $override ) { _deprecated_function( __METHOD__, '6.0.0', 'get_metadata_boolean' ); if ( is_bool( $override ) ) { return $override; } /* * The relationship between whether to override the defaults * and whether the defaults are enabled is inverse: * * - If defaults are enabled => theme presets should not be overridden * - If defaults are disabled => theme presets should be overridden * * For example, a theme sets defaultPalette to false, * making the default palette hidden from the user. * In that case, we want all the theme presets to be present, * so they should override the defaults. */ if ( is_array( $override ) ) { $value = _wp_array_get( $theme_json, array_merge( $path, $override ) ); if ( isset( $value ) ) { return ! $value; } // Search the top-level key if none was found for this node. $value = _wp_array_get( $theme_json, array_merge( array( 'settings' ), $override ) ); if ( isset( $value ) ) { return ! $value; } return true; } } /** * Returns the default slugs for all the presets in an associative array * whose keys are the preset paths and the leaves is the list of slugs. * * For example: * * array( * 'color' => array( * 'palette' => array( 'slug-1', 'slug-2' ), * 'gradients' => array( 'slug-3', 'slug-4' ), * ), * ) * * @since 5.9.0 * * @param array $data A theme.json like structure. * @param array $node_path The path to inspect. It's 'settings' by default. * @return array */ protected static function get_default_slugs( $data, $node_path ) { $slugs = array(); foreach ( static::PRESETS_METADATA as $metadata ) { $path = $node_path; foreach ( $metadata['path'] as $leaf ) { $path[] = $leaf; } $path[] = 'default'; $preset = _wp_array_get( $data, $path, null ); if ( ! isset( $preset ) ) { continue; } $slugs_for_preset = array(); foreach ( $preset as $item ) { if ( isset( $item['slug'] ) ) { $slugs_for_preset[] = $item['slug']; } } _wp_array_set( $slugs, $metadata['path'], $slugs_for_preset ); } return $slugs; } /** * Gets a `default`'s preset name by a provided slug. * * @since 5.9.0 * * @param string $slug The slug we want to find a match from default presets. * @param array $base_path The path to inspect. It's 'settings' by default. * @return string|null */ protected function get_name_from_defaults( $slug, $base_path ) { $path = $base_path; $path[] = 'default'; $default_content = _wp_array_get( $this->theme_json, $path, null ); if ( ! $default_content ) { return null; } foreach ( $default_content as $item ) { if ( $slug === $item['slug'] ) { return $item['name']; } } return null; } /** * Removes the preset values whose slug is equal to any of given slugs. * * @since 5.9.0 * * @param array $node The node with the presets to validate. * @param array $slugs The slugs that should not be overridden. * @return array The new node. */ protected static function filter_slugs( $node, $slugs ) { if ( empty( $slugs ) ) { return $node; } $new_node = array(); foreach ( $node as $value ) { if ( isset( $value['slug'] ) && ! in_array( $value['slug'], $slugs, true ) ) { $new_node[] = $value; } } return $new_node; } /** * Removes insecure data from theme.json. * * @since 5.9.0 * @since 6.3.2 Preserves global styles block variations when securing styles. * @since 6.6.0 Updated to allow variation element styles and $origin parameter. * * @param array $theme_json Structure to sanitize. * @param string $origin Optional. What source of data this object represents. * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'. * @return array Sanitized structure. */ public static function remove_insecure_properties( $theme_json, $origin = 'theme' ) { if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { $origin = 'theme'; } $sanitized = array(); $theme_json = WP_Theme_JSON_Schema::migrate( $theme_json, $origin ); $blocks_metadata = static::get_blocks_metadata(); $valid_block_names = array_keys( $blocks_metadata ); $valid_element_names = array_keys( static::ELEMENTS ); $valid_variations = static::get_valid_block_style_variations( $blocks_metadata ); $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names, $valid_variations ); $blocks_metadata = static::get_blocks_metadata(); $style_options = array( 'include_block_style_variations' => true ); // Allow variations data. $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata, $style_options ); foreach ( $style_nodes as $metadata ) { $input = _wp_array_get( $theme_json, $metadata['path'], array() ); if ( empty( $input ) ) { continue; } // The global styles custom CSS is not sanitized, but can only be edited by users with 'edit_css' capability. if ( isset( $input['css'] ) && current_user_can( 'edit_css' ) ) { $output = $input; } else { $output = static::remove_insecure_styles( $input ); } /* * Get a reference to element name from path. * $metadata['path'] = array( 'styles', 'elements', 'link' ); */ $current_element = $metadata['path'][ count( $metadata['path'] ) - 1 ]; /* * $output is stripped of pseudo selectors. Re-add and process them * or insecure styles here. */ if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) ) { foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] as $pseudo_selector ) { if ( isset( $input[ $pseudo_selector ] ) ) { $output[ $pseudo_selector ] = static::remove_insecure_styles( $input[ $pseudo_selector ] ); } } } if ( ! empty( $output ) ) { _wp_array_set( $sanitized, $metadata['path'], $output ); } if ( isset( $metadata['variations'] ) ) { foreach ( $metadata['variations'] as $variation ) { $variation_input = _wp_array_get( $theme_json, $variation['path'], array() ); if ( empty( $variation_input ) ) { continue; } $variation_output = static::remove_insecure_styles( $variation_input ); if ( isset( $variation_input['blocks'] ) ) { $variation_output['blocks'] = static::remove_insecure_inner_block_styles( $variation_input['blocks'] ); } if ( isset( $variation_input['elements'] ) ) { $variation_output['elements'] = static::remove_insecure_element_styles( $variation_input['elements'] ); } if ( ! empty( $variation_output ) ) { _wp_array_set( $sanitized, $variation['path'], $variation_output ); } } } } $setting_nodes = static::get_setting_nodes( $theme_json ); foreach ( $setting_nodes as $metadata ) { $input = _wp_array_get( $theme_json, $metadata['path'], array() ); if ( empty( $input ) ) { continue; } $output = static::remove_insecure_settings( $input ); if ( ! empty( $output ) ) { _wp_array_set( $sanitized, $metadata['path'], $output ); } } if ( empty( $sanitized['styles'] ) ) { unset( $theme_json['styles'] ); } else { $theme_json['styles'] = $sanitized['styles']; } if ( empty( $sanitized['settings'] ) ) { unset( $theme_json['settings'] ); } else { $theme_json['settings'] = $sanitized['settings']; } return $theme_json; } /** * Remove insecure element styles within a variation or block. * * @since 6.8.0 * * @param array $elements The elements to process. * @return array The sanitized elements styles. */ protected static function remove_insecure_element_styles( $elements ) { $sanitized = array(); $valid_element_names = array_keys( static::ELEMENTS ); foreach ( $valid_element_names as $element_name ) { $element_input = $elements[ $element_name ] ?? null; if ( $element_input ) { $element_output = static::remove_insecure_styles( $element_input ); if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) { foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) { if ( isset( $element_input[ $pseudo_selector ] ) ) { $element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] ); } } } $sanitized[ $element_name ] = $element_output; } } return $sanitized; } /** * Remove insecure styles from inner blocks and their elements. * * @since 6.8.0 * * @param array $blocks The block styles to process. * @return array Sanitized block type styles. */ protected static function remove_insecure_inner_block_styles( $blocks ) { $sanitized = array(); foreach ( $blocks as $block_type => $block_input ) { $block_output = static::remove_insecure_styles( $block_input ); if ( isset( $block_input['elements'] ) ) { $block_output['elements'] = static::remove_insecure_element_styles( $block_input['elements'] ); } $sanitized[ $block_type ] = $block_output; } return $sanitized; } /** * Processes a setting node and returns the same node * without the insecure settings. * * @since 5.9.0 * * @param array $input Node to process. * @return array */ protected static function remove_insecure_settings( $input ) { $output = array(); foreach ( static::PRESETS_METADATA as $preset_metadata ) { foreach ( static::VALID_ORIGINS as $origin ) { $path_with_origin = $preset_metadata['path']; $path_with_origin[] = $origin; $presets = _wp_array_get( $input, $path_with_origin, null ); if ( null === $presets ) { continue; } $escaped_preset = array(); foreach ( $presets as $preset ) { if ( esc_attr( esc_html( $preset['name'] ) ) === $preset['name'] && sanitize_html_class( $preset['slug'] ) === $preset['slug'] ) { $value = null; if ( isset( $preset_metadata['value_key'], $preset[ $preset_metadata['value_key'] ] ) ) { $value = $preset[ $preset_metadata['value_key'] ]; } elseif ( isset( $preset_metadata['value_func'] ) && is_callable( $preset_metadata['value_func'] ) ) { $value = call_user_func( $preset_metadata['value_func'], $preset ); } $preset_is_valid = true; foreach ( $preset_metadata['properties'] as $property ) { if ( ! static::is_safe_css_declaration( $property, $value ) ) { $preset_is_valid = false; break; } } if ( $preset_is_valid ) { $escaped_preset[] = $preset; } } } if ( ! empty( $escaped_preset ) ) { _wp_array_set( $output, $path_with_origin, $escaped_preset ); } } } // Ensure indirect properties not included in any `PRESETS_METADATA` value are allowed. static::remove_indirect_properties( $input, $output ); return $output; } /** * Processes a style node and returns the same node * without the insecure styles. * * @since 5.9.0 * * @param array $input Node to process. * @return array */ protected static function remove_insecure_styles( $input ) { $output = array(); $declarations = static::compute_style_properties( $input ); foreach ( $declarations as $declaration ) { if ( static::is_safe_css_declaration( $declaration['name'], $declaration['value'] ) ) { $path = static::PROPERTIES_METADATA[ $declaration['name'] ]; /* * Check the value isn't an array before adding so as to not * double up shorthand and longhand styles. */ $value = _wp_array_get( $input, $path, array() ); if ( ! is_array( $value ) ) { _wp_array_set( $output, $path, $value ); } } } // Ensure indirect properties not handled by `compute_style_properties` are allowed. static::remove_indirect_properties( $input, $output ); return $output; } /** * Checks that a declaration provided by the user is safe. * * @since 5.9.0 * * @param string $property_name Property name in a CSS declaration, i.e. the `color` in `color: red`. * @param string $property_value Value in a CSS declaration, i.e. the `red` in `color: red`. * @return bool */ protected static function is_safe_css_declaration( $property_name, $property_value ) { $style_to_validate = $property_name . ': ' . $property_value; $filtered = esc_html( safecss_filter_attr( $style_to_validate ) ); return ! empty( trim( $filtered ) ); } /** * Removes indirect properties from the given input node and * sets in the given output node. * * @since 6.2.0 * * @param array $input Node to process. * @param array $output The processed node. Passed by reference. */ private static function remove_indirect_properties( $input, &$output ) { foreach ( static::INDIRECT_PROPERTIES_METADATA as $property => $paths ) { foreach ( $paths as $path ) { $value = _wp_array_get( $input, $path ); if ( is_string( $value ) && static::is_safe_css_declaration( $property, $value ) ) { _wp_array_set( $output, $path, $value ); } } } } /** * Returns the raw data. * * @since 5.8.0 * * @return array Raw data. */ public function get_raw_data() { return $this->theme_json; } /** * Transforms the given editor settings according the * add_theme_support format to the theme.json format. * * @since 5.8.0 * * @param array $settings Existing editor settings. * @return array Config that adheres to the theme.json schema. */ public static function get_from_editor_settings( $settings ) { $theme_settings = array( 'version' => static::LATEST_SCHEMA, 'settings' => array(), ); // Deprecated theme supports. if ( isset( $settings['disableCustomColors'] ) ) { $theme_settings['settings']['color']['custom'] = ! $settings['disableCustomColors']; } if ( isset( $settings['disableCustomGradients'] ) ) { $theme_settings['settings']['color']['customGradient'] = ! $settings['disableCustomGradients']; } if ( isset( $settings['disableCustomFontSizes'] ) ) { $theme_settings['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; } if ( isset( $settings['enableCustomLineHeight'] ) ) { $theme_settings['settings']['typography']['lineHeight'] = $settings['enableCustomLineHeight']; } if ( isset( $settings['enableCustomUnits'] ) ) { $theme_settings['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? array( 'px', 'em', 'rem', 'vh', 'vw', '%' ) : $settings['enableCustomUnits']; } if ( isset( $settings['colors'] ) ) { $theme_settings['settings']['color']['palette'] = $settings['colors']; } if ( isset( $settings['gradients'] ) ) { $theme_settings['settings']['color']['gradients'] = $settings['gradients']; } if ( isset( $settings['fontSizes'] ) ) { $font_sizes = $settings['fontSizes']; // Back-compatibility for presets without units. foreach ( $font_sizes as $key => $font_size ) { if ( is_numeric( $font_size['size'] ) ) { $font_sizes[ $key ]['size'] = $font_size['size'] . 'px'; } } $theme_settings['settings']['typography']['fontSizes'] = $font_sizes; } if ( isset( $settings['enableCustomSpacing'] ) ) { $theme_settings['settings']['spacing']['padding'] = $settings['enableCustomSpacing']; } if ( isset( $settings['spacingSizes'] ) ) { $theme_settings['settings']['spacing']['spacingSizes'] = $settings['spacingSizes']; } return $theme_settings; } /** * Returns the current theme's wanted patterns(slugs) to be * registered from Pattern Directory. * * @since 6.0.0 * * @return string[] */ public function get_patterns() { if ( isset( $this->theme_json['patterns'] ) && is_array( $this->theme_json['patterns'] ) ) { return $this->theme_json['patterns']; } return array(); } /** * Returns a valid theme.json as provided by a theme. * * Unlike get_raw_data() this returns the presets flattened, as provided by a theme. * This also uses appearanceTools instead of their opt-ins if all of them are true. * * @since 6.0.0 * * @return array */ public function get_data() { $output = $this->theme_json; $nodes = static::get_setting_nodes( $output ); /** * Flatten the theme & custom origins into a single one. * * For example, the following: * * { * "settings": { * "color": { * "palette": { * "theme": [ {} ], * "custom": [ {} ] * } * } * } * } * * will be converted to: * * { * "settings": { * "color": { * "palette": [ {} ] * } * } * } */ foreach ( $nodes as $node ) { foreach ( static::PRESETS_METADATA as $preset_metadata ) { $path = $node['path']; foreach ( $preset_metadata['path'] as $preset_metadata_path ) { $path[] = $preset_metadata_path; } $preset = _wp_array_get( $output, $path, null ); if ( null === $preset ) { continue; } $items = array(); if ( isset( $preset['theme'] ) ) { foreach ( $preset['theme'] as $item ) { $slug = $item['slug']; unset( $item['slug'] ); $items[ $slug ] = $item; } } if ( isset( $preset['custom'] ) ) { foreach ( $preset['custom'] as $item ) { $slug = $item['slug']; unset( $item['slug'] ); $items[ $slug ] = $item; } } $flattened_preset = array(); foreach ( $items as $slug => $value ) { $flattened_preset[] = array_merge( array( 'slug' => (string) $slug ), $value ); } _wp_array_set( $output, $path, $flattened_preset ); } } /* * If all of the static::APPEARANCE_TOOLS_OPT_INS are true, * this code unsets them and sets 'appearanceTools' instead. */ foreach ( $nodes as $node ) { $all_opt_ins_are_set = true; foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { $full_path = $node['path']; foreach ( $opt_in_path as $opt_in_path_item ) { $full_path[] = $opt_in_path_item; } /* * Use "unset prop" as a marker instead of "null" because * "null" can be a valid value for some props (e.g. blockGap). */ $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); if ( 'unset prop' === $opt_in_value ) { $all_opt_ins_are_set = false; break; } } if ( $all_opt_ins_are_set ) { $node_path_with_appearance_tools = $node['path']; $node_path_with_appearance_tools[] = 'appearanceTools'; _wp_array_set( $output, $node_path_with_appearance_tools, true ); foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { $full_path = $node['path']; foreach ( $opt_in_path as $opt_in_path_item ) { $full_path[] = $opt_in_path_item; } /* * Use "unset prop" as a marker instead of "null" because * "null" can be a valid value for some props (e.g. blockGap). */ $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); if ( true !== $opt_in_value ) { continue; } /* * The following could be improved to be path independent. * At the moment it relies on a couple of assumptions: * * - all opt-ins having a path of size 2. * - there's two sources of settings: the top-level and the block-level. */ if ( ( 1 === count( $node['path'] ) ) && ( 'settings' === $node['path'][0] ) ) { // Top-level settings. unset( $output['settings'][ $opt_in_path[0] ][ $opt_in_path[1] ] ); if ( empty( $output['settings'][ $opt_in_path[0] ] ) ) { unset( $output['settings'][ $opt_in_path[0] ] ); } } elseif ( ( 3 === count( $node['path'] ) ) && ( 'settings' === $node['path'][0] ) && ( 'blocks' === $node['path'][1] ) ) { // Block-level settings. $block_name = $node['path'][2]; unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ][ $opt_in_path[1] ] ); if ( empty( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ) ) { unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ); } } } } } wp_recursive_ksort( $output ); return $output; } /** * Sets the spacingSizes array based on the spacingScale values from theme.json. * * @since 6.1.0 * @deprecated 6.6.0 No longer used as the spacingSizes are automatically * generated in the constructor and merge methods instead * of manually after instantiation. * * @return null|void */ public function set_spacing_sizes() { _deprecated_function( __METHOD__, '6.6.0' ); $spacing_scale = isset( $this->theme_json['settings']['spacing']['spacingScale'] ) ? $this->theme_json['settings']['spacing']['spacingScale'] : array(); if ( ! isset( $spacing_scale['steps'] ) || ! is_numeric( $spacing_scale['steps'] ) || ! isset( $spacing_scale['mediumStep'] ) || ! isset( $spacing_scale['unit'] ) || ! isset( $spacing_scale['operator'] ) || ! isset( $spacing_scale['increment'] ) || ! isset( $spacing_scale['steps'] ) || ! is_numeric( $spacing_scale['increment'] ) || ! is_numeric( $spacing_scale['mediumStep'] ) || ( '+' !== $spacing_scale['operator'] && '*' !== $spacing_scale['operator'] ) ) { if ( ! empty( $spacing_scale ) ) { wp_trigger_error( __METHOD__, sprintf( /* translators: 1: theme.json, 2: settings.spacing.spacingScale */ __( 'Some of the %1$s %2$s values are invalid' ), 'theme.json', 'settings.spacing.spacingScale' ), E_USER_NOTICE ); } return null; } // If theme authors want to prevent the generation of the core spacing scale they can set their theme.json spacingScale.steps to 0. if ( 0 === $spacing_scale['steps'] ) { return null; } $spacing_sizes = static::compute_spacing_sizes( $spacing_scale ); // If there are 7 or fewer steps in the scale revert to numbers for labels instead of t-shirt sizes. if ( $spacing_scale['steps'] <= 7 ) { for ( $spacing_sizes_count = 0; $spacing_sizes_count < count( $spacing_sizes ); $spacing_sizes_count++ ) { $spacing_sizes[ $spacing_sizes_count ]['name'] = (string) ( $spacing_sizes_count + 1 ); } } _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes ); } /** * Merges two sets of spacing size presets. * * @since 6.6.0 * * @param array $base The base set of spacing sizes. * @param array $incoming The set of spacing sizes to merge with the base. Duplicate slugs will override the base values. * @return array The merged set of spacing sizes. */ private static function merge_spacing_sizes( $base, $incoming ) { // Preserve the order if there are no base (spacingScale) values. if ( empty( $base ) ) { return $incoming; } $merged = array(); foreach ( $base as $item ) { $merged[ $item['slug'] ] = $item; } foreach ( $incoming as $item ) { $merged[ $item['slug'] ] = $item; } ksort( $merged, SORT_NUMERIC ); return array_values( $merged ); } /** * Generates a set of spacing sizes by starting with a medium size and * applying an operator with an increment value to generate the rest of the * sizes outward from the medium size. The medium slug is '50' with the rest * of the slugs being 10 apart. The generated names use t-shirt sizing. * * Example: * * $spacing_scale = array( * 'steps' => 4, * 'mediumStep' => 16, * 'unit' => 'px', * 'operator' => '+', * 'increment' => 2, * ); * $spacing_sizes = static::compute_spacing_sizes( $spacing_scale ); * // -> array( * // array( 'name' => 'Small', 'slug' => '40', 'size' => '14px' ), * // array( 'name' => 'Medium', 'slug' => '50', 'size' => '16px' ), * // array( 'name' => 'Large', 'slug' => '60', 'size' => '18px' ), * // array( 'name' => 'X-Large', 'slug' => '70', 'size' => '20px' ), * // ) * * @since 6.6.0 * * @param array $spacing_scale { * The spacing scale values. All are required. * * @type int $steps The number of steps in the scale. (up to 10 steps are supported.) * @type float $mediumStep The middle value that gets the slug '50'. (For even number of steps, this becomes the first middle value.) * @type string $unit The CSS unit to use for the sizes. * @type string $operator The mathematical operator to apply to generate the other sizes. Either '+' or '*'. * @type float $increment The value used with the operator to generate the other sizes. * } * @return array The spacing sizes presets or an empty array if some spacing scale values are missing or invalid. */ private static function compute_spacing_sizes( $spacing_scale ) { /* * This condition is intentionally missing some checks on ranges for the values in order to * keep backwards compatibility with the previous implementation. */ if ( ! isset( $spacing_scale['steps'] ) || ! is_numeric( $spacing_scale['steps'] ) || 0 === $spacing_scale['steps'] || ! isset( $spacing_scale['mediumStep'] ) || ! is_numeric( $spacing_scale['mediumStep'] ) || ! isset( $spacing_scale['unit'] ) || ! isset( $spacing_scale['operator'] ) || ( '+' !== $spacing_scale['operator'] && '*' !== $spacing_scale['operator'] ) || ! isset( $spacing_scale['increment'] ) || ! is_numeric( $spacing_scale['increment'] ) ) { return array(); } $unit = '%' === $spacing_scale['unit'] ? '%' : sanitize_title( $spacing_scale['unit'] ); $current_step = $spacing_scale['mediumStep']; $steps_mid_point = round( $spacing_scale['steps'] / 2, 0 ); $x_small_count = null; $below_sizes = array(); $slug = 40; $remainder = 0; for ( $below_midpoint_count = $steps_mid_point - 1; $spacing_scale['steps'] > 1 && $slug > 0 && $below_midpoint_count > 0; $below_midpoint_count-- ) { if ( '+' === $spacing_scale['operator'] ) { $current_step -= $spacing_scale['increment']; } elseif ( $spacing_scale['increment'] > 1 ) { $current_step /= $spacing_scale['increment']; } else { $current_step *= $spacing_scale['increment']; } if ( $current_step <= 0 ) { $remainder = $below_midpoint_count; break; } $below_sizes[] = array( /* translators: %s: Digit to indicate multiple of sizing, eg. 2X-Small. */ 'name' => $below_midpoint_count === $steps_mid_point - 1 ? __( 'Small' ) : sprintf( __( '%sX-Small' ), (string) $x_small_count ), 'slug' => (string) $slug, 'size' => round( $current_step, 2 ) . $unit, ); if ( $below_midpoint_count === $steps_mid_point - 2 ) { $x_small_count = 2; } if ( $below_midpoint_count < $steps_mid_point - 2 ) { ++$x_small_count; } $slug -= 10; } $below_sizes = array_reverse( $below_sizes ); $below_sizes[] = array( 'name' => __( 'Medium' ), 'slug' => '50', 'size' => $spacing_scale['mediumStep'] . $unit, ); $current_step = $spacing_scale['mediumStep']; $x_large_count = null; $above_sizes = array(); $slug = 60; $steps_above = ( $spacing_scale['steps'] - $steps_mid_point ) + $remainder; for ( $above_midpoint_count = 0; $above_midpoint_count < $steps_above; $above_midpoint_count++ ) { $current_step = '+' === $spacing_scale['operator'] ? $current_step + $spacing_scale['increment'] : ( $spacing_scale['increment'] >= 1 ? $current_step * $spacing_scale['increment'] : $current_step / $spacing_scale['increment'] ); $above_sizes[] = array( /* translators: %s: Digit to indicate multiple of sizing, eg. 2X-Large. */ 'name' => 0 === $above_midpoint_count ? __( 'Large' ) : sprintf( __( '%sX-Large' ), (string) $x_large_count ), 'slug' => (string) $slug, 'size' => round( $current_step, 2 ) . $unit, ); if ( 1 === $above_midpoint_count ) { $x_large_count = 2; } if ( $above_midpoint_count > 1 ) { ++$x_large_count; } $slug += 10; } $spacing_sizes = $below_sizes; foreach ( $above_sizes as $above_sizes_item ) { $spacing_sizes[] = $above_sizes_item; } return $spacing_sizes; } /** * This is used to convert the internal representation of variables to the CSS representation. * For example, `var:preset|color|vivid-green-cyan` becomes `var(--wp--preset--color--vivid-green-cyan)`. * * @since 6.3.0 * * @param string $value The variable such as var:preset|color|vivid-green-cyan to convert. * @return string The converted variable. */ private static function convert_custom_properties( $value ) { $prefix = 'var:'; $prefix_len = strlen( $prefix ); $token_in = '|'; $token_out = '--'; if ( str_starts_with( $value, $prefix ) ) { $unwrapped_name = str_replace( $token_in, $token_out, substr( $value, $prefix_len ) ); $value = "var(--wp--$unwrapped_name)"; } return $value; } /** * Given a tree, converts the internal representation of variables to the CSS representation. * It is recursive and modifies the input in-place. * * @since 6.3.0 * * @param array $tree Input to process. * @return array The modified $tree. */ private static function resolve_custom_css_format( $tree ) { $prefix = 'var:'; foreach ( $tree as $key => $data ) { if ( is_string( $data ) && str_starts_with( $data, $prefix ) ) { $tree[ $key ] = self::convert_custom_properties( $data ); } elseif ( is_array( $data ) ) { $tree[ $key ] = self::resolve_custom_css_format( $data ); } } return $tree; } /** * Returns the selectors metadata for a block. * * @since 6.3.0 * * @param object $block_type The block type. * @param string $root_selector The block's root selector. * @return array The custom selectors set by the block. */ protected static function get_block_selectors( $block_type, $root_selector ) { if ( ! empty( $block_type->selectors ) ) { return $block_type->selectors; } $selectors = array( 'root' => $root_selector ); foreach ( static::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $key => $feature ) { $feature_selector = wp_get_block_css_selector( $block_type, $key ); if ( null !== $feature_selector ) { $selectors[ $feature ] = array( 'root' => $feature_selector ); } } return $selectors; } /** * Generates all the element selectors for a block. * * @since 6.3.0 * * @param string $root_selector The block's root CSS selector. * @return array The block's element selectors. */ protected static function get_block_element_selectors( $root_selector ) { /* * Assign defaults, then override those that the block sets by itself. * If the block selector is compounded, will append the element to each * individual block selector. */ $block_selectors = explode( ',', $root_selector ); $element_selectors = array(); foreach ( static::ELEMENTS as $el_name => $el_selector ) { $element_selector = array(); foreach ( $block_selectors as $selector ) { if ( $selector === $el_selector ) { $element_selector = array( $el_selector ); break; } $element_selector[] = static::prepend_to_selector( $el_selector, $selector . ' ' ); } $element_selectors[ $el_name ] = implode( ',', $element_selector ); } return $element_selectors; } /** * Generates style declarations for a node's features e.g., color, border, * typography etc. that have custom selectors in their related block's * metadata. * * @since 6.3.0 * * @param object $metadata The related block metadata containing selectors. * @param object $node A merged theme.json node for block or variation. * @return array The style declarations for the node's features with custom * selectors. */ protected function get_feature_declarations_for_node( $metadata, &$node ) { $declarations = array(); if ( ! isset( $metadata['selectors'] ) ) { return $declarations; } $settings = isset( $this->theme_json['settings'] ) ? $this->theme_json['settings'] : array(); foreach ( $metadata['selectors'] as $feature => $feature_selectors ) { /* * Skip if this is the block's root selector or the block doesn't * have any styles for the feature. */ if ( 'root' === $feature || empty( $node[ $feature ] ) ) { continue; } if ( is_array( $feature_selectors ) ) { foreach ( $feature_selectors as $subfeature => $subfeature_selector ) { if ( 'root' === $subfeature || empty( $node[ $feature ][ $subfeature ] ) ) { continue; } /* * Create temporary node containing only the subfeature data * to leverage existing `compute_style_properties` function. */ $subfeature_node = array( $feature => array( $subfeature => $node[ $feature ][ $subfeature ], ), ); // Generate style declarations. $new_declarations = static::compute_style_properties( $subfeature_node, $settings, null, $this->theme_json ); // Merge subfeature declarations into feature declarations. if ( isset( $declarations[ $subfeature_selector ] ) ) { foreach ( $new_declarations as $new_declaration ) { $declarations[ $subfeature_selector ][] = $new_declaration; } } else { $declarations[ $subfeature_selector ] = $new_declarations; } /* * Remove the subfeature from the block's node now its * styles will be included under its own selector not the * block's. */ unset( $node[ $feature ][ $subfeature ] ); } } /* * Now subfeatures have been processed and removed we can process * feature root selector or simple string selector. */ if ( is_string( $feature_selectors ) || ( isset( $feature_selectors['root'] ) && $feature_selectors['root'] ) ) { $feature_selector = is_string( $feature_selectors ) ? $feature_selectors : $feature_selectors['root']; /* * Create temporary node containing only the feature data * to leverage existing `compute_style_properties` function. */ $feature_node = array( $feature => $node[ $feature ] ); // Generate the style declarations. $new_declarations = static::compute_style_properties( $feature_node, $settings, null, $this->theme_json ); /* * Merge new declarations with any that already exist for * the feature selector. This may occur when multiple block * support features use the same custom selector. */ if ( isset( $declarations[ $feature_selector ] ) ) { foreach ( $new_declarations as $new_declaration ) { $declarations[ $feature_selector ][] = $new_declaration; } } else { $declarations[ $feature_selector ] = $new_declarations; } /* * Remove the feature from the block's node now its styles * will be included under its own selector not the block's. */ unset( $node[ $feature ] ); } } return $declarations; } /** * Replaces CSS variables with their values in place. * * @since 6.3.0 * @since 6.5.0 Check for empty style before processing its value. * * @param array $styles CSS declarations to convert. * @param array $values key => value pairs to use for replacement. * @return array */ private static function convert_variables_to_value( $styles, $values ) { foreach ( $styles as $key => $style ) { if ( empty( $style ) ) { continue; } if ( is_array( $style ) ) { $styles[ $key ] = self::convert_variables_to_value( $style, $values ); continue; } if ( 0 <= strpos( $style, 'var(' ) ) { // find all the variables in the string in the form of var(--variable-name, fallback), with fallback in the second capture group. $has_matches = preg_match_all( '/var\(([^),]+)?,?\s?(\S+)?\)/', $style, $var_parts ); if ( $has_matches ) { $resolved_style = $styles[ $key ]; foreach ( $var_parts[1] as $index => $var_part ) { $key_in_values = 'var(' . $var_part . ')'; $rule_to_replace = $var_parts[0][ $index ]; // the css rule to replace e.g. var(--wp--preset--color--vivid-green-cyan). $fallback = $var_parts[2][ $index ]; // the fallback value. $resolved_style = str_replace( array( $rule_to_replace, $fallback, ), array( isset( $values[ $key_in_values ] ) ? $values[ $key_in_values ] : $rule_to_replace, isset( $values[ $fallback ] ) ? $values[ $fallback ] : $fallback, ), $resolved_style ); } $styles[ $key ] = $resolved_style; } } } return $styles; } /** * Resolves the values of CSS variables in the given styles. * * @since 6.3.0 * * @param WP_Theme_JSON $theme_json The theme json resolver. * @return WP_Theme_JSON The $theme_json with resolved variables. */ public static function resolve_variables( $theme_json ) { $settings = $theme_json->get_settings(); $styles = $theme_json->get_raw_data()['styles']; $preset_vars = static::compute_preset_vars( $settings, static::VALID_ORIGINS ); $theme_vars = static::compute_theme_vars( $settings ); $vars = array_reduce( array_merge( $preset_vars, $theme_vars ), function ( $carry, $item ) { $name = $item['name']; $carry[ "var({$name})" ] = $item['value']; return $carry; }, array() ); $theme_json->theme_json['styles'] = self::convert_variables_to_value( $styles, $vars ); return $theme_json; } /** * Generates a selector for a block style variation. * * @since 6.5.0 * * @param string $variation_name Name of the block style variation. * @param string $block_selector CSS selector for the block. * @return string Block selector with block style variation selector added to it. */ protected static function get_block_style_variation_selector( $variation_name, $block_selector ) { $variation_class = ".is-style-$variation_name"; if ( ! $block_selector ) { return $variation_class; } $limit = 1; $selector_parts = explode( ',', $block_selector ); $result = array(); foreach ( $selector_parts as $part ) { $result[] = preg_replace_callback( '/((?::\([^)]+\))?\s*)([^\s:]+)/', function ( $matches ) use ( $variation_class ) { return $matches[1] . $matches[2] . $variation_class; }, $part, $limit ); } return implode( ',', $result ); } /** * Collects valid block style variations keyed by block type. * * @since 6.6.0 * @since 6.8.0 Added the `$blocks_metadata` parameter. * * @param array $blocks_metadata Optional. List of metadata per block. Default is the metadata for all blocks. * @return array Valid block style variations by block type. */ protected static function get_valid_block_style_variations( $blocks_metadata = array() ) { $valid_variations = array(); $blocks_metadata = empty( $blocks_metadata ) ? static::get_blocks_metadata() : $blocks_metadata; foreach ( $blocks_metadata as $block_name => $block_meta ) { if ( ! isset( $block_meta['styleVariations'] ) ) { continue; } $valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] ); } return $valid_variations; } } class-wp-error.php 0000666 00000016516 15226762357 0010171 0 ustar 00 <?php /** * WordPress Error API. * * @package WordPress */ /** * WordPress Error class. * * Container for checking for WordPress errors and error messages. Return * WP_Error and use is_wp_error() to check if this class is returned. Many * core WordPress functions pass this class in the event of an error and * if not handled properly will result in code errors. * * @since 2.1.0 */ #[AllowDynamicProperties] class WP_Error { /** * Stores the list of errors. * * @since 2.1.0 * @var array */ public $errors = array(); /** * Stores the most recently added data for each error code. * * @since 2.1.0 * @var array */ public $error_data = array(); /** * Stores previously added data added for error codes, oldest-to-newest by code. * * @since 5.6.0 * @var array[] */ protected $additional_data = array(); /** * Initializes the error. * * If `$code` is empty, the other parameters will be ignored. * When `$code` is not empty, `$message` will be used even if * it is empty. The `$data` parameter will be used only if it * is not empty. * * Though the class is constructed with a single error code and * message, multiple codes can be added using the `add()` method. * * @since 2.1.0 * * @param string|int $code Error code. * @param string $message Error message. * @param mixed $data Optional. Error data. Default empty string. */ public function __construct( $code = '', $message = '', $data = '' ) { if ( empty( $code ) ) { return; } $this->add( $code, $message, $data ); } /** * Retrieves all error codes. * * @since 2.1.0 * * @return array List of error codes, if available. */ public function get_error_codes() { if ( ! $this->has_errors() ) { return array(); } return array_keys( $this->errors ); } /** * Retrieves the first error code available. * * @since 2.1.0 * * @return string|int Empty string, if no error codes. */ public function get_error_code() { $codes = $this->get_error_codes(); if ( empty( $codes ) ) { return ''; } return $codes[0]; } /** * Retrieves all error messages, or the error messages for the given error code. * * @since 2.1.0 * * @param string|int $code Optional. Error code to retrieve the messages for. * Default empty string. * @return string[] Error strings on success, or empty array if there are none. */ public function get_error_messages( $code = '' ) { // Return all messages if no code specified. if ( empty( $code ) ) { $all_messages = array(); foreach ( (array) $this->errors as $code => $messages ) { $all_messages = array_merge( $all_messages, $messages ); } return $all_messages; } if ( isset( $this->errors[ $code ] ) ) { return $this->errors[ $code ]; } else { return array(); } } /** * Gets a single error message. * * This will get the first message available for the code. If no code is * given then the first code available will be used. * * @since 2.1.0 * * @param string|int $code Optional. Error code to retrieve the message for. * Default empty string. * @return string The error message. */ public function get_error_message( $code = '' ) { if ( empty( $code ) ) { $code = $this->get_error_code(); } $messages = $this->get_error_messages( $code ); if ( empty( $messages ) ) { return ''; } return $messages[0]; } /** * Retrieves the most recently added error data for an error code. * * @since 2.1.0 * * @param string|int $code Optional. Error code. Default empty string. * @return mixed Error data, if it exists. */ public function get_error_data( $code = '' ) { if ( empty( $code ) ) { $code = $this->get_error_code(); } if ( isset( $this->error_data[ $code ] ) ) { return $this->error_data[ $code ]; } } /** * Verifies if the instance contains errors. * * @since 5.1.0 * * @return bool If the instance contains errors. */ public function has_errors() { if ( ! empty( $this->errors ) ) { return true; } return false; } /** * Adds an error or appends an additional message to an existing error. * * @since 2.1.0 * * @param string|int $code Error code. * @param string $message Error message. * @param mixed $data Optional. Error data. Default empty string. */ public function add( $code, $message, $data = '' ) { $this->errors[ $code ][] = $message; if ( ! empty( $data ) ) { $this->add_data( $data, $code ); } /** * Fires when an error is added to a WP_Error object. * * @since 5.6.0 * * @param string|int $code Error code. * @param string $message Error message. * @param mixed $data Error data. Might be empty. * @param WP_Error $wp_error The WP_Error object. */ do_action( 'wp_error_added', $code, $message, $data, $this ); } /** * Adds data to an error with the given code. * * @since 2.1.0 * @since 5.6.0 Errors can now contain more than one item of error data. {@see WP_Error::$additional_data}. * * @param mixed $data Error data. * @param string|int $code Error code. */ public function add_data( $data, $code = '' ) { if ( empty( $code ) ) { $code = $this->get_error_code(); } if ( isset( $this->error_data[ $code ] ) ) { $this->additional_data[ $code ][] = $this->error_data[ $code ]; } $this->error_data[ $code ] = $data; } /** * Retrieves all error data for an error code in the order in which the data was added. * * @since 5.6.0 * * @param string|int $code Error code. * @return mixed[] Array of error data, if it exists. */ public function get_all_error_data( $code = '' ) { if ( empty( $code ) ) { $code = $this->get_error_code(); } $data = array(); if ( isset( $this->additional_data[ $code ] ) ) { $data = $this->additional_data[ $code ]; } if ( isset( $this->error_data[ $code ] ) ) { $data[] = $this->error_data[ $code ]; } return $data; } /** * Removes the specified error. * * This function removes all error messages associated with the specified * error code, along with any error data for that code. * * @since 4.1.0 * * @param string|int $code Error code. */ public function remove( $code ) { unset( $this->errors[ $code ] ); unset( $this->error_data[ $code ] ); unset( $this->additional_data[ $code ] ); } /** * Merges the errors in the given error object into this one. * * @since 5.6.0 * * @param WP_Error $error Error object to merge. */ public function merge_from( WP_Error $error ) { static::copy_errors( $error, $this ); } /** * Exports the errors in this object into the given one. * * @since 5.6.0 * * @param WP_Error $error Error object to export into. */ public function export_to( WP_Error $error ) { static::copy_errors( $this, $error ); } /** * Copies errors from one WP_Error instance to another. * * @since 5.6.0 * * @param WP_Error $from The WP_Error to copy from. * @param WP_Error $to The WP_Error to copy to. */ protected static function copy_errors( WP_Error $from, WP_Error $to ) { foreach ( $from->get_error_codes() as $code ) { foreach ( $from->get_error_messages( $code ) as $error_message ) { $to->add( $code, $error_message ); } foreach ( $from->get_all_error_data( $code ) as $data ) { $to->add_data( $data, $code ); } } } } class-wp-customize-nav-menu-locations-control.php 0000666 00000005401 15226762357 0016244 0 ustar 00 <?php /** * Customize API: WP_Customize_Nav_Menu_Locations_Control class * * @package WordPress * @subpackage Customize * @since 4.9.0 */ /** * Customize Nav Menu Locations Control Class. * * @since 4.9.0 * * @see WP_Customize_Control */ class WP_Customize_Nav_Menu_Locations_Control extends WP_Customize_Control { /** * Control type. * * @since 4.9.0 * @var string */ public $type = 'nav_menu_locations'; /** * Don't render the control's content - it uses a JS template instead. * * @since 4.9.0 */ public function render_content() {} /** * JS/Underscore template for the control UI. * * @since 4.9.0 */ public function content_template() { if ( current_theme_supports( 'menus' ) ) : ?> <# var elementId; #> <ul class="menu-location-settings"> <li class="customize-control assigned-menu-locations-title"> <span class="customize-control-title">{{ wp.customize.Menus.data.l10n.locationsTitle }}</span> <# if ( data.isCreating ) { #> <p> <?php echo _x( 'Where do you want this menu to appear?', 'menu locations' ); ?> <?php printf( /* translators: 1: Documentation URL, 2: Additional link attributes, 3: Accessibility text. */ _x( '(If you plan to use a menu <a href="%1$s" %2$s>widget%3$s</a>, skip this step.)', 'menu locations' ), __( 'https://wordpress.org/documentation/article/manage-wordpress-widgets/' ), ' class="external-link" target="_blank"', sprintf( '<span class="screen-reader-text"> %s</span>', /* translators: Hidden accessibility text. */ __( '(opens in a new tab)' ) ) ); ?> </p> <# } else { #> <p><?php echo _x( 'Here’s where this menu appears. If you would like to change that, pick another location.', 'menu locations' ); ?></p> <# } #> </li> <?php foreach ( get_registered_nav_menus() as $location => $description ) : ?> <# elementId = _.uniqueId( 'customize-nav-menu-control-location-' ); #> <li class="customize-control customize-control-checkbox assigned-menu-location"> <span class="customize-inside-control-row"> <input id="{{ elementId }}" type="checkbox" data-menu-id="{{ data.menu_id }}" data-location-id="<?php echo esc_attr( $location ); ?>" class="menu-location" /> <label for="{{ elementId }}"> <?php echo $description; ?> <span class="theme-location-set"> <?php printf( /* translators: %s: Menu name. */ _x( '(Current: %s)', 'menu location' ), '<span class="current-menu-location-name-' . esc_attr( $location ) . '"></span>' ); ?> </span> </label> </span> </li> <?php endforeach; ?> </ul> <?php endif; } } https-detection-20260606165331.php 0000666 00000013341 15226762357 0012006 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260605023619.php 0000666 00000013341 15226762357 0012007 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260605094635-20260621212923.php 0000666 00000013341 15226762357 0013400 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260605172835.php 0000666 00000011205 15226762357 0012024 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } embed.php 0000666 00000113777 15226762357 0006374 0 ustar 00 <?php /** * oEmbed API: Top-level oEmbed functionality * * @package WordPress * @subpackage oEmbed * @since 4.4.0 */ /** * Registers an embed handler. * * Should probably only be used for sites that do not support oEmbed. * * @since 2.9.0 * * @global WP_Embed $wp_embed WordPress Embed object. * * @param string $id An internal ID/name for the handler. Needs to be unique. * @param string $regex The regex that will be used to see if this handler should be used for a URL. * @param callable $callback The callback function that will be called if the regex is matched. * @param int $priority Optional. Used to specify the order in which the registered handlers will * be tested. Default 10. */ function wp_embed_register_handler( $id, $regex, $callback, $priority = 10 ) { global $wp_embed; $wp_embed->register_handler( $id, $regex, $callback, $priority ); } /** * Unregisters a previously-registered embed handler. * * @since 2.9.0 * * @global WP_Embed $wp_embed WordPress Embed object. * * @param string $id The handler ID that should be removed. * @param int $priority Optional. The priority of the handler to be removed. Default 10. */ function wp_embed_unregister_handler( $id, $priority = 10 ) { global $wp_embed; $wp_embed->unregister_handler( $id, $priority ); } /** * Creates default array of embed parameters. * * The width defaults to the content width as specified by the theme. If the * theme does not specify a content width, then 500px is used. * * The default height is 1.5 times the width, or 1000px, whichever is smaller. * * The {@see 'embed_defaults'} filter can be used to adjust either of these values. * * @since 2.9.0 * * @global int $content_width * * @param string $url Optional. The URL that should be embedded. Default empty. * @return int[] { * Indexed array of the embed width and height in pixels. * * @type int $0 The embed width. * @type int $1 The embed height. * } */ function wp_embed_defaults( $url = '' ) { if ( ! empty( $GLOBALS['content_width'] ) ) { $width = (int) $GLOBALS['content_width']; } if ( empty( $width ) ) { $width = 500; } $height = min( (int) ceil( $width * 1.5 ), 1000 ); /** * Filters the default array of embed dimensions. * * @since 2.9.0 * * @param int[] $size { * Indexed array of the embed width and height in pixels. * * @type int $0 The embed width. * @type int $1 The embed height. * } * @param string $url The URL that should be embedded. */ return apply_filters( 'embed_defaults', compact( 'width', 'height' ), $url ); } /** * Attempts to fetch the embed HTML for a provided URL using oEmbed. * * @since 2.9.0 * * @see WP_oEmbed * * @param string $url The URL that should be embedded. * @param array|string $args { * Optional. Additional arguments for retrieving embed HTML. Default empty. * * @type int|string $width Optional. The `maxwidth` value passed to the provider URL. * @type int|string $height Optional. The `maxheight` value passed to the provider URL. * @type bool $discover Optional. Determines whether to attempt to discover link tags * at the given URL for an oEmbed provider when the provider URL * is not found in the built-in providers list. Default true. * } * @return string|false The embed HTML on success, false on failure. */ function wp_oembed_get( $url, $args = '' ) { $oembed = _wp_oembed_get_object(); return $oembed->get_html( $url, $args ); } /** * Returns the initialized WP_oEmbed object. * * @since 2.9.0 * @access private * * @return WP_oEmbed object. */ function _wp_oembed_get_object() { static $wp_oembed = null; if ( is_null( $wp_oembed ) ) { $wp_oembed = new WP_oEmbed(); } return $wp_oembed; } /** * Adds a URL format and oEmbed provider URL pair. * * @since 2.9.0 * * @see WP_oEmbed * * @param string $format The format of URL that this provider can handle. You can use asterisks * as wildcards. * @param string $provider The URL to the oEmbed provider. * @param bool $regex Optional. Whether the `$format` parameter is in a RegEx format. Default false. */ function wp_oembed_add_provider( $format, $provider, $regex = false ) { if ( did_action( 'plugins_loaded' ) ) { $oembed = _wp_oembed_get_object(); $oembed->providers[ $format ] = array( $provider, $regex ); } else { WP_oEmbed::_add_provider_early( $format, $provider, $regex ); } } /** * Removes an oEmbed provider. * * @since 3.5.0 * * @see WP_oEmbed * * @param string $format The URL format for the oEmbed provider to remove. * @return bool Was the provider removed successfully? */ function wp_oembed_remove_provider( $format ) { if ( did_action( 'plugins_loaded' ) ) { $oembed = _wp_oembed_get_object(); if ( isset( $oembed->providers[ $format ] ) ) { unset( $oembed->providers[ $format ] ); return true; } } else { WP_oEmbed::_remove_provider_early( $format ); } return false; } /** * Determines if default embed handlers should be loaded. * * Checks to make sure that the embeds library hasn't already been loaded. If * it hasn't, then it will load the embeds library. * * @since 2.9.0 * * @see wp_embed_register_handler() */ function wp_maybe_load_embeds() { /** * Filters whether to load the default embed handlers. * * Returning a falsey value will prevent loading the default embed handlers. * * @since 2.9.0 * * @param bool $maybe_load_embeds Whether to load the embeds library. Default true. */ if ( ! apply_filters( 'load_default_embeds', true ) ) { return; } wp_embed_register_handler( 'youtube_embed_url', '#https?://(www\.)?youtube\.com/(?:v|embed)/([^/]+)#i', 'wp_embed_handler_youtube' ); /** * Filters the audio embed handler callback. * * @since 3.6.0 * * @param callable $handler Audio embed handler callback function. */ wp_embed_register_handler( 'audio', '#^https?://.+?\.(' . implode( '|', wp_get_audio_extensions() ) . ')$#i', apply_filters( 'wp_audio_embed_handler', 'wp_embed_handler_audio' ), 9999 ); /** * Filters the video embed handler callback. * * @since 3.6.0 * * @param callable $handler Video embed handler callback function. */ wp_embed_register_handler( 'video', '#^https?://.+?\.(' . implode( '|', wp_get_video_extensions() ) . ')$#i', apply_filters( 'wp_video_embed_handler', 'wp_embed_handler_video' ), 9999 ); } /** * YouTube iframe embed handler callback. * * Catches YouTube iframe embed URLs that are not parsable by oEmbed but can be translated into a URL that is. * * @since 4.0.0 * * @global WP_Embed $wp_embed WordPress Embed object. * * @param array $matches The RegEx matches from the provided regex when calling * wp_embed_register_handler(). * @param array $attr Embed attributes. * @param string $url The original URL that was matched by the regex. * @param array $rawattr The original unmodified attributes. * @return string The embed HTML. */ function wp_embed_handler_youtube( $matches, $attr, $url, $rawattr ) { global $wp_embed; $embed = $wp_embed->autoembed( sprintf( 'https://youtube.com/watch?v=%s', urlencode( $matches[2] ) ) ); /** * Filters the YouTube embed output. * * @since 4.0.0 * * @see wp_embed_handler_youtube() * * @param string $embed YouTube embed output. * @param array $attr An array of embed attributes. * @param string $url The original URL that was matched by the regex. * @param array $rawattr The original unmodified attributes. */ return apply_filters( 'wp_embed_handler_youtube', $embed, $attr, $url, $rawattr ); } /** * Audio embed handler callback. * * @since 3.6.0 * * @param array $matches The RegEx matches from the provided regex when calling wp_embed_register_handler(). * @param array $attr Embed attributes. * @param string $url The original URL that was matched by the regex. * @param array $rawattr The original unmodified attributes. * @return string The embed HTML. */ function wp_embed_handler_audio( $matches, $attr, $url, $rawattr ) { $audio = sprintf( '[audio src="%s" /]', esc_url( $url ) ); /** * Filters the audio embed output. * * @since 3.6.0 * * @param string $audio Audio embed output. * @param array $attr An array of embed attributes. * @param string $url The original URL that was matched by the regex. * @param array $rawattr The original unmodified attributes. */ return apply_filters( 'wp_embed_handler_audio', $audio, $attr, $url, $rawattr ); } /** * Video embed handler callback. * * @since 3.6.0 * * @param array $matches The RegEx matches from the provided regex when calling wp_embed_register_handler(). * @param array $attr Embed attributes. * @param string $url The original URL that was matched by the regex. * @param array $rawattr The original unmodified attributes. * @return string The embed HTML. */ function wp_embed_handler_video( $matches, $attr, $url, $rawattr ) { $dimensions = ''; if ( ! empty( $rawattr['width'] ) && ! empty( $rawattr['height'] ) ) { $dimensions .= sprintf( 'width="%d" ', (int) $rawattr['width'] ); $dimensions .= sprintf( 'height="%d" ', (int) $rawattr['height'] ); } $video = sprintf( '[video %s src="%s" /]', $dimensions, esc_url( $url ) ); /** * Filters the video embed output. * * @since 3.6.0 * * @param string $video Video embed output. * @param array $attr An array of embed attributes. * @param string $url The original URL that was matched by the regex. * @param array $rawattr The original unmodified attributes. */ return apply_filters( 'wp_embed_handler_video', $video, $attr, $url, $rawattr ); } /** * Registers the oEmbed REST API route. * * @since 4.4.0 */ function wp_oembed_register_route() { $controller = new WP_oEmbed_Controller(); $controller->register_routes(); } /** * Adds oEmbed discovery links in the head element of the website. * * @since 4.4.0 * @since 6.8.0 Output was adjusted to only embed if the post supports it. * @since 6.9.0 Now runs first at `wp_head` priority 4, with a fallback to priority 10. This helps ensure the discovery links appear within the first 150KB. */ function wp_oembed_add_discovery_links() { if ( doing_action( 'wp_head' ) ) { // For back-compat, short-circuit if a plugin has removed the action at the original priority. if ( ! has_action( 'wp_head', 'wp_oembed_add_discovery_links', 10 ) ) { return; } // Prevent running again at the original priority. remove_action( 'wp_head', 'wp_oembed_add_discovery_links' ); } $output = ''; if ( is_singular() && is_post_embeddable() ) { $output .= '<link rel="alternate" title="' . _x( 'oEmbed (JSON)', 'oEmbed resource link name' ) . '" type="application/json+oembed" href="' . esc_url( get_oembed_endpoint_url( get_permalink() ) ) . '" />' . "\n"; if ( class_exists( 'SimpleXMLElement' ) ) { $output .= '<link rel="alternate" title="' . _x( 'oEmbed (XML)', 'oEmbed resource link name' ) . '" type="text/xml+oembed" href="' . esc_url( get_oembed_endpoint_url( get_permalink(), 'xml' ) ) . '" />' . "\n"; } } /** * Filters the oEmbed discovery links HTML. * * @since 4.4.0 * * @param string $output HTML of the discovery links. */ echo apply_filters( 'oembed_discovery_links', $output ); } /** * Adds the necessary JavaScript to communicate with the embedded iframes. * * This function is no longer used directly. For back-compat it exists exclusively as a way to indicate that the oEmbed * host JS _should_ be added. In `default-filters.php` there remains this code: * * add_action( 'wp_head', 'wp_oembed_add_host_js' ) * * Historically a site has been able to disable adding the oEmbed host script by doing: * * remove_action( 'wp_head', 'wp_oembed_add_host_js' ) * * In order to ensure that such code still works as expected, this function remains. There is now a `has_action()` check * in `wp_maybe_enqueue_oembed_host_js()` to see if `wp_oembed_add_host_js()` has not been unhooked from running at the * `wp_head` action. * * @since 4.4.0 * @deprecated 5.9.0 Use {@see wp_maybe_enqueue_oembed_host_js()} instead. */ function wp_oembed_add_host_js() {} /** * Enqueue the wp-embed script if the provided oEmbed HTML contains a post embed. * * In order to only enqueue the wp-embed script on pages that actually contain post embeds, this function checks if the * provided HTML contains post embed markup and if so enqueues the script so that it will get printed in the footer. * * @since 5.9.0 * * @param string $html Embed markup. * @return string Embed markup (without modifications). */ function wp_maybe_enqueue_oembed_host_js( $html ) { if ( has_action( 'wp_head', 'wp_oembed_add_host_js' ) && preg_match( '/<blockquote\s[^>]*?wp-embedded-content/', $html ) ) { wp_enqueue_script( 'wp-embed' ); } return $html; } /** * Retrieves the URL to embed a specific post in an iframe. * * @since 4.4.0 * * @param int|WP_Post $post Optional. Post ID or object. Defaults to the current post. * @return string|false The post embed URL on success, false if the post doesn't exist. */ function get_post_embed_url( $post = null ) { $post = get_post( $post ); if ( ! $post ) { return false; } $embed_url = trailingslashit( get_permalink( $post ) ) . user_trailingslashit( 'embed' ); $path_conflict = get_page_by_path( str_replace( home_url(), '', $embed_url ), OBJECT, get_post_types( array( 'public' => true ) ) ); if ( ! get_option( 'permalink_structure' ) || $path_conflict ) { $embed_url = add_query_arg( array( 'embed' => 'true' ), get_permalink( $post ) ); } /** * Filters the URL to embed a specific post. * * @since 4.4.0 * * @param string $embed_url The post embed URL. * @param WP_Post $post The corresponding post object. */ return sanitize_url( apply_filters( 'post_embed_url', $embed_url, $post ) ); } /** * Retrieves the oEmbed endpoint URL for a given permalink. * * Pass an empty string as the first argument to get the endpoint base URL. * * @since 4.4.0 * * @param string $permalink Optional. The permalink used for the `url` query arg. Default empty. * @param string $format Optional. The requested response format. Default 'json'. * @return string The oEmbed endpoint URL. */ function get_oembed_endpoint_url( $permalink = '', $format = 'json' ) { $url = rest_url( 'oembed/1.0/embed' ); if ( '' !== $permalink ) { $url = add_query_arg( array( 'url' => urlencode( $permalink ), 'format' => ( 'json' !== $format ) ? $format : false, ), $url ); } /** * Filters the oEmbed endpoint URL. * * @since 4.4.0 * * @param string $url The URL to the oEmbed endpoint. * @param string $permalink The permalink used for the `url` query arg. * @param string $format The requested response format. */ return apply_filters( 'oembed_endpoint_url', $url, $permalink, $format ); } /** * Retrieves the embed code for a specific post. * * @since 4.4.0 * * @param int $width The width for the response. * @param int $height The height for the response. * @param int|WP_Post $post Optional. Post ID or object. Default is global `$post`. * @return string|false Embed code on success, false if post doesn't exist. */ function get_post_embed_html( $width, $height, $post = null ) { $post = get_post( $post ); if ( ! $post ) { return false; } $embed_url = get_post_embed_url( $post ); $secret = wp_generate_password( 10, false ); $embed_url .= "#?secret={$secret}"; $output = sprintf( '<blockquote class="wp-embedded-content" data-secret="%1$s"><a href="%2$s">%3$s</a></blockquote>', esc_attr( $secret ), esc_url( get_permalink( $post ) ), get_the_title( $post ) ); $output .= sprintf( '<iframe sandbox="allow-scripts" security="restricted" src="%1$s" width="%2$d" height="%3$d" title="%4$s" data-secret="%5$s" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" class="wp-embedded-content"></iframe>', esc_url( $embed_url ), absint( $width ), absint( $height ), esc_attr( sprintf( /* translators: 1: Post title, 2: Site title. */ __( '“%1$s” — %2$s' ), get_the_title( $post ), get_bloginfo( 'name' ) ) ), esc_attr( $secret ) ); /* * Note that the script must be placed after the <blockquote> and <iframe> due to a regexp parsing issue in * `wp_filter_oembed_result()`. Because of the regex pattern starts with `|(<blockquote>.*?</blockquote>)?.*|` * wherein the <blockquote> is marked as being optional, if it is not at the beginning of the string then the group * will fail to match and everything will be matched by `.*` and not included in the group. This regex issue goes * back to WordPress 4.4, so in order to not break older installs this script must come at the end. */ $js_path = '/js/wp-embed' . wp_scripts_get_suffix() . '.js'; $output .= wp_get_inline_script_tag( trim( file_get_contents( ABSPATH . WPINC . $js_path ) ) . "\n//# sourceURL=" . esc_url_raw( includes_url( $js_path ) ) ); /** * Filters the embed HTML output for a given post. * * @since 4.4.0 * * @param string $output The default iframe tag to display embedded content. * @param WP_Post $post Current post object. * @param int $width Width of the response. * @param int $height Height of the response. */ return apply_filters( 'embed_html', $output, $post, $width, $height ); } /** * Retrieves the oEmbed response data for a given post. * * @since 4.4.0 * @since 6.8.0 Output was adjusted to only embed if the post type supports it. * * @param WP_Post|int $post Post ID or post object. * @param int $width The requested width. * @return array|false Response data on success, false if post doesn't exist, * is not publicly viewable or post type is not embeddable. */ function get_oembed_response_data( $post, $width ) { $post = get_post( $post ); $width = absint( $width ); if ( ! $post ) { return false; } if ( ! is_post_publicly_viewable( $post ) ) { return false; } if ( ! is_post_embeddable( $post ) ) { return false; } /** * Filters the allowed minimum and maximum widths for the oEmbed response. * * @since 4.4.0 * * @param array $min_max_width { * Minimum and maximum widths for the oEmbed response. * * @type int $min Minimum width. Default 200. * @type int $max Maximum width. Default 600. * } */ $min_max_width = apply_filters( 'oembed_min_max_width', array( 'min' => 200, 'max' => 600, ) ); $width = min( max( $min_max_width['min'], $width ), $min_max_width['max'] ); $height = max( (int) ceil( $width / 16 * 9 ), 200 ); $data = array( 'version' => '1.0', 'provider_name' => get_bloginfo( 'name' ), 'provider_url' => get_home_url(), 'author_name' => get_bloginfo( 'name' ), 'author_url' => get_home_url(), 'title' => get_the_title( $post ), 'type' => 'link', ); $author = get_userdata( $post->post_author ); if ( $author ) { $data['author_name'] = $author->display_name; $data['author_url'] = get_author_posts_url( $author->ID ); } /** * Filters the oEmbed response data. * * @since 4.4.0 * * @param array $data The response data. * @param WP_Post $post The post object. * @param int $width The requested width. * @param int $height The calculated height. */ return apply_filters( 'oembed_response_data', $data, $post, $width, $height ); } /** * Retrieves the oEmbed response data for a given URL. * * @since 5.0.0 * * @param string $url The URL that should be inspected for discovery `<link>` tags. * @param array $args oEmbed remote get arguments. * @return object|false oEmbed response data if the URL does belong to the current site. False otherwise. */ function get_oembed_response_data_for_url( $url, $args ) { $switched_blog = false; if ( is_multisite() ) { $url_parts = wp_parse_args( wp_parse_url( $url ), array( 'host' => '', 'port' => null, 'path' => '/', ) ); $qv = array( 'domain' => $url_parts['host'] . ( $url_parts['port'] ? ':' . $url_parts['port'] : '' ), 'path' => '/', 'update_site_meta_cache' => false, ); // In case of subdirectory configs, set the path. if ( ! is_subdomain_install() ) { $path = explode( '/', ltrim( $url_parts['path'], '/' ) ); $path = reset( $path ); if ( $path ) { $qv['path'] = get_network()->path . $path . '/'; } } $sites = get_sites( $qv ); $site = reset( $sites ); // Do not allow embeds for deleted/archived/spam sites. if ( ! empty( $site->deleted ) || ! empty( $site->spam ) || ! empty( $site->archived ) ) { return false; } if ( $site && get_current_blog_id() !== (int) $site->blog_id ) { switch_to_blog( $site->blog_id ); $switched_blog = true; } } $post_id = url_to_postid( $url ); /** This filter is documented in wp-includes/class-wp-oembed-controller.php */ $post_id = apply_filters( 'oembed_request_post_id', $post_id, $url ); if ( ! $post_id ) { if ( $switched_blog ) { restore_current_blog(); } return false; } $width = isset( $args['width'] ) ? $args['width'] : 0; $data = get_oembed_response_data( $post_id, $width ); if ( $switched_blog ) { restore_current_blog(); } return $data ? (object) $data : false; } /** * Filters the oEmbed response data to return an iframe embed code. * * @since 4.4.0 * * @param array $data The response data. * @param WP_Post $post The post object. * @param int $width The requested width. * @param int $height The calculated height. * @return array The modified response data. */ function get_oembed_response_data_rich( $data, $post, $width, $height ) { $data['width'] = absint( $width ); $data['height'] = absint( $height ); $data['type'] = 'rich'; $data['html'] = get_post_embed_html( $width, $height, $post ); // Add post thumbnail to response if available. $thumbnail_id = false; if ( has_post_thumbnail( $post->ID ) ) { $thumbnail_id = get_post_thumbnail_id( $post->ID ); } if ( 'attachment' === get_post_type( $post ) ) { if ( wp_attachment_is_image( $post ) ) { $thumbnail_id = $post->ID; } elseif ( wp_attachment_is( 'video', $post ) ) { $thumbnail_id = get_post_thumbnail_id( $post ); $data['type'] = 'video'; } } if ( $thumbnail_id ) { list( $thumbnail_url, $thumbnail_width, $thumbnail_height ) = wp_get_attachment_image_src( $thumbnail_id, array( $width, 0 ) ); $data['thumbnail_url'] = $thumbnail_url; $data['thumbnail_width'] = $thumbnail_width; $data['thumbnail_height'] = $thumbnail_height; } return $data; } /** * Ensures that the specified format is either 'json' or 'xml'. * * @since 4.4.0 * * @param string $format The oEmbed response format. Accepts 'json' or 'xml'. * @return string The format, either 'xml' or 'json'. Default 'json'. */ function wp_oembed_ensure_format( $format ) { if ( ! in_array( $format, array( 'json', 'xml' ), true ) ) { return 'json'; } return $format; } /** * Hooks into the REST API output to print XML instead of JSON. * * This is only done for the oEmbed API endpoint, * which supports both formats. * * @access private * @since 4.4.0 * * @param bool $served Whether the request has already been served. * @param WP_HTTP_Response $result Result to send to the client. Usually a `WP_REST_Response`. * @param WP_REST_Request $request Request used to generate the response. * @param WP_REST_Server $server Server instance. * @return bool True if the request was served, false otherwise. */ function _oembed_rest_pre_serve_request( $served, $result, $request, $server ) { $params = $request->get_params(); if ( '/oembed/1.0/embed' !== $request->get_route() || 'GET' !== $request->get_method() ) { return $served; } if ( ! isset( $params['format'] ) || 'xml' !== $params['format'] ) { return $served; } // Embed links inside the request. $data = $server->response_to_data( $result, false ); if ( ! class_exists( 'SimpleXMLElement' ) ) { status_header( 501 ); die( get_status_header_desc( 501 ) ); } $result = _oembed_create_xml( $data ); // Bail if there's no XML. if ( ! $result ) { status_header( 501 ); die( get_status_header_desc( 501 ) ); } if ( ! headers_sent() ) { $server->send_header( 'Content-Type', 'text/xml; charset=' . get_option( 'blog_charset' ) ); } echo $result; return true; } /** * Creates an XML string from a given array. * * @since 4.4.0 * @access private * * @param array $data The original oEmbed response data. * @param SimpleXMLElement $node Optional. XML node to append the result to recursively. * @return string|false XML string on success, false on error. */ function _oembed_create_xml( $data, $node = null ) { if ( ! is_array( $data ) || empty( $data ) ) { return false; } if ( null === $node ) { $node = new SimpleXMLElement( '<oembed></oembed>' ); } foreach ( $data as $key => $value ) { if ( is_numeric( $key ) ) { $key = 'oembed'; } if ( is_array( $value ) ) { $item = $node->addChild( $key ); _oembed_create_xml( $value, $item ); } else { $node->addChild( $key, esc_html( $value ) ); } } return $node->asXML(); } /** * Filters the given oEmbed HTML to make sure iframes have a title attribute. * * @since 5.2.0 * * @param string|false $result The oEmbed HTML result. * @param object $data A data object result from an oEmbed provider. * @param string $url The URL of the content to be embedded. * @return string|false The filtered oEmbed result. */ function wp_filter_oembed_iframe_title_attribute( $result, $data, $url ) { if ( false === $result || ! in_array( $data->type, array( 'rich', 'video' ), true ) ) { return $result; } $title = ! empty( $data->title ) ? $data->title : ''; $pattern = '`<iframe([^>]*)>`i'; if ( preg_match( $pattern, $result, $matches ) ) { $attrs = wp_kses_hair( $matches[1], wp_allowed_protocols() ); foreach ( $attrs as $attr => $item ) { $lower_attr = strtolower( $attr ); if ( $lower_attr === $attr ) { continue; } if ( ! isset( $attrs[ $lower_attr ] ) ) { $attrs[ $lower_attr ] = $item; unset( $attrs[ $attr ] ); } } } if ( ! empty( $attrs['title']['value'] ) ) { $title = $attrs['title']['value']; } /** * Filters the title attribute of the given oEmbed HTML iframe. * * @since 5.2.0 * * @param string $title The title attribute. * @param string $result The oEmbed HTML result. * @param object $data A data object result from an oEmbed provider. * @param string $url The URL of the content to be embedded. */ $title = apply_filters( 'oembed_iframe_title_attribute', $title, $result, $data, $url ); if ( '' === $title ) { return $result; } if ( isset( $attrs['title'] ) ) { unset( $attrs['title'] ); $attr_string = implode( ' ', wp_list_pluck( $attrs, 'whole' ) ); $result = str_replace( $matches[0], '<iframe ' . trim( $attr_string ) . '>', $result ); } return str_ireplace( '<iframe ', sprintf( '<iframe title="%s" ', esc_attr( $title ) ), $result ); } /** * Filters the given oEmbed HTML. * * If the `$url` isn't on the trusted providers list, * we need to filter the HTML heavily for security. * * Only filters 'rich' and 'video' response types. * * @since 4.4.0 * * @param string|false $result The oEmbed HTML result. * @param object $data A data object result from an oEmbed provider. * @param string $url The URL of the content to be embedded. * @return string|false The filtered and sanitized oEmbed result. */ function wp_filter_oembed_result( $result, $data, $url ) { if ( false === $result || ! in_array( $data->type, array( 'rich', 'video' ), true ) ) { return $result; } $wp_oembed = _wp_oembed_get_object(); // Don't modify the HTML for trusted providers. if ( false !== $wp_oembed->get_provider( $url, array( 'discover' => false ) ) ) { return $result; } $allowed_html = array( 'a' => array( 'href' => true, ), 'blockquote' => array(), 'iframe' => array( 'src' => true, 'width' => true, 'height' => true, 'frameborder' => true, 'marginwidth' => true, 'marginheight' => true, 'scrolling' => true, 'title' => true, ), ); $html = wp_kses( $result, $allowed_html ); preg_match( '|(<blockquote>.*?</blockquote>)?.*(<iframe.*?></iframe>)|ms', $html, $content ); // We require at least the iframe to exist. if ( empty( $content[2] ) ) { return false; } $html = $content[1] . $content[2]; preg_match( '/ src=([\'"])(.*?)\1/', $html, $results ); if ( ! empty( $results ) ) { $secret = wp_generate_password( 10, false ); $url = esc_url( "{$results[2]}#?secret=$secret" ); $q = $results[1]; $html = str_replace( $results[0], ' src=' . $q . $url . $q . ' data-secret=' . $q . $secret . $q, $html ); $html = str_replace( '<blockquote', "<blockquote data-secret=\"$secret\"", $html ); } $allowed_html['blockquote']['data-secret'] = true; $allowed_html['iframe']['data-secret'] = true; $html = wp_kses( $html, $allowed_html ); if ( ! empty( $content[1] ) ) { // We have a blockquote to fall back on. Hide the iframe by default. $html = str_replace( '<iframe', '<iframe style="position: absolute; visibility: hidden;"', $html ); $html = str_replace( '<blockquote', '<blockquote class="wp-embedded-content"', $html ); } $html = str_ireplace( '<iframe', '<iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"', $html ); return $html; } /** * Filters the string in the 'more' link displayed after a trimmed excerpt. * * Replaces '[...]' (appended to automatically generated excerpts) with an * ellipsis and a "Continue reading" link in the embed template. * * @since 4.4.0 * * @param string $more_string Default 'more' string. * @return string 'Continue reading' link prepended with an ellipsis. */ function wp_embed_excerpt_more( $more_string ) { if ( ! is_embed() ) { return $more_string; } $link = sprintf( '<a href="%1$s" class="wp-embed-more" target="_top">%2$s</a>', esc_url( get_permalink() ), /* translators: %s: Post title. */ sprintf( __( 'Continue reading %s' ), '<span class="screen-reader-text">' . get_the_title() . '</span>' ) ); return ' … ' . $link; } /** * Displays the post excerpt for the embed template. * * Intended to be used in 'The Loop'. * * @since 4.4.0 */ function the_excerpt_embed() { $output = get_the_excerpt(); /** * Filters the post excerpt for the embed template. * * @since 4.4.0 * * @param string $output The current post excerpt. */ echo apply_filters( 'the_excerpt_embed', $output ); } /** * Filters the post excerpt for the embed template. * * Shows players for video and audio attachments. * * @since 4.4.0 * * @param string $content The current post excerpt. * @return string The modified post excerpt. */ function wp_embed_excerpt_attachment( $content ) { if ( is_attachment() ) { return prepend_attachment( '' ); } return $content; } /** * Enqueues embed iframe default CSS and JS. * * Enqueue PNG fallback CSS for embed iframe for legacy versions of IE. * * Allows plugins to queue scripts for the embed iframe end using wp_enqueue_script(). * Runs first in oembed_head(). * * @since 4.4.0 */ function enqueue_embed_scripts() { /** * Fires when scripts and styles are enqueued for the embed iframe. * * @since 4.4.0 */ do_action( 'enqueue_embed_scripts' ); } /** * Enqueues the CSS in the embed iframe header. * * @since 6.4.0 */ function wp_enqueue_embed_styles() { // Back-compat for plugins that disable functionality by unhooking this action. if ( ! has_action( 'embed_head', 'print_embed_styles' ) ) { return; } remove_action( 'embed_head', 'print_embed_styles' ); $suffix = wp_scripts_get_suffix(); $handle = 'wp-embed-template'; wp_register_style( $handle, false ); wp_add_inline_style( $handle, file_get_contents( ABSPATH . WPINC . "/css/wp-embed-template$suffix.css" ) ); wp_enqueue_style( $handle ); } /** * Prints the JavaScript in the embed iframe header. * * @since 4.4.0 */ function print_embed_scripts() { $js_path = '/js/wp-embed-template' . wp_scripts_get_suffix() . '.js'; wp_print_inline_script_tag( trim( file_get_contents( ABSPATH . WPINC . $js_path ) ) . "\n//# sourceURL=" . esc_url_raw( includes_url( $js_path ) ) ); } /** * Prepare the oembed HTML to be displayed in an RSS feed. * * @since 4.4.0 * @access private * * @param string $content The content to filter. * @return string The filtered content. */ function _oembed_filter_feed_content( $content ) { $p = new WP_HTML_Tag_Processor( $content ); while ( $p->next_tag( array( 'tag_name' => 'iframe' ) ) ) { if ( $p->has_class( 'wp-embedded-content' ) ) { $p->remove_attribute( 'style' ); } } return $p->get_updated_html(); } /** * Prints the necessary markup for the embed comments button. * * @since 4.4.0 */ function print_embed_comments_button() { if ( is_404() || ! ( get_comments_number() || comments_open() ) ) { return; } ?> <div class="wp-embed-comments"> <a href="<?php comments_link(); ?>" target="_top"> <span class="dashicons dashicons-admin-comments"></span> <?php printf( /* translators: %s: Number of comments. */ _n( '%s <span class="screen-reader-text">Comment</span>', '%s <span class="screen-reader-text">Comments</span>', get_comments_number() ), number_format_i18n( get_comments_number() ) ); ?> </a> </div> <?php } /** * Prints the necessary markup for the embed sharing button. * * @since 4.4.0 */ function print_embed_sharing_button() { if ( is_404() ) { return; } ?> <div class="wp-embed-share"> <button type="button" class="wp-embed-share-dialog-open" aria-label="<?php esc_attr_e( 'Open sharing dialog' ); ?>"> <span class="dashicons dashicons-share"></span> </button> </div> <?php } /** * Prints the necessary markup for the embed sharing dialog. * * @since 4.4.0 */ function print_embed_sharing_dialog() { if ( is_404() ) { return; } $unique_suffix = get_the_ID() . '-' . wp_rand(); $share_tab_wordpress_id = 'wp-embed-share-tab-wordpress-' . $unique_suffix; $share_tab_html_id = 'wp-embed-share-tab-html-' . $unique_suffix; $description_wordpress_id = 'wp-embed-share-description-wordpress-' . $unique_suffix; $description_html_id = 'wp-embed-share-description-html-' . $unique_suffix; ?> <div class="wp-embed-share-dialog hidden" role="dialog" aria-label="<?php esc_attr_e( 'Sharing options' ); ?>"> <div class="wp-embed-share-dialog-content"> <div class="wp-embed-share-dialog-text"> <ul class="wp-embed-share-tabs" role="tablist"> <li class="wp-embed-share-tab-button wp-embed-share-tab-button-wordpress" role="presentation"> <button type="button" role="tab" aria-controls="<?php echo $share_tab_wordpress_id; ?>" aria-selected="true" tabindex="0"><?php esc_html_e( 'WordPress Embed' ); ?></button> </li> <li class="wp-embed-share-tab-button wp-embed-share-tab-button-html" role="presentation"> <button type="button" role="tab" aria-controls="<?php echo $share_tab_html_id; ?>" aria-selected="false" tabindex="-1"><?php esc_html_e( 'HTML Embed' ); ?></button> </li> </ul> <div id="<?php echo $share_tab_wordpress_id; ?>" class="wp-embed-share-tab" role="tabpanel" aria-hidden="false"> <input type="text" value="<?php the_permalink(); ?>" class="wp-embed-share-input" aria-label="<?php esc_attr_e( 'URL' ); ?>" aria-describedby="<?php echo $description_wordpress_id; ?>" tabindex="0" readonly/> <p class="wp-embed-share-description" id="<?php echo $description_wordpress_id; ?>"> <?php _e( 'Copy and paste this URL into your WordPress site to embed' ); ?> </p> </div> <div id="<?php echo $share_tab_html_id; ?>" class="wp-embed-share-tab" role="tabpanel" aria-hidden="true"> <textarea class="wp-embed-share-input" aria-label="<?php esc_attr_e( 'HTML' ); ?>" aria-describedby="<?php echo $description_html_id; ?>" tabindex="0" readonly><?php echo esc_textarea( get_post_embed_html( 600, 400 ) ); ?></textarea> <p class="wp-embed-share-description" id="<?php echo $description_html_id; ?>"> <?php _e( 'Copy and paste this code into your site to embed' ); ?> </p> </div> </div> <button type="button" class="wp-embed-share-dialog-close" aria-label="<?php esc_attr_e( 'Close sharing dialog' ); ?>"> <span class="dashicons dashicons-no"></span> </button> </div> </div> <?php } /** * Prints the necessary markup for the site title in an embed template. * * @since 4.5.0 */ function the_embed_site_title() { $site_title = sprintf( '<a href="%s" target="_top"><img src="%s" srcset="%s 2x" width="32" height="32" alt="" class="wp-embed-site-icon" /><span>%s</span></a>', esc_url( home_url() ), esc_url( get_site_icon_url( 32, includes_url( 'images/w-logo-blue.png' ) ) ), esc_url( get_site_icon_url( 64, includes_url( 'images/w-logo-blue.png' ) ) ), esc_html( get_bloginfo( 'name' ) ) ); $site_title = '<div class="wp-embed-site-title">' . $site_title . '</div>'; /** * Filters the site title HTML in the embed footer. * * @since 4.4.0 * * @param string $site_title The site title HTML. */ echo apply_filters( 'embed_site_title_html', $site_title ); } /** * Filters the oEmbed result before any HTTP requests are made. * * If the URL belongs to the current site, the result is fetched directly instead of * going through the oEmbed discovery process. * * @since 4.5.3 * * @param null|string $result The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. Default null. * @param string $url The URL that should be inspected for discovery `<link>` tags. * @param array $args oEmbed remote get arguments. * @return null|string The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. * Null if the URL does not belong to the current site. */ function wp_filter_pre_oembed_result( $result, $url, $args ) { $data = get_oembed_response_data_for_url( $url, $args ); if ( $data ) { return _wp_oembed_get_object()->data2html( $data, $url ); } return $result; } https-migration-20260622024947.php 0000666 00000011205 15226762357 0012023 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } user.php 0000666 00000533616 15226762357 0006274 0 ustar 00 <?php /** * Core User API * * @package WordPress * @subpackage Users */ /** * Authenticates and logs a user in with 'remember' capability. * * The credentials is an array that has 'user_login', 'user_password', and * 'remember' indices. If the credentials is not given, then the log in form * will be assumed and used if set. * * The various authentication cookies will be set by this function and will be * set for a longer period depending on if the 'remember' credential is set to * true. * * Note: wp_signon() doesn't handle setting the current user. This means that if the * function is called before the {@see 'init'} hook is fired, is_user_logged_in() will * evaluate as false until that point. If is_user_logged_in() is needed in conjunction * with wp_signon(), wp_set_current_user() should be called explicitly. * * @since 2.5.0 * * @global string $auth_secure_cookie * @global wpdb $wpdb WordPress database abstraction object. * * @param array $credentials { * Optional. User info in order to sign on. * * @type string $user_login Username. * @type string $user_password User password. * @type bool $remember Whether to 'remember' the user. Increases the time * that the cookie will be kept. Default false. * } * @param string|bool $secure_cookie Optional. Whether to use secure cookie. * @return WP_User|WP_Error WP_User on success, WP_Error on failure. */ function wp_signon( $credentials = array(), $secure_cookie = '' ) { global $auth_secure_cookie, $wpdb; if ( empty( $credentials ) ) { $credentials = array( 'user_login' => '', 'user_password' => '', 'remember' => false, ); if ( ! empty( $_POST['log'] ) && is_string( $_POST['log'] ) ) { $credentials['user_login'] = wp_unslash( $_POST['log'] ); } if ( ! empty( $_POST['pwd'] ) && is_string( $_POST['pwd'] ) ) { $credentials['user_password'] = $_POST['pwd']; } if ( ! empty( $_POST['rememberme'] ) ) { $credentials['remember'] = $_POST['rememberme']; } } if ( ! empty( $credentials['remember'] ) ) { $credentials['remember'] = true; } else { $credentials['remember'] = false; } /** * Fires before the user is authenticated. * * The variables passed to the callbacks are passed by reference, * and can be modified by callback functions. * * @since 1.5.1 * * @todo Decide whether to deprecate the wp_authenticate action. * * @param string $user_login Username (passed by reference). * @param string $user_password User password (passed by reference). */ do_action_ref_array( 'wp_authenticate', array( &$credentials['user_login'], &$credentials['user_password'] ) ); if ( '' === $secure_cookie ) { $secure_cookie = is_ssl(); } /** * Filters whether to use a secure sign-on cookie. * * @since 3.1.0 * * @param bool $secure_cookie Whether to use a secure sign-on cookie. * @param array $credentials { * Array of entered sign-on data. * * @type string $user_login Username. * @type string $user_password Password entered. * @type bool $remember Whether to 'remember' the user. Increases the time * that the cookie will be kept. Default false. * } */ $secure_cookie = apply_filters( 'secure_signon_cookie', $secure_cookie, $credentials ); // XXX ugly hack to pass this to wp_authenticate_cookie(). $auth_secure_cookie = $secure_cookie; add_filter( 'authenticate', 'wp_authenticate_cookie', 30, 3 ); $user = wp_authenticate( $credentials['user_login'], $credentials['user_password'] ); if ( is_wp_error( $user ) ) { return $user; } wp_set_auth_cookie( $user->ID, $credentials['remember'], $secure_cookie ); // Clear `user_activation_key` after a successful login. if ( ! empty( $user->user_activation_key ) ) { $wpdb->update( $wpdb->users, array( 'user_activation_key' => '', ), array( 'ID' => $user->ID ) ); $user->user_activation_key = ''; } /** * Fires after the user has successfully logged in. * * @since 1.5.0 * * @param string $user_login Username. * @param WP_User $user WP_User object of the logged-in user. */ do_action( 'wp_login', $user->user_login, $user ); return $user; } /** * Authenticates a user, confirming the username and password are valid. * * @since 2.8.0 * * @param WP_User|WP_Error|null $user WP_User or WP_Error object from a previous callback. Default null. * @param string $username Username for authentication. * @param string $password Password for authentication. * @return WP_User|WP_Error WP_User on success, WP_Error on failure. */ function wp_authenticate_username_password( $user, $username, #[\SensitiveParameter] $password ) { if ( $user instanceof WP_User ) { return $user; } if ( empty( $username ) || empty( $password ) ) { if ( is_wp_error( $user ) ) { return $user; } $error = new WP_Error(); if ( empty( $username ) ) { $error->add( 'empty_username', __( '<strong>Error:</strong> The username field is empty.' ) ); } if ( empty( $password ) ) { $error->add( 'empty_password', __( '<strong>Error:</strong> The password field is empty.' ) ); } return $error; } $user = get_user_by( 'login', $username ); if ( ! $user ) { return new WP_Error( 'invalid_username', sprintf( /* translators: %s: User name. */ __( '<strong>Error:</strong> The username <strong>%s</strong> is not registered on this site. If you are unsure of your username, try your email address instead.' ), $username ) ); } /** * Filters whether the given user can be authenticated with the provided password. * * @since 2.5.0 * * @param WP_User|WP_Error $user WP_User or WP_Error object if a previous * callback failed authentication. * @param string $password Password to check against the user. */ $user = apply_filters( 'wp_authenticate_user', $user, $password ); if ( is_wp_error( $user ) ) { return $user; } $valid = wp_check_password( $password, $user->user_pass, $user->ID ); if ( ! $valid ) { return new WP_Error( 'incorrect_password', sprintf( /* translators: %s: User name. */ __( '<strong>Error:</strong> The password you entered for the username %s is incorrect.' ), '<strong>' . $username . '</strong>' ) . ' <a href="' . wp_lostpassword_url() . '">' . __( 'Lost your password?' ) . '</a>' ); } if ( wp_password_needs_rehash( $user->user_pass, $user->ID ) ) { wp_set_password( $password, $user->ID ); } return $user; } /** * Authenticates a user using the email and password. * * @since 4.5.0 * * @param WP_User|WP_Error|null $user WP_User or WP_Error object if a previous * callback failed authentication. * @param string $email Email address for authentication. * @param string $password Password for authentication. * @return WP_User|WP_Error WP_User on success, WP_Error on failure. */ function wp_authenticate_email_password( $user, $email, #[\SensitiveParameter] $password ) { if ( $user instanceof WP_User ) { return $user; } if ( empty( $email ) || empty( $password ) ) { if ( is_wp_error( $user ) ) { return $user; } $error = new WP_Error(); if ( empty( $email ) ) { // Uses 'empty_username' for back-compat with wp_signon(). $error->add( 'empty_username', __( '<strong>Error:</strong> The email field is empty.' ) ); } if ( empty( $password ) ) { $error->add( 'empty_password', __( '<strong>Error:</strong> The password field is empty.' ) ); } return $error; } if ( ! is_email( $email ) ) { return $user; } $user = get_user_by( 'email', $email ); if ( ! $user ) { return new WP_Error( 'invalid_email', __( 'Unknown email address. Check again or try your username.' ) ); } /** This filter is documented in wp-includes/user.php */ $user = apply_filters( 'wp_authenticate_user', $user, $password ); if ( is_wp_error( $user ) ) { return $user; } $valid = wp_check_password( $password, $user->user_pass, $user->ID ); if ( ! $valid ) { return new WP_Error( 'incorrect_password', sprintf( /* translators: %s: Email address. */ __( '<strong>Error:</strong> The password you entered for the email address %s is incorrect.' ), '<strong>' . $email . '</strong>' ) . ' <a href="' . wp_lostpassword_url() . '">' . __( 'Lost your password?' ) . '</a>' ); } if ( wp_password_needs_rehash( $user->user_pass, $user->ID ) ) { wp_set_password( $password, $user->ID ); } return $user; } /** * Authenticates the user using the WordPress auth cookie. * * @since 2.8.0 * * @global string $auth_secure_cookie * * @param WP_User|WP_Error|null $user WP_User or WP_Error object from a previous callback. Default null. * @param string $username Username. If not empty, cancels the cookie authentication. * @param string $password Password. If not empty, cancels the cookie authentication. * @return WP_User|WP_Error WP_User on success, WP_Error on failure. */ function wp_authenticate_cookie( $user, $username, #[\SensitiveParameter] $password ) { global $auth_secure_cookie; if ( $user instanceof WP_User ) { return $user; } if ( empty( $username ) && empty( $password ) ) { $user_id = wp_validate_auth_cookie(); if ( $user_id ) { return new WP_User( $user_id ); } if ( $auth_secure_cookie ) { $auth_cookie = SECURE_AUTH_COOKIE; } else { $auth_cookie = AUTH_COOKIE; } if ( ! empty( $_COOKIE[ $auth_cookie ] ) ) { return new WP_Error( 'expired_session', __( 'Please log in again.' ) ); } // If the cookie is not set, be silent. } return $user; } /** * Authenticates the user using an application password. * * @since 5.6.0 * * @param WP_User|WP_Error|null $input_user WP_User or WP_Error object if a previous * callback failed authentication. * @param string $username Username for authentication. * @param string $password Password for authentication. * @return WP_User|WP_Error|null WP_User on success, WP_Error on failure, null if * null is passed in and this isn't an API request. */ function wp_authenticate_application_password( $input_user, $username, #[\SensitiveParameter] $password ) { if ( $input_user instanceof WP_User ) { return $input_user; } if ( ! WP_Application_Passwords::is_in_use() ) { return $input_user; } // The 'REST_REQUEST' check here may happen too early for the constant to be available. $is_api_request = ( ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ); /** * Filters whether this is an API request that Application Passwords can be used on. * * By default, Application Passwords is available for the REST API and XML-RPC. * * @since 5.6.0 * * @param bool $is_api_request If this is an acceptable API request. */ $is_api_request = apply_filters( 'application_password_is_api_request', $is_api_request ); if ( ! $is_api_request ) { return $input_user; } $error = null; $user = get_user_by( 'login', $username ); if ( ! $user && is_email( $username ) ) { $user = get_user_by( 'email', $username ); } // If the login name is invalid, short circuit. if ( ! $user ) { if ( is_email( $username ) ) { $error = new WP_Error( 'invalid_email', __( '<strong>Error:</strong> Unknown email address. Check again or try your username.' ) ); } else { $error = new WP_Error( 'invalid_username', __( '<strong>Error:</strong> Unknown username. Check again or try your email address.' ) ); } } elseif ( ! wp_is_application_passwords_available() ) { $error = new WP_Error( 'application_passwords_disabled', __( 'Application passwords are not available.' ) ); } elseif ( ! wp_is_application_passwords_available_for_user( $user ) ) { $error = new WP_Error( 'application_passwords_disabled_for_user', __( 'Application passwords are not available for your account. Please contact the site administrator for assistance.' ) ); } if ( $error ) { /** * Fires when an application password failed to authenticate the user. * * @since 5.6.0 * * @param WP_Error $error The authentication error. */ do_action( 'application_password_failed_authentication', $error ); return $error; } /* * Strips out anything non-alphanumeric. This is so passwords can be used with * or without spaces to indicate the groupings for readability. * * Generated application passwords are exclusively alphanumeric. */ $password = preg_replace( '/[^a-z\d]/i', '', $password ); $hashed_passwords = WP_Application_Passwords::get_user_application_passwords( $user->ID ); foreach ( $hashed_passwords as $key => $item ) { if ( ! WP_Application_Passwords::check_password( $password, $item['password'] ) ) { continue; } $error = new WP_Error(); /** * Fires when an application password has been successfully checked as valid. * * This allows for plugins to add additional constraints to prevent an application password from being used. * * @since 5.6.0 * * @param WP_Error $error The error object. * @param WP_User $user The user authenticating. * @param array $item The details about the application password. * @param string $password The raw supplied password. */ do_action( 'wp_authenticate_application_password_errors', $error, $user, $item, $password ); if ( $error->has_errors() ) { /** This action is documented in wp-includes/user.php */ do_action( 'application_password_failed_authentication', $error ); return $error; } WP_Application_Passwords::record_application_password_usage( $user->ID, $item['uuid'] ); /** * Fires after an application password was used for authentication. * * @since 5.6.0 * * @param WP_User $user The user who was authenticated. * @param array $item The application password used. */ do_action( 'application_password_did_authenticate', $user, $item ); return $user; } $error = new WP_Error( 'incorrect_password', __( 'The provided password is an invalid application password.' ) ); /** This action is documented in wp-includes/user.php */ do_action( 'application_password_failed_authentication', $error ); return $error; } /** * Validates the application password credentials passed via Basic Authentication. * * @since 5.6.0 * * @param int|false $input_user User ID if one has been determined, false otherwise. * @return int|false The authenticated user ID if successful, false otherwise. */ function wp_validate_application_password( $input_user ) { // Don't authenticate twice. if ( ! empty( $input_user ) ) { return $input_user; } if ( ! wp_is_application_passwords_available() ) { return $input_user; } // Both $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'] must be set in order to attempt authentication. if ( ! isset( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) ) { return $input_user; } $authenticated = wp_authenticate_application_password( null, $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ); if ( $authenticated instanceof WP_User ) { return $authenticated->ID; } // If it wasn't a user what got returned, just pass on what we had received originally. return $input_user; } /** * For Multisite blogs, checks if the authenticated user has been marked as a * spammer, or if the user's primary blog has been marked as spam. * * @since 3.7.0 * * @param WP_User|WP_Error|null $user WP_User or WP_Error object from a previous callback. Default null. * @return WP_User|WP_Error WP_User on success, WP_Error if the user is considered a spammer. */ function wp_authenticate_spam_check( $user ) { if ( $user instanceof WP_User && is_multisite() ) { /** * Filters whether the user has been marked as a spammer. * * @since 3.7.0 * * @param bool $spammed Whether the user is considered a spammer. * @param WP_User $user User to check against. */ $spammed = apply_filters( 'check_is_user_spammed', is_user_spammy( $user ), $user ); if ( $spammed ) { return new WP_Error( 'spammer_account', __( '<strong>Error:</strong> Your account has been marked as a spammer.' ) ); } } return $user; } /** * Validates the logged-in cookie. * * Checks the logged-in cookie if the previous auth cookie could not be * validated and parsed. * * This is a callback for the {@see 'determine_current_user'} filter, rather than API. * * @since 3.9.0 * * @param int|false $user_id The user ID (or false) as received from * the `determine_current_user` filter. * @return int|false User ID if validated, false otherwise. If a user ID from * an earlier filter callback is received, that value is returned. */ function wp_validate_logged_in_cookie( $user_id ) { if ( $user_id ) { return $user_id; } if ( is_blog_admin() || is_network_admin() || empty( $_COOKIE[ LOGGED_IN_COOKIE ] ) ) { return false; } return wp_validate_auth_cookie( $_COOKIE[ LOGGED_IN_COOKIE ], 'logged_in' ); } /** * Gets the number of posts a user has written. * * @since 3.0.0 * @since 4.1.0 Added `$post_type` argument. * @since 4.3.0 Added `$public_only` argument. Added the ability to pass an array * of post types to `$post_type`. * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $userid User ID. * @param array|string $post_type Optional. Single post type or array of post types to count the number of posts for. Default 'post'. * @param bool $public_only Optional. Whether to only return counts for public posts. Default false. * @return string Number of posts the user has written in this post type. */ function count_user_posts( $userid, $post_type = 'post', $public_only = false ) { global $wpdb; $post_type = array_unique( (array) $post_type ); sort( $post_type ); $where = get_posts_by_author_sql( $post_type, true, $userid, $public_only ); $query = "SELECT COUNT(*) FROM $wpdb->posts $where"; $last_changed = wp_cache_get_last_changed( 'posts' ); $cache_key = 'count_user_posts:' . md5( $query ); $count = wp_cache_get_salted( $cache_key, 'post-queries', $last_changed ); if ( false === $count ) { $count = $wpdb->get_var( $query ); wp_cache_set_salted( $cache_key, $count, 'post-queries', $last_changed ); } /** * Filters the number of posts a user has written. * * @since 2.7.0 * @since 4.1.0 Added `$post_type` argument. * @since 4.3.1 Added `$public_only` argument. * * @param string $count The user's post count as a numeric string. * @param int $userid User ID. * @param string|array $post_type Single post type or array of post types to count the number of posts for. * @param bool $public_only Whether to limit counted posts to public posts. */ return apply_filters( 'get_usernumposts', $count, $userid, $post_type, $public_only ); } /** * Gets the number of posts written by a list of users. * * @since 3.0.0 * @since 6.9.0 The results are now cached. * * @global wpdb $wpdb WordPress database abstraction object. * * @param int[] $users Array of user IDs. * @param string|string[] $post_type Optional. Single post type or array of post types to check. Defaults to 'post'. * @param bool $public_only Optional. Only return counts for public posts. Defaults to false. * @return array<int, string> Amount of posts each user has written, as strings, keyed by user ID. */ function count_many_users_posts( $users, $post_type = 'post', $public_only = false ) { global $wpdb; if ( empty( $users ) || ! is_array( $users ) ) { return array(); } /** * Filters whether to short-circuit performing the post counts. * * When filtering, return an array of posts counts as strings, keyed * by the user ID. * * @since 6.8.0 * * @param string[]|null $count The post counts. Return a non-null value to short-circuit. * @param int[] $users Array of user IDs. * @param string|string[] $post_type Single post type or array of post types to check. * @param bool $public_only Whether to only return counts for public posts. */ $pre = apply_filters( 'pre_count_many_users_posts', null, $users, $post_type, $public_only ); if ( null !== $pre ) { return $pre; } // Cleanup the users array. Remove duplicates and sort for consistent ordering. $users = array_unique( array_filter( array_map( 'intval', $users ) ) ); sort( $users ); // Cleanup the post type argument. Remove duplicates and sort for consistent ordering. $post_type = array_unique( (array) $post_type ); sort( $post_type ); $userlist = implode( ',', $users ); $where = get_posts_by_author_sql( $post_type, true, null, $public_only ); $query = "SELECT post_author, COUNT(*) FROM $wpdb->posts $where AND post_author IN ($userlist) GROUP BY post_author"; $cache_key = 'count_many_users_posts:' . md5( $query ); $cache_salts = array( wp_cache_get_last_changed( 'posts' ), wp_cache_get_last_changed( 'users' ) ); $count = wp_cache_get_salted( $cache_key, 'post-queries', $cache_salts ); if ( false === $count ) { $result = $wpdb->get_results( $query, ARRAY_N ); $count = array_fill_keys( $users, 0 ); foreach ( $result as $row ) { $count[ $row[0] ] = $row[1]; } wp_cache_set_salted( $cache_key, $count, 'post-queries', $cache_salts, HOUR_IN_SECONDS ); } return $count; } // // User option functions. // /** * Gets the current user's ID. * * @since MU (3.0.0) * * @return int The current user's ID, or 0 if no user is logged in. */ function get_current_user_id() { if ( ! function_exists( 'wp_get_current_user' ) ) { return 0; } $user = wp_get_current_user(); return ( isset( $user->ID ) ? (int) $user->ID : 0 ); } /** * Retrieves user option that can be either per Site or per Network. * * If the user ID is not given, then the current user will be used instead. If * the user ID is given, then the user data will be retrieved. The filter for * the result, will also pass the original option name and finally the user data * object as the third parameter. * * The option will first check for the per site name and then the per Network name. * * @since 2.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $option User option name. * @param int $user Optional. User ID. * @param string $deprecated Use get_option() to check for an option in the options table. * @return mixed User option value on success, false on failure. */ function get_user_option( $option, $user = 0, $deprecated = '' ) { global $wpdb; if ( ! empty( $deprecated ) ) { _deprecated_argument( __FUNCTION__, '3.0.0' ); } if ( empty( $user ) ) { $user = get_current_user_id(); } $user = get_userdata( $user ); if ( ! $user ) { return false; } $prefix = $wpdb->get_blog_prefix(); if ( $user->has_prop( $prefix . $option ) ) { // Blog-specific. $result = $user->get( $prefix . $option ); } elseif ( $user->has_prop( $option ) ) { // User-specific and cross-blog. $result = $user->get( $option ); } else { $result = false; } /** * Filters a specific user option value. * * The dynamic portion of the hook name, `$option`, refers to the user option name. * * @since 2.5.0 * * @param mixed $result Value for the user's option. * @param string $option Name of the option being retrieved. * @param WP_User $user WP_User object of the user whose option is being retrieved. */ return apply_filters( "get_user_option_{$option}", $result, $option, $user ); } /** * Updates user option with global blog capability. * * User options are just like user metadata except that they have support for * global blog options. If the 'is_global' parameter is false, which it is by default, * it will prepend the WordPress table prefix to the option name. * * Deletes the user option if $newvalue is empty. * * @since 2.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $user_id User ID. * @param string $option_name User option name. * @param mixed $newvalue User option value. * @param bool $is_global Optional. Whether option name is global or blog specific. * Default false (blog specific). * @return int|bool User meta ID if the option didn't exist, true on successful update, * false on failure. */ function update_user_option( $user_id, $option_name, $newvalue, $is_global = false ) { global $wpdb; if ( ! $is_global ) { $option_name = $wpdb->get_blog_prefix() . $option_name; } return update_user_meta( $user_id, $option_name, $newvalue ); } /** * Deletes user option with global blog capability. * * User options are just like user metadata except that they have support for * global blog options. If the 'is_global' parameter is false, which it is by default, * it will prepend the WordPress table prefix to the option name. * * @since 3.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $user_id User ID * @param string $option_name User option name. * @param bool $is_global Optional. Whether option name is global or blog specific. * Default false (blog specific). * @return bool True on success, false on failure. */ function delete_user_option( $user_id, $option_name, $is_global = false ) { global $wpdb; if ( ! $is_global ) { $option_name = $wpdb->get_blog_prefix() . $option_name; } return delete_user_meta( $user_id, $option_name ); } /** * Retrieves user info by user ID. * * @since 6.7.0 * * @param int $user_id User ID. * * @return WP_User|false WP_User object on success, false on failure. */ function get_user( $user_id ) { return get_user_by( 'id', $user_id ); } /** * Retrieves list of users matching criteria. * * @since 3.1.0 * * @see WP_User_Query * * @param array $args Optional. Arguments to retrieve users. See WP_User_Query::prepare_query() * for more information on accepted arguments. * @return array List of users. */ function get_users( $args = array() ) { $args = wp_parse_args( $args ); $args['count_total'] = false; $user_search = new WP_User_Query( $args ); return (array) $user_search->get_results(); } /** * Lists all the users of the site, with several options available. * * @since 5.9.0 * * @param string|array $args { * Optional. Array or string of default arguments. * * @type string $orderby How to sort the users. Accepts 'nicename', 'email', 'url', 'registered', * 'user_nicename', 'user_email', 'user_url', 'user_registered', 'name', * 'display_name', 'post_count', 'ID', 'meta_value', 'user_login'. Default 'name'. * @type string $order Sorting direction for $orderby. Accepts 'ASC', 'DESC'. Default 'ASC'. * @type int $number Maximum users to return or display. Default empty (all users). * @type bool $exclude_admin Whether to exclude the 'admin' account, if it exists. Default false. * @type bool $show_fullname Whether to show the user's full name. Default false. * @type string $feed If not empty, show a link to the user's feed and use this text as the alt * parameter of the link. Default empty. * @type string $feed_image If not empty, show a link to the user's feed and use this image URL as * clickable anchor. Default empty. * @type string $feed_type The feed type to link to, such as 'rss2'. Defaults to default feed type. * @type bool $echo Whether to output the result or instead return it. Default true. * @type string $style If 'list', each user is wrapped in an `<li>` element, otherwise the users * will be separated by commas. * @type bool $html Whether to list the items in HTML form or plaintext. Default true. * @type string $exclude An array, comma-, or space-separated list of user IDs to exclude. Default empty. * @type string $include An array, comma-, or space-separated list of user IDs to include. Default empty. * } * @return string|null The output if echo is false. Otherwise null. */ function wp_list_users( $args = array() ) { $defaults = array( 'orderby' => 'name', 'order' => 'ASC', 'number' => '', 'exclude_admin' => true, 'show_fullname' => false, 'feed' => '', 'feed_image' => '', 'feed_type' => '', 'echo' => true, 'style' => 'list', 'html' => true, 'exclude' => '', 'include' => '', ); $parsed_args = wp_parse_args( $args, $defaults ); $return = ''; $query_args = wp_array_slice_assoc( $parsed_args, array( 'orderby', 'order', 'number', 'exclude', 'include' ) ); $query_args['fields'] = 'ids'; /** * Filters the query arguments for the list of all users of the site. * * @since 6.1.0 * * @param array $query_args The query arguments for get_users(). * @param array $parsed_args The arguments passed to wp_list_users() combined with the defaults. */ $query_args = apply_filters( 'wp_list_users_args', $query_args, $parsed_args ); $users = get_users( $query_args ); foreach ( $users as $user_id ) { $user = get_userdata( $user_id ); if ( $parsed_args['exclude_admin'] && 'admin' === $user->display_name ) { continue; } if ( $parsed_args['show_fullname'] && '' !== $user->first_name && '' !== $user->last_name ) { $name = sprintf( /* translators: 1: User's first name, 2: Last name. */ _x( '%1$s %2$s', 'Display name based on first name and last name' ), $user->first_name, $user->last_name ); } else { $name = $user->display_name; } if ( ! $parsed_args['html'] ) { $return .= $name . ', '; continue; // No need to go further to process HTML. } if ( 'list' === $parsed_args['style'] ) { $return .= '<li>'; } $row = $name; if ( ! empty( $parsed_args['feed_image'] ) || ! empty( $parsed_args['feed'] ) ) { $row .= ' '; if ( empty( $parsed_args['feed_image'] ) ) { $row .= '('; } $row .= '<a href="' . get_author_feed_link( $user->ID, $parsed_args['feed_type'] ) . '"'; $alt = ''; if ( ! empty( $parsed_args['feed'] ) ) { $alt = ' alt="' . esc_attr( $parsed_args['feed'] ) . '"'; $name = $parsed_args['feed']; } $row .= '>'; if ( ! empty( $parsed_args['feed_image'] ) ) { $row .= '<img src="' . esc_url( $parsed_args['feed_image'] ) . '" style="border: none;"' . $alt . ' />'; } else { $row .= $name; } $row .= '</a>'; if ( empty( $parsed_args['feed_image'] ) ) { $row .= ')'; } } $return .= $row; $return .= ( 'list' === $parsed_args['style'] ) ? '</li>' : ', '; } $return = rtrim( $return, ', ' ); if ( ! $parsed_args['echo'] ) { return $return; } echo $return; } /** * Gets the sites a user belongs to. * * @since 3.0.0 * @since 4.7.0 Converted to use `get_sites()`. * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $user_id User ID * @param bool $all Whether to retrieve all sites, or only sites that are not * marked as deleted, archived, or spam. * @return object[] A list of the user's sites. An empty array if the user doesn't exist * or belongs to no sites. */ function get_blogs_of_user( $user_id, $all = false ) { global $wpdb; $user_id = (int) $user_id; // Logged out users can't have sites. if ( empty( $user_id ) ) { return array(); } /** * Filters the list of a user's sites before it is populated. * * Returning a non-null value from the filter will effectively short circuit * get_blogs_of_user(), returning that value instead. * * @since 4.6.0 * * @param null|object[] $sites An array of site objects of which the user is a member. * @param int $user_id User ID. * @param bool $all Whether the returned array should contain all sites, including * those marked 'deleted', 'archived', or 'spam'. Default false. */ $sites = apply_filters( 'pre_get_blogs_of_user', null, $user_id, $all ); if ( null !== $sites ) { return $sites; } $keys = get_user_meta( $user_id ); if ( empty( $keys ) ) { return array(); } if ( ! is_multisite() ) { $site_id = get_current_blog_id(); $sites = array( $site_id => new stdClass() ); $sites[ $site_id ]->userblog_id = $site_id; $sites[ $site_id ]->blogname = get_option( 'blogname' ); $sites[ $site_id ]->domain = ''; $sites[ $site_id ]->path = ''; $sites[ $site_id ]->site_id = 1; $sites[ $site_id ]->siteurl = get_option( 'siteurl' ); $sites[ $site_id ]->archived = 0; $sites[ $site_id ]->spam = 0; $sites[ $site_id ]->deleted = 0; return $sites; } $site_ids = array(); if ( isset( $keys[ $wpdb->base_prefix . 'capabilities' ] ) && defined( 'MULTISITE' ) ) { $site_ids[] = 1; unset( $keys[ $wpdb->base_prefix . 'capabilities' ] ); } $keys = array_keys( $keys ); foreach ( $keys as $key ) { if ( ! str_ends_with( $key, 'capabilities' ) ) { continue; } if ( $wpdb->base_prefix && ! str_starts_with( $key, $wpdb->base_prefix ) ) { continue; } $site_id = str_replace( array( $wpdb->base_prefix, '_capabilities' ), '', $key ); if ( ! is_numeric( $site_id ) ) { continue; } $site_ids[] = (int) $site_id; } $sites = array(); if ( ! empty( $site_ids ) ) { $args = array( 'number' => '', 'site__in' => $site_ids, ); if ( ! $all ) { $args['archived'] = 0; $args['spam'] = 0; $args['deleted'] = 0; } $_sites = get_sites( $args ); foreach ( $_sites as $site ) { $sites[ $site->id ] = (object) array( 'userblog_id' => $site->id, 'blogname' => $site->blogname, 'domain' => $site->domain, 'path' => $site->path, 'site_id' => $site->network_id, 'siteurl' => $site->siteurl, 'archived' => $site->archived, 'mature' => $site->mature, 'spam' => $site->spam, 'deleted' => $site->deleted, ); } } /** * Filters the list of sites a user belongs to. * * @since MU (3.0.0) * * @param object[] $sites An array of site objects belonging to the user. * @param int $user_id User ID. * @param bool $all Whether the returned sites array should contain all sites, including * those flagged for deletion, archived, or marked as spam. */ return apply_filters( 'get_blogs_of_user', $sites, $user_id, $all ); } /** * Finds out whether a user is a member of a given blog. * * @since MU (3.0.0) * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $user_id Optional. The unique ID of the user. Defaults to the current user. * @param int $blog_id Optional. ID of the blog to check. Defaults to the current site. * @return bool */ function is_user_member_of_blog( $user_id = 0, $blog_id = 0 ) { global $wpdb; $user_id = (int) $user_id; $blog_id = (int) $blog_id; if ( empty( $user_id ) ) { $user_id = get_current_user_id(); } /* * Technically not needed, but does save calls to get_site() and get_user_meta() * in the event that the function is called when a user isn't logged in. */ if ( empty( $user_id ) ) { return false; } else { $user = get_userdata( $user_id ); if ( ! $user instanceof WP_User ) { return false; } } if ( ! is_multisite() ) { return true; } if ( empty( $blog_id ) ) { $blog_id = get_current_blog_id(); } $blog = get_site( $blog_id ); if ( ! $blog || ! isset( $blog->domain ) || $blog->archived || $blog->spam || $blog->deleted ) { return false; } if ( 1 === $blog_id ) { $capabilities_key = $wpdb->base_prefix . 'capabilities'; } else { $capabilities_key = $wpdb->base_prefix . $blog_id . '_capabilities'; } $has_cap = get_user_meta( $user_id, $capabilities_key, true ); return is_array( $has_cap ); } /** * Adds meta data to a user. * * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 3.0.0 * * @param int $user_id User ID. * @param string $meta_key Metadata name. * @param mixed $meta_value Metadata value. Arrays and objects are stored as serialized data and * will be returned as the same type when retrieved. Other data types will * be stored as strings in the database: * - false is stored and retrieved as an empty string ('') * - true is stored and retrieved as '1' * - numbers (both integer and float) are stored and retrieved as strings * Must be serializable if non-scalar. * @param bool $unique Optional. Whether the same key should not be added. * Default false. * @return int|false Meta ID on success, false on failure. */ function add_user_meta( $user_id, $meta_key, $meta_value, $unique = false ) { return add_metadata( 'user', $user_id, $meta_key, $meta_value, $unique ); } /** * Removes metadata matching criteria from a user. * * You can match based on the key, or key and value. Removing based on key and * value, will keep from removing duplicate metadata with the same key. It also * allows removing all metadata matching key, if needed. * * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 3.0.0 * * @link https://developer.wordpress.org/reference/functions/delete_user_meta/ * * @param int $user_id User ID * @param string $meta_key Metadata name. * @param mixed $meta_value Optional. Metadata value. If provided, * rows will only be removed that match the value. * Must be serializable if non-scalar. Default empty. * @return bool True on success, false on failure. */ function delete_user_meta( $user_id, $meta_key, $meta_value = '' ) { return delete_metadata( 'user', $user_id, $meta_key, $meta_value ); } /** * Retrieves user meta field for a user. * * @since 3.0.0 * * @link https://developer.wordpress.org/reference/functions/get_user_meta/ * * @param int $user_id User ID. * @param string $key Optional. The meta key to retrieve. By default, * returns data for all keys. * @param bool $single Optional. Whether to return a single value. * This parameter has no effect if `$key` is not specified. * Default false. * @return mixed An array of values if `$single` is false. * The value of meta data field if `$single` is true. * False for an invalid `$user_id` (non-numeric, zero, or negative value). * An empty array if a valid but non-existing user ID is passed and `$single` is false. * An empty string if a valid but non-existing user ID is passed and `$single` is true. * Note: Non-serialized values are returned as strings: * - false values are returned as empty strings ('') * - true values are returned as '1' * - numbers (both integer and float) are returned as strings * Arrays and objects retain their original type. */ function get_user_meta( $user_id, $key = '', $single = false ) { return get_metadata( 'user', $user_id, $key, $single ); } /** * Updates user meta field based on user ID. * * Use the $prev_value parameter to differentiate between meta fields with the * same key and user ID. * * If the meta field for the user does not exist, it will be added. * * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 3.0.0 * * @link https://developer.wordpress.org/reference/functions/update_user_meta/ * * @param int $user_id User ID. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param mixed $prev_value Optional. Previous value to check before updating. * If specified, only update existing metadata entries with * this value. Otherwise, update all entries. Default empty. * @return int|bool Meta ID if the key didn't exist, true on successful update, * false on failure or if the value passed to the function * is the same as the one that is already in the database. */ function update_user_meta( $user_id, $meta_key, $meta_value, $prev_value = '' ) { return update_metadata( 'user', $user_id, $meta_key, $meta_value, $prev_value ); } /** * Counts number of users who have each of the user roles. * * Assumes there are neither duplicated nor orphaned capabilities meta_values. * Assumes role names are unique phrases. Same assumption made by WP_User_Query::prepare_query() * Using $strategy = 'time' this is CPU-intensive and should handle around 10^7 users. * Using $strategy = 'memory' this is memory-intensive and should handle around 10^5 users, but see WP Bug #12257. * * @since 3.0.0 * @since 4.4.0 The number of users with no role is now included in the `none` element. * @since 4.9.0 The `$site_id` parameter was added to support multisite. * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $strategy Optional. The computational strategy to use when counting the users. * Accepts either 'time' or 'memory'. Default 'time'. * @param int|null $site_id Optional. The site ID to count users for. Defaults to the current site. * @return array { * User counts. * * @type int $total_users Total number of users on the site. * @type int[] $avail_roles Array of user counts keyed by user role. * } */ function count_users( $strategy = 'time', $site_id = null ) { global $wpdb; // Initialize. if ( ! $site_id ) { $site_id = get_current_blog_id(); } /** * Filters the user count before queries are run. * * Return a non-null value to cause count_users() to return early. * * @since 5.1.0 * * @param null|array $result The value to return instead. Default null to continue with the query. * @param string $strategy Optional. The computational strategy to use when counting the users. * Accepts either 'time' or 'memory'. Default 'time'. * @param int $site_id The site ID to count users for. */ $pre = apply_filters( 'pre_count_users', null, $strategy, $site_id ); if ( null !== $pre ) { return $pre; } $blog_prefix = $wpdb->get_blog_prefix( $site_id ); $result = array(); if ( 'time' === $strategy ) { if ( is_multisite() && get_current_blog_id() !== $site_id ) { switch_to_blog( $site_id ); $avail_roles = wp_roles()->get_names(); restore_current_blog(); } else { $avail_roles = wp_roles()->get_names(); } // Build a CPU-intensive query that will return concise information. $select_count = array(); foreach ( $avail_roles as $this_role => $name ) { $select_count[] = $wpdb->prepare( 'COUNT(NULLIF(`meta_value` LIKE %s, false))', '%' . $wpdb->esc_like( '"' . $this_role . '"' ) . '%' ); } $select_count[] = "COUNT(NULLIF(`meta_value` = 'a:0:{}', false))"; $select_count = implode( ', ', $select_count ); // Add the meta_value index to the selection list, then run the query. $row = $wpdb->get_row( " SELECT {$select_count}, COUNT(*) FROM {$wpdb->usermeta} INNER JOIN {$wpdb->users} ON user_id = ID WHERE meta_key = '{$blog_prefix}capabilities' ", ARRAY_N ); // Run the previous loop again to associate results with role names. $col = 0; $role_counts = array(); foreach ( $avail_roles as $this_role => $name ) { $count = (int) $row[ $col++ ]; if ( $count > 0 ) { $role_counts[ $this_role ] = $count; } } $role_counts['none'] = (int) $row[ $col++ ]; // Get the meta_value index from the end of the result set. $total_users = (int) $row[ $col ]; $result['total_users'] = $total_users; $result['avail_roles'] =& $role_counts; } else { $avail_roles = array( 'none' => 0, ); $users_of_blog = $wpdb->get_col( " SELECT meta_value FROM {$wpdb->usermeta} INNER JOIN {$wpdb->users} ON user_id = ID WHERE meta_key = '{$blog_prefix}capabilities' " ); foreach ( $users_of_blog as $caps_meta ) { $b_roles = maybe_unserialize( $caps_meta ); if ( ! is_array( $b_roles ) ) { continue; } if ( empty( $b_roles ) ) { ++$avail_roles['none']; } foreach ( $b_roles as $b_role => $val ) { if ( isset( $avail_roles[ $b_role ] ) ) { ++$avail_roles[ $b_role ]; } else { $avail_roles[ $b_role ] = 1; } } } $result['total_users'] = count( $users_of_blog ); $result['avail_roles'] =& $avail_roles; } return $result; } /** * Returns the number of active users in your installation. * * Note that on a large site the count may be cached and only updated twice daily. * * @since MU (3.0.0) * @since 4.8.0 The `$network_id` parameter has been added. * @since 6.0.0 Moved to wp-includes/user.php. * * @param int|null $network_id ID of the network. Defaults to the current network. * @return int Number of active users on the network. */ function get_user_count( $network_id = null ) { if ( ! is_multisite() && null !== $network_id ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: %s: $network_id */ __( 'Unable to pass %s if not using multisite.' ), '<code>$network_id</code>' ), '6.0.0' ); } return (int) get_network_option( $network_id, 'user_count', -1 ); } /** * Updates the total count of users on the site if live user counting is enabled. * * @since 6.0.0 * * @param int|null $network_id ID of the network. Defaults to the current network. * @return bool Whether the update was successful. */ function wp_maybe_update_user_counts( $network_id = null ) { if ( ! is_multisite() && null !== $network_id ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: %s: $network_id */ __( 'Unable to pass %s if not using multisite.' ), '<code>$network_id</code>' ), '6.0.0' ); } $is_small_network = ! wp_is_large_user_count( $network_id ); /** This filter is documented in wp-includes/ms-functions.php */ if ( ! apply_filters( 'enable_live_network_counts', $is_small_network, 'users' ) ) { return false; } return wp_update_user_counts( $network_id ); } /** * Updates the total count of users on the site. * * @global wpdb $wpdb WordPress database abstraction object. * @since 6.0.0 * * @param int|null $network_id ID of the network. Defaults to the current network. * @return bool Whether the update was successful. */ function wp_update_user_counts( $network_id = null ) { global $wpdb; if ( ! is_multisite() && null !== $network_id ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: %s: $network_id */ __( 'Unable to pass %s if not using multisite.' ), '<code>$network_id</code>' ), '6.0.0' ); } $query = "SELECT COUNT(ID) as c FROM $wpdb->users"; if ( is_multisite() ) { $query .= " WHERE spam = '0' AND deleted = '0'"; } $count = $wpdb->get_var( $query ); return update_network_option( $network_id, 'user_count', $count ); } /** * Schedules a recurring recalculation of the total count of users. * * @since 6.0.0 */ function wp_schedule_update_user_counts() { if ( ! is_main_site() ) { return; } if ( ! wp_next_scheduled( 'wp_update_user_counts' ) && ! wp_installing() ) { wp_schedule_event( time(), 'twicedaily', 'wp_update_user_counts' ); } } /** * Determines whether the site has a large number of users. * * The default criteria for a large site is more than 10,000 users. * * @since 6.0.0 * * @param int|null $network_id ID of the network. Defaults to the current network. * @return bool Whether the site has a large number of users. */ function wp_is_large_user_count( $network_id = null ) { if ( ! is_multisite() && null !== $network_id ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: %s: $network_id */ __( 'Unable to pass %s if not using multisite.' ), '<code>$network_id</code>' ), '6.0.0' ); } $count = get_user_count( $network_id ); /** * Filters whether the site is considered large, based on its number of users. * * @since 6.0.0 * * @param bool $is_large_user_count Whether the site has a large number of users. * @param int $count The total number of users. * @param int|null $network_id ID of the network. `null` represents the current network. */ return apply_filters( 'wp_is_large_user_count', $count > 10000, $count, $network_id ); } // // Private helper functions. // /** * Sets up global user vars. * * Used by wp_set_current_user() for back compat. Might be deprecated in the future. * * @since 2.0.4 * * @global string $user_login The user username for logging in * @global WP_User $userdata User data. * @global int $user_level The level of the user * @global int $user_ID The ID of the user * @global string $user_email The email address of the user * @global string $user_url The url in the user's profile * @global string $user_identity The display name of the user * * @param int $for_user_id Optional. User ID to set up global data. Default 0. */ function setup_userdata( $for_user_id = 0 ) { global $user_login, $userdata, $user_level, $user_ID, $user_email, $user_url, $user_identity; if ( ! $for_user_id ) { $for_user_id = get_current_user_id(); } $user = get_userdata( $for_user_id ); if ( ! $user ) { $user_ID = 0; $user_level = 0; $userdata = null; $user_login = ''; $user_email = ''; $user_url = ''; $user_identity = ''; return; } $user_ID = (int) $user->ID; $user_level = (int) $user->user_level; $userdata = $user; $user_login = $user->user_login; $user_email = $user->user_email; $user_url = $user->user_url; $user_identity = $user->display_name; } /** * Creates dropdown HTML content of users. * * The content can either be displayed, which it is by default, or retrieved by * setting the 'echo' argument to false. The 'include' and 'exclude' arguments * are optional; if they are not specified, all users will be displayed. Only one * can be used in a single call, either 'include' or 'exclude', but not both. * * @since 2.3.0 * @since 4.5.0 Added the 'display_name_with_login' value for 'show'. * @since 4.7.0 Added the 'role', 'role__in', and 'role__not_in' parameters. * @since 5.9.0 Added the 'capability', 'capability__in', and 'capability__not_in' parameters. * Deprecated the 'who' parameter. * * @param array|string $args { * Optional. Array or string of arguments to generate a drop-down of users. * See WP_User_Query::prepare_query() for additional available arguments. * * @type string $show_option_all Text to show as the drop-down default (all). * Default empty. * @type string $show_option_none Text to show as the drop-down default when no * users were found. Default empty. * @type int|string $option_none_value Value to use for `$show_option_none` when no users * were found. Default -1. * @type string $hide_if_only_one_author Whether to skip generating the drop-down * if only one user was found. Default empty. * @type string $orderby Field to order found users by. Accepts user fields. * Default 'display_name'. * @type string $order Whether to order users in ascending or descending * order. Accepts 'ASC' (ascending) or 'DESC' (descending). * Default 'ASC'. * @type int[]|string $include Array or comma-separated list of user IDs to include. * Default empty. * @type int[]|string $exclude Array or comma-separated list of user IDs to exclude. * Default empty. * @type bool|int $multi Whether to skip the ID attribute on the 'select' element. * Accepts 1|true or 0|false. Default 0|false. * @type string $show User data to display. If the selected item is empty * then the 'user_login' will be displayed in parentheses. * Accepts any user field, or 'display_name_with_login' to show * the display name with user_login in parentheses. * Default 'display_name'. * @type int|bool $echo Whether to echo or return the drop-down. Accepts 1|true (echo) * or 0|false (return). Default 1|true. * @type int $selected Which user ID should be selected. Default 0. * @type bool $include_selected Whether to always include the selected user ID in the drop- * down. Default false. * @type string $name Name attribute of select element. Default 'user'. * @type string $id ID attribute of the select element. Default is the value of `$name`. * @type string $class Class attribute of the select element. Default empty. * @type int $blog_id ID of blog (Multisite only). Default is ID of the current blog. * @type string $who Deprecated, use `$capability` instead. * Which type of users to query. Accepts only an empty string or * 'authors'. Default empty (all users). * @type string|string[] $role An array or a comma-separated list of role names that users * must match to be included in results. Note that this is * an inclusive list: users must match *each* role. Default empty. * @type string[] $role__in An array of role names. Matched users must have at least one * of these roles. Default empty array. * @type string[] $role__not_in An array of role names to exclude. Users matching one or more * of these roles will not be included in results. Default empty array. * @type string|string[] $capability An array or a comma-separated list of capability names that users * must match to be included in results. Note that this is * an inclusive list: users must match *each* capability. * Does NOT work for capabilities not in the database or filtered * via {@see 'map_meta_cap'}. Default empty. * @type string[] $capability__in An array of capability names. Matched users must have at least one * of these capabilities. * Does NOT work for capabilities not in the database or filtered * via {@see 'map_meta_cap'}. Default empty array. * @type string[] $capability__not_in An array of capability names to exclude. Users matching one or more * of these capabilities will not be included in results. * Does NOT work for capabilities not in the database or filtered * via {@see 'map_meta_cap'}. Default empty array. * } * @return string HTML dropdown list of users. */ function wp_dropdown_users( $args = '' ) { $defaults = array( 'show_option_all' => '', 'show_option_none' => '', 'hide_if_only_one_author' => '', 'orderby' => 'display_name', 'order' => 'ASC', 'include' => '', 'exclude' => '', 'multi' => 0, 'show' => 'display_name', 'echo' => 1, 'selected' => 0, 'name' => 'user', 'class' => '', 'id' => '', 'blog_id' => get_current_blog_id(), 'who' => '', 'include_selected' => false, 'option_none_value' => -1, 'role' => '', 'role__in' => array(), 'role__not_in' => array(), 'capability' => '', 'capability__in' => array(), 'capability__not_in' => array(), ); $defaults['selected'] = is_author() ? get_query_var( 'author' ) : 0; $parsed_args = wp_parse_args( $args, $defaults ); $query_args = wp_array_slice_assoc( $parsed_args, array( 'blog_id', 'include', 'exclude', 'orderby', 'order', 'who', 'role', 'role__in', 'role__not_in', 'capability', 'capability__in', 'capability__not_in', ) ); $fields = array( 'ID', 'user_login' ); $show = ! empty( $parsed_args['show'] ) ? $parsed_args['show'] : 'display_name'; if ( 'display_name_with_login' === $show ) { $fields[] = 'display_name'; } else { $fields[] = $show; } $query_args['fields'] = $fields; $show_option_all = $parsed_args['show_option_all']; $show_option_none = $parsed_args['show_option_none']; $option_none_value = $parsed_args['option_none_value']; /** * Filters the query arguments for the list of users in the dropdown. * * @since 4.4.0 * * @param array $query_args The query arguments for get_users(). * @param array $parsed_args The arguments passed to wp_dropdown_users() combined with the defaults. */ $query_args = apply_filters( 'wp_dropdown_users_args', $query_args, $parsed_args ); $users = get_users( $query_args ); $output = ''; if ( ! empty( $users ) && ( empty( $parsed_args['hide_if_only_one_author'] ) || count( $users ) > 1 ) ) { $name = esc_attr( $parsed_args['name'] ); if ( $parsed_args['multi'] && ! $parsed_args['id'] ) { $id = ''; } else { $id = $parsed_args['id'] ? " id='" . esc_attr( $parsed_args['id'] ) . "'" : " id='$name'"; } $output = "<select name='{$name}'{$id} class='" . $parsed_args['class'] . "'>\n"; if ( $show_option_all ) { $output .= "\t<option value='0'>$show_option_all</option>\n"; } if ( $show_option_none ) { $_selected = selected( $option_none_value, $parsed_args['selected'], false ); $output .= "\t<option value='" . esc_attr( $option_none_value ) . "'$_selected>$show_option_none</option>\n"; } if ( $parsed_args['include_selected'] && ( $parsed_args['selected'] > 0 ) ) { $found_selected = false; $parsed_args['selected'] = (int) $parsed_args['selected']; foreach ( (array) $users as $user ) { $user->ID = (int) $user->ID; if ( $user->ID === $parsed_args['selected'] ) { $found_selected = true; } } if ( ! $found_selected ) { $selected_user = get_userdata( $parsed_args['selected'] ); if ( $selected_user ) { $users[] = $selected_user; } } } foreach ( (array) $users as $user ) { if ( 'display_name_with_login' === $show ) { /* translators: 1: User's display name, 2: User login. */ $display = sprintf( _x( '%1$s (%2$s)', 'user dropdown' ), $user->display_name, $user->user_login ); } elseif ( ! empty( $user->$show ) ) { $display = $user->$show; } else { $display = '(' . $user->user_login . ')'; } $_selected = selected( $user->ID, $parsed_args['selected'], false ); $output .= "\t<option value='$user->ID'$_selected>" . esc_html( $display ) . "</option>\n"; } $output .= '</select>'; } /** * Filters the wp_dropdown_users() HTML output. * * @since 2.3.0 * * @param string $output HTML output generated by wp_dropdown_users(). */ $html = apply_filters( 'wp_dropdown_users', $output ); if ( $parsed_args['echo'] ) { echo $html; } return $html; } /** * Sanitizes user field based on context. * * Possible context values are: 'raw', 'edit', 'db', 'display', 'attribute' and 'js'. The * 'display' context is used by default. 'attribute' and 'js' contexts are treated like 'display' * when calling filters. * * @since 2.3.0 * * @param string $field The user Object field name. * @param mixed $value The user Object value. * @param int $user_id User ID. * @param string $context How to sanitize user fields. Looks for 'raw', 'edit', 'db', 'display', * 'attribute' and 'js'. * @return mixed Sanitized value. */ function sanitize_user_field( $field, $value, $user_id, $context ) { $int_fields = array( 'ID' ); if ( in_array( $field, $int_fields, true ) ) { $value = (int) $value; } if ( 'raw' === $context ) { return $value; } if ( ! is_string( $value ) && ! is_numeric( $value ) ) { return $value; } $prefixed = str_contains( $field, 'user_' ); if ( 'edit' === $context ) { if ( $prefixed ) { /** This filter is documented in wp-includes/post.php */ $value = apply_filters( "edit_{$field}", $value, $user_id ); } else { /** * Filters a user field value in the 'edit' context. * * The dynamic portion of the hook name, `$field`, refers to the prefixed user * field being filtered, such as 'user_login', 'user_email', 'first_name', etc. * * @since 2.9.0 * * @param mixed $value Value of the prefixed user field. * @param int $user_id User ID. */ $value = apply_filters( "edit_user_{$field}", $value, $user_id ); } if ( 'description' === $field ) { $value = esc_html( $value ); // textarea_escaped? } else { $value = esc_attr( $value ); } } elseif ( 'db' === $context ) { if ( $prefixed ) { /** This filter is documented in wp-includes/post.php */ $value = apply_filters( "pre_{$field}", $value ); } else { /** * Filters the value of a user field in the 'db' context. * * The dynamic portion of the hook name, `$field`, refers to the prefixed user * field being filtered, such as 'user_login', 'user_email', 'first_name', etc. * * @since 2.9.0 * * @param mixed $value Value of the prefixed user field. */ $value = apply_filters( "pre_user_{$field}", $value ); } } else { // Use display filters by default. if ( $prefixed ) { /** This filter is documented in wp-includes/post.php */ $value = apply_filters( "{$field}", $value, $user_id, $context ); } else { /** * Filters the value of a user field in a standard context. * * The dynamic portion of the hook name, `$field`, refers to the prefixed user * field being filtered, such as 'user_login', 'user_email', 'first_name', etc. * * @since 2.9.0 * * @param mixed $value The user object value to sanitize. * @param int $user_id User ID. * @param string $context The context to filter within. */ $value = apply_filters( "user_{$field}", $value, $user_id, $context ); } } if ( 'user_url' === $field ) { $value = esc_url( $value ); } if ( 'attribute' === $context ) { $value = esc_attr( $value ); } elseif ( 'js' === $context ) { $value = esc_js( $value ); } // Restore the type for integer fields after esc_attr(). if ( in_array( $field, $int_fields, true ) ) { $value = (int) $value; } return $value; } /** * Updates all user caches. * * @since 3.0.0 * * @param object|WP_User $user User object or database row to be cached * @return void|false Void on success, false on failure. */ function update_user_caches( $user ) { if ( $user instanceof WP_User ) { if ( ! $user->exists() ) { return false; } $user = $user->data; } wp_cache_add( $user->ID, $user, 'users' ); wp_cache_add( $user->user_login, $user->ID, 'userlogins' ); wp_cache_add( $user->user_nicename, $user->ID, 'userslugs' ); if ( ! empty( $user->user_email ) ) { wp_cache_add( $user->user_email, $user->ID, 'useremail' ); } } /** * Cleans all user caches. * * @since 3.0.0 * @since 4.4.0 'clean_user_cache' action was added. * @since 6.2.0 User metadata caches are now cleared. * * @param WP_User|int $user User object or ID to be cleaned from the cache */ function clean_user_cache( $user ) { if ( is_numeric( $user ) ) { $user = new WP_User( $user ); } if ( ! $user->exists() ) { return; } wp_cache_delete( $user->ID, 'users' ); wp_cache_delete( $user->user_login, 'userlogins' ); wp_cache_delete( $user->user_nicename, 'userslugs' ); if ( ! empty( $user->user_email ) ) { wp_cache_delete( $user->user_email, 'useremail' ); } wp_cache_delete( $user->ID, 'user_meta' ); wp_cache_set_users_last_changed(); /** * Fires immediately after the given user's cache is cleaned. * * @since 4.4.0 * * @param int $user_id User ID. * @param WP_User $user User object. */ do_action( 'clean_user_cache', $user->ID, $user ); } /** * Determines whether the given username exists. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.0.0 * * @param string $username The username to check for existence. * @return int|false The user ID on success, false on failure. */ function username_exists( $username ) { $user = get_user_by( 'login', $username ); if ( $user ) { $user_id = $user->ID; } else { $user_id = false; } /** * Filters whether the given username exists. * * @since 4.9.0 * * @param int|false $user_id The user ID associated with the username, * or false if the username does not exist. * @param string $username The username to check for existence. */ return apply_filters( 'username_exists', $user_id, $username ); } /** * Determines whether the given email exists. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.1.0 * * @param string $email The email to check for existence. * @return int|false The user ID on success, false on failure. */ function email_exists( $email ) { $user = get_user_by( 'email', $email ); if ( $user ) { $user_id = $user->ID; } else { $user_id = false; } /** * Filters whether the given email exists. * * @since 5.6.0 * * @param int|false $user_id The user ID associated with the email, * or false if the email does not exist. * @param string $email The email to check for existence. */ return apply_filters( 'email_exists', $user_id, $email ); } /** * Checks whether a username is valid. * * @since 2.0.1 * @since 4.4.0 Empty sanitized usernames are now considered invalid. * * @param string $username Username. * @return bool Whether username given is valid. */ function validate_username( $username ) { $sanitized = sanitize_user( $username, true ); $valid = ( $sanitized === $username && ! empty( $sanitized ) ); /** * Filters whether the provided username is valid. * * @since 2.0.1 * * @param bool $valid Whether given username is valid. * @param string $username Username to check. */ return apply_filters( 'validate_username', $valid, $username ); } /** * Inserts a user into the database. * * Most of the `$userdata` array fields have filters associated with the values. Exceptions are * 'ID', 'rich_editing', 'syntax_highlighting', 'comment_shortcuts', 'admin_color', 'use_ssl', * 'user_registered', 'user_activation_key', 'spam', and 'role'. The filters have the prefix * 'pre_user_' followed by the field name. An example using 'description' would have the filter * called 'pre_user_description' that can be hooked into. * * @since 2.0.0 * @since 3.6.0 The `aim`, `jabber`, and `yim` fields were removed as default user contact * methods for new installations. See wp_get_user_contact_methods(). * @since 4.7.0 The `locale` field can be passed to `$userdata`. * @since 5.3.0 The `user_activation_key` field can be passed to `$userdata`. * @since 5.3.0 The `spam` field can be passed to `$userdata` (Multisite only). * @since 5.9.0 The `meta_input` field can be passed to `$userdata` to allow addition of user meta data. * * @global wpdb $wpdb WordPress database abstraction object. * * @param array|object|WP_User $userdata { * An array, object, or WP_User object of user data arguments. * * @type int $ID User ID. If supplied, the user will be updated. * @type string $user_pass The plain-text user password for new users. * Hashed password for existing users. * @type string $user_login The user's login username. * @type string $user_nicename The URL-friendly user name. * @type string $user_url The user URL. * @type string $user_email The user email address. * @type string $display_name The user's display name. * Default is the user's username. * @type string $nickname The user's nickname. * Default is the user's username. * @type string $first_name The user's first name. For new users, will be used * to build the first part of the user's display name * if `$display_name` is not specified. * @type string $last_name The user's last name. For new users, will be used * to build the second part of the user's display name * if `$display_name` is not specified. * @type string $description The user's biographical description. * @type string $rich_editing Whether to enable the rich-editor for the user. * Accepts 'true' or 'false' as a string literal, * not boolean. Default 'true'. * @type string $syntax_highlighting Whether to enable the rich code editor for the user. * Accepts 'true' or 'false' as a string literal, * not boolean. Default 'true'. * @type string $comment_shortcuts Whether to enable comment moderation keyboard * shortcuts for the user. Accepts 'true' or 'false' * as a string literal, not boolean. Default 'false'. * @type string $admin_color Admin color scheme for the user. Default 'fresh'. * @type bool $use_ssl Whether the user should always access the admin over * https. Default false. * @type string $user_registered Date the user registered in UTC. Format is 'Y-m-d H:i:s'. * @type string $user_activation_key Password reset key. Default empty. * @type bool $spam Multisite only. Whether the user is marked as spam. * Default false. * @type string $show_admin_bar_front Whether to display the Admin Bar for the user * on the site's front end. Accepts 'true' or 'false' * as a string literal, not boolean. Default 'true'. * @type string $role User's role. * @type string $locale User's locale. Default empty. * @type array $meta_input Array of custom user meta values keyed by meta key. * Default empty. * } * @return int|WP_Error The newly created user's ID or a WP_Error object if the user could not * be created. */ function wp_insert_user( $userdata ) { global $wpdb; if ( $userdata instanceof stdClass ) { $userdata = get_object_vars( $userdata ); } elseif ( $userdata instanceof WP_User ) { $userdata = $userdata->to_array(); } // Are we updating or creating? if ( ! empty( $userdata['ID'] ) ) { $user_id = (int) $userdata['ID']; $update = true; $old_user_data = get_userdata( $user_id ); if ( ! $old_user_data ) { return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) ); } // Slash current user email to compare it later with slashed new user email. $old_user_data->user_email = wp_slash( $old_user_data->user_email ); // Hashed in wp_update_user(), plaintext if called directly. $user_pass = ! empty( $userdata['user_pass'] ) ? $userdata['user_pass'] : $old_user_data->user_pass; } else { $update = false; if ( empty( $userdata['user_pass'] ) ) { wp_trigger_error( __FUNCTION__, __( 'The user_pass field is required when creating a new user. The user will need to reset their password before logging in.' ), E_USER_WARNING ); // Set the password as an empty string to force the password reset flow. $userdata['user_pass'] = ''; } // Hash the password. $user_pass = wp_hash_password( $userdata['user_pass'] ); } $sanitized_user_login = sanitize_user( $userdata['user_login'], true ); /** * Filters a username after it has been sanitized. * * This filter is called before the user is created or updated. * * @since 2.0.3 * * @param string $sanitized_user_login Username after it has been sanitized. */ $pre_user_login = apply_filters( 'pre_user_login', $sanitized_user_login ); // Remove any non-printable chars from the login string to see if we have ended up with an empty username. $user_login = trim( $pre_user_login ); // user_login must be between 0 and 60 characters. if ( empty( $user_login ) ) { return new WP_Error( 'empty_user_login', __( 'Cannot create a user with an empty login name.' ) ); } elseif ( mb_strlen( $user_login ) > 60 ) { return new WP_Error( 'user_login_too_long', __( 'Username may not be longer than 60 characters.' ) ); } if ( ! $update && username_exists( $user_login ) ) { return new WP_Error( 'existing_user_login', __( 'Sorry, that username already exists!' ) ); } /** * Filters the list of disallowed usernames. * * @since 4.4.0 * * @param array $usernames Array of disallowed usernames. */ $illegal_logins = (array) apply_filters( 'illegal_user_logins', array() ); if ( in_array( strtolower( $user_login ), array_map( 'strtolower', $illegal_logins ), true ) ) { return new WP_Error( 'invalid_username', __( 'Sorry, that username is not allowed.' ) ); } /* * If a nicename is provided, remove unsafe user characters before using it. * Otherwise build a nicename from the user_login. */ if ( ! empty( $userdata['user_nicename'] ) ) { $user_nicename = sanitize_user( $userdata['user_nicename'], true ); } else { $user_nicename = mb_substr( $user_login, 0, 50 ); } $user_nicename = sanitize_title( $user_nicename ); /** * Filters a user's nicename before the user is created or updated. * * @since 2.0.3 * * @param string $user_nicename The user's nicename. */ $user_nicename = apply_filters( 'pre_user_nicename', $user_nicename ); // Check if the sanitized nicename is empty. if ( empty( $user_nicename ) ) { return new WP_Error( 'empty_user_nicename', __( 'Cannot create a user with an empty nicename.' ) ); } elseif ( mb_strlen( $user_nicename ) > 50 ) { return new WP_Error( 'user_nicename_too_long', __( 'Nicename may not be longer than 50 characters.' ) ); } $user_nicename_check = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->users WHERE user_nicename = %s AND user_login != %s LIMIT 1", $user_nicename, $user_login ) ); if ( $user_nicename_check ) { $suffix = 2; while ( $user_nicename_check ) { // user_nicename allows 50 chars. Subtract one for a hyphen, plus the length of the suffix. $base_length = 49 - mb_strlen( $suffix ); $alt_user_nicename = mb_substr( $user_nicename, 0, $base_length ) . "-$suffix"; $user_nicename_check = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->users WHERE user_nicename = %s AND user_login != %s LIMIT 1", $alt_user_nicename, $user_login ) ); ++$suffix; } $user_nicename = $alt_user_nicename; } $raw_user_email = empty( $userdata['user_email'] ) ? '' : $userdata['user_email']; /** * Filters a user's email before the user is created or updated. * * @since 2.0.3 * * @param string $raw_user_email The user's email. */ $user_email = apply_filters( 'pre_user_email', $raw_user_email ); /* * If there is no update, just check for `email_exists`. If there is an update, * check if current email and new email are the same, and check `email_exists` * accordingly. */ if ( ( ! $update || ( ! empty( $old_user_data ) && 0 !== strcasecmp( $user_email, $old_user_data->user_email ) ) ) && ! defined( 'WP_IMPORTING' ) && email_exists( $user_email ) ) { return new WP_Error( 'existing_user_email', __( 'Sorry, that email address is already used!' ) ); } $raw_user_url = empty( $userdata['user_url'] ) ? '' : $userdata['user_url']; /** * Filters a user's URL before the user is created or updated. * * @since 2.0.3 * * @param string $raw_user_url The user's URL. */ $user_url = apply_filters( 'pre_user_url', $raw_user_url ); if ( mb_strlen( $user_url ) > 100 ) { return new WP_Error( 'user_url_too_long', __( 'User URL may not be longer than 100 characters.' ) ); } $user_registered = empty( $userdata['user_registered'] ) ? gmdate( 'Y-m-d H:i:s' ) : $userdata['user_registered']; $user_activation_key = empty( $userdata['user_activation_key'] ) ? '' : $userdata['user_activation_key']; if ( ! empty( $userdata['spam'] ) && ! is_multisite() ) { return new WP_Error( 'no_spam', __( 'Sorry, marking a user as spam is only supported on Multisite.' ) ); } $spam = empty( $userdata['spam'] ) ? 0 : (bool) $userdata['spam']; // Store values to save in user meta. $meta = array(); $nickname = empty( $userdata['nickname'] ) ? $user_login : $userdata['nickname']; /** * Filters a user's nickname before the user is created or updated. * * @since 2.0.3 * * @param string $nickname The user's nickname. */ $meta['nickname'] = apply_filters( 'pre_user_nickname', $nickname ); $first_name = empty( $userdata['first_name'] ) ? '' : $userdata['first_name']; /** * Filters a user's first name before the user is created or updated. * * @since 2.0.3 * * @param string $first_name The user's first name. */ $meta['first_name'] = apply_filters( 'pre_user_first_name', $first_name ); $last_name = empty( $userdata['last_name'] ) ? '' : $userdata['last_name']; /** * Filters a user's last name before the user is created or updated. * * @since 2.0.3 * * @param string $last_name The user's last name. */ $meta['last_name'] = apply_filters( 'pre_user_last_name', $last_name ); if ( empty( $userdata['display_name'] ) ) { if ( $update ) { $display_name = $user_login; } elseif ( $meta['first_name'] && $meta['last_name'] ) { $display_name = sprintf( /* translators: 1: User's first name, 2: Last name. */ _x( '%1$s %2$s', 'Display name based on first name and last name' ), $meta['first_name'], $meta['last_name'] ); } elseif ( $meta['first_name'] ) { $display_name = $meta['first_name']; } elseif ( $meta['last_name'] ) { $display_name = $meta['last_name']; } else { $display_name = $user_login; } } else { $display_name = $userdata['display_name']; } /** * Filters a user's display name before the user is created or updated. * * @since 2.0.3 * * @param string $display_name The user's display name. */ $display_name = apply_filters( 'pre_user_display_name', $display_name ); $description = empty( $userdata['description'] ) ? '' : $userdata['description']; /** * Filters a user's description before the user is created or updated. * * @since 2.0.3 * * @param string $description The user's description. */ $meta['description'] = apply_filters( 'pre_user_description', $description ); $meta['rich_editing'] = empty( $userdata['rich_editing'] ) ? 'true' : $userdata['rich_editing']; $meta['syntax_highlighting'] = empty( $userdata['syntax_highlighting'] ) ? 'true' : $userdata['syntax_highlighting']; $meta['comment_shortcuts'] = empty( $userdata['comment_shortcuts'] ) || 'false' === $userdata['comment_shortcuts'] ? 'false' : 'true'; $admin_color = empty( $userdata['admin_color'] ) ? 'fresh' : $userdata['admin_color']; $meta['admin_color'] = preg_replace( '|[^a-z0-9 _.\-@]|i', '', $admin_color ); $meta['use_ssl'] = empty( $userdata['use_ssl'] ) ? '0' : '1'; $meta['show_admin_bar_front'] = empty( $userdata['show_admin_bar_front'] ) ? 'true' : $userdata['show_admin_bar_front']; $meta['locale'] = isset( $userdata['locale'] ) ? $userdata['locale'] : ''; $compacted = compact( 'user_pass', 'user_nicename', 'user_email', 'user_url', 'user_registered', 'user_activation_key', 'display_name' ); $data = wp_unslash( $compacted ); if ( ! $update ) { $data = $data + compact( 'user_login' ); } if ( is_multisite() ) { $data = $data + compact( 'spam' ); } /** * Filters user data before the record is created or updated. * * It only includes data in the users table, not any user metadata. * * @since 4.9.0 * @since 5.8.0 The `$userdata` parameter was added. * @since 6.8.0 The user's password is now hashed using bcrypt by default instead of phpass. * * @param array $data { * Values and keys for the user. * * @type string $user_login The user's login. Only included if $update == false * @type string $user_pass The user's password. * @type string $user_email The user's email. * @type string $user_url The user's url. * @type string $user_nicename The user's nice name. Defaults to a URL-safe version of user's login. * @type string $display_name The user's display name. * @type string $user_registered MySQL timestamp describing the moment when the user registered. Defaults to * the current UTC timestamp. * } * @param bool $update Whether the user is being updated rather than created. * @param int|null $user_id ID of the user to be updated, or NULL if the user is being created. * @param array $userdata The raw array of data passed to wp_insert_user(). */ $data = apply_filters( 'wp_pre_insert_user_data', $data, $update, ( $update ? $user_id : null ), $userdata ); if ( empty( $data ) || ! is_array( $data ) ) { return new WP_Error( 'empty_data', __( 'Not enough data to create this user.' ) ); } if ( $update ) { if ( $user_email !== $old_user_data->user_email || $user_pass !== $old_user_data->user_pass ) { $data['user_activation_key'] = ''; } $wpdb->update( $wpdb->users, $data, array( 'ID' => $user_id ) ); } else { $wpdb->insert( $wpdb->users, $data ); $user_id = (int) $wpdb->insert_id; } $user = new WP_User( $user_id ); if ( ! $update ) { /** This action is documented in wp-includes/pluggable.php */ do_action( 'wp_set_password', $userdata['user_pass'], $user_id, $user ); } /** * Filters a user's meta values and keys immediately after the user is created or updated * and before any user meta is inserted or updated. * * Does not include contact methods. These are added using `wp_get_user_contact_methods( $user )`. * * For custom meta fields, see the {@see 'insert_custom_user_meta'} filter. * * @since 4.4.0 * @since 5.8.0 The `$userdata` parameter was added. * * @param array $meta { * Default meta values and keys for the user. * * @type string $nickname The user's nickname. Default is the user's username. * @type string $first_name The user's first name. * @type string $last_name The user's last name. * @type string $description The user's description. * @type string $rich_editing Whether to enable the rich-editor for the user. Default 'true'. * @type string $syntax_highlighting Whether to enable the rich code editor for the user. Default 'true'. * @type string $comment_shortcuts Whether to enable keyboard shortcuts for the user. Default 'false'. * @type string $admin_color The color scheme for a user's admin screen. Default 'fresh'. * @type int|bool $use_ssl Whether to force SSL on the user's admin area. 0|false if SSL * is not forced. * @type string $show_admin_bar_front Whether to show the admin bar on the front end for the user. * Default 'true'. * @type string $locale User's locale. Default empty. * } * @param WP_User $user User object. * @param bool $update Whether the user is being updated rather than created. * @param array $userdata The raw array of data passed to wp_insert_user(). */ $meta = apply_filters( 'insert_user_meta', $meta, $user, $update, $userdata ); $custom_meta = array(); if ( array_key_exists( 'meta_input', $userdata ) && is_array( $userdata['meta_input'] ) && ! empty( $userdata['meta_input'] ) ) { $custom_meta = $userdata['meta_input']; } /** * Filters a user's custom meta values and keys immediately after the user is created or updated * and before any user meta is inserted or updated. * * For non-custom meta fields, see the {@see 'insert_user_meta'} filter. * * @since 5.9.0 * * @param array $custom_meta Array of custom user meta values keyed by meta key. * @param WP_User $user User object. * @param bool $update Whether the user is being updated rather than created. * @param array $userdata The raw array of data passed to wp_insert_user(). */ $custom_meta = apply_filters( 'insert_custom_user_meta', $custom_meta, $user, $update, $userdata ); $meta = array_merge( $meta, $custom_meta ); if ( $update ) { // Update user meta. foreach ( $meta as $key => $value ) { update_user_meta( $user_id, $key, $value ); } } else { // Add user meta. foreach ( $meta as $key => $value ) { add_user_meta( $user_id, $key, $value ); } } foreach ( wp_get_user_contact_methods( $user ) as $key => $value ) { if ( isset( $userdata[ $key ] ) ) { update_user_meta( $user_id, $key, $userdata[ $key ] ); } } if ( isset( $userdata['role'] ) ) { $user->set_role( $userdata['role'] ); } elseif ( ! $update ) { $user->set_role( get_option( 'default_role' ) ); } clean_user_cache( $user_id ); if ( $update ) { /** * Fires immediately after an existing user is updated. * * @since 2.0.0 * @since 5.8.0 The `$userdata` parameter was added. * * @param int $user_id User ID. * @param WP_User $old_user_data Object containing user's data prior to update. * @param array $userdata The raw array of data passed to wp_insert_user(). */ do_action( 'profile_update', $user_id, $old_user_data, $userdata ); if ( isset( $userdata['spam'] ) && $userdata['spam'] !== $old_user_data->spam ) { if ( '1' === $userdata['spam'] ) { /** * Fires after the user is marked as a SPAM user. * * @since 3.0.0 * * @param int $user_id ID of the user marked as SPAM. */ do_action( 'make_spam_user', $user_id ); } else { /** * Fires after the user is marked as a HAM user. Opposite of SPAM. * * @since 3.0.0 * * @param int $user_id ID of the user marked as HAM. */ do_action( 'make_ham_user', $user_id ); } } } else { /** * Fires immediately after a new user is registered. * * @since 1.5.0 * @since 5.8.0 The `$userdata` parameter was added. * * @param int $user_id User ID. * @param array $userdata The raw array of data passed to wp_insert_user(). */ do_action( 'user_register', $user_id, $userdata ); } return $user_id; } /** * Updates a user in the database. * * It is possible to update a user's password by specifying the 'user_pass' * value in the $userdata parameter array. * * If current user's password is being updated, then the cookies will be * cleared. * * @since 2.0.0 * * @see wp_insert_user() For what fields can be set in $userdata. * * @param array|object|WP_User $userdata An array of user data or a user object of type stdClass or WP_User. * @return int|WP_Error The updated user's ID or a WP_Error object if the user could not be updated. */ function wp_update_user( $userdata ) { if ( $userdata instanceof stdClass ) { $userdata = get_object_vars( $userdata ); } elseif ( $userdata instanceof WP_User ) { $userdata = $userdata->to_array(); } $userdata_raw = $userdata; $user_id = isset( $userdata['ID'] ) ? (int) $userdata['ID'] : 0; if ( ! $user_id ) { return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) ); } // First, get all of the original fields. $user_obj = get_userdata( $user_id ); if ( ! $user_obj ) { return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) ); } $user = $user_obj->to_array(); // Add additional custom fields. foreach ( _get_additional_user_keys( $user_obj ) as $key ) { $user[ $key ] = get_user_meta( $user_id, $key, true ); } // Escape data pulled from DB. $user = add_magic_quotes( $user ); if ( ! empty( $userdata['user_pass'] ) && $userdata['user_pass'] !== $user_obj->user_pass ) { // If password is changing, hash it now. $plaintext_pass = $userdata['user_pass']; $userdata['user_pass'] = wp_hash_password( $userdata['user_pass'] ); /** This action is documented in wp-includes/pluggable.php */ do_action( 'wp_set_password', $plaintext_pass, $user_id, $user_obj ); /** * Filters whether to send the password change email. * * @since 4.3.0 * * @see wp_insert_user() For `$user` and `$userdata` fields. * * @param bool $send Whether to send the email. * @param array $user The original user array. * @param array $userdata The updated user array. */ $send_password_change_email = apply_filters( 'send_password_change_email', true, $user, $userdata ); } if ( isset( $userdata['user_email'] ) && $user['user_email'] !== $userdata['user_email'] ) { /** * Filters whether to send the email change email. * * @since 4.3.0 * * @see wp_insert_user() For `$user` and `$userdata` fields. * * @param bool $send Whether to send the email. * @param array $user The original user array. * @param array $userdata The updated user array. */ $send_email_change_email = apply_filters( 'send_email_change_email', true, $user, $userdata ); } clean_user_cache( $user_obj ); // Merge old and new fields with new fields overwriting old ones. $userdata = array_merge( $user, $userdata ); $user_id = wp_insert_user( $userdata ); if ( is_wp_error( $user_id ) ) { return $user_id; } $blog_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); $switched_locale = false; if ( ! empty( $send_password_change_email ) || ! empty( $send_email_change_email ) ) { $switched_locale = switch_to_user_locale( $user_id ); } if ( ! empty( $send_password_change_email ) ) { /* translators: Do not translate USERNAME, ADMIN_EMAIL, EMAIL, SITENAME, SITEURL: those are placeholders. */ $pass_change_text = __( 'Hi ###USERNAME###, This notice confirms that your password was changed on ###SITENAME###. If you did not change your password, please contact the Site Administrator at ###ADMIN_EMAIL### This email has been sent to ###EMAIL### Regards, All at ###SITENAME### ###SITEURL###' ); $pass_change_email = array( 'to' => $user['user_email'], /* translators: Password change notification email subject. %s: Site title. */ 'subject' => __( '[%s] Password Changed' ), 'message' => $pass_change_text, 'headers' => '', ); /** * Filters the contents of the email sent when the user's password is changed. * * @since 4.3.0 * * @param array $pass_change_email { * Used to build wp_mail(). * * @type string $to The intended recipients. Add emails in a comma separated string. * @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: * - `###USERNAME###` The current user's username. * - `###ADMIN_EMAIL###` The admin email in case this was unexpected. * - `###EMAIL###` The user's email address. * - `###SITENAME###` The name of the site. * - `###SITEURL###` The URL to the site. * @type string $headers Headers. Add headers in a newline (\r\n) separated string. * } * @param array $user The original user array. * @param array $userdata The updated user array. */ $pass_change_email = apply_filters( 'password_change_email', $pass_change_email, $user, $userdata ); $pass_change_email['message'] = str_replace( '###USERNAME###', $user['user_login'], $pass_change_email['message'] ); $pass_change_email['message'] = str_replace( '###ADMIN_EMAIL###', get_option( 'admin_email' ), $pass_change_email['message'] ); $pass_change_email['message'] = str_replace( '###EMAIL###', $user['user_email'], $pass_change_email['message'] ); $pass_change_email['message'] = str_replace( '###SITENAME###', $blog_name, $pass_change_email['message'] ); $pass_change_email['message'] = str_replace( '###SITEURL###', home_url(), $pass_change_email['message'] ); wp_mail( $pass_change_email['to'], sprintf( $pass_change_email['subject'], $blog_name ), $pass_change_email['message'], $pass_change_email['headers'] ); } if ( ! empty( $send_email_change_email ) ) { /* translators: Do not translate USERNAME, ADMIN_EMAIL, NEW_EMAIL, EMAIL, SITENAME, SITEURL: those are placeholders. */ $email_change_text = __( 'Hi ###USERNAME###, This notice confirms that your email address on ###SITENAME### was changed to ###NEW_EMAIL###. If you did not change your email, please contact the Site Administrator at ###ADMIN_EMAIL### This email has been sent to ###EMAIL### Regards, All at ###SITENAME### ###SITEURL###' ); $email_change_email = array( 'to' => $user['user_email'], /* translators: Email change notification email subject. %s: Site title. */ 'subject' => __( '[%s] Email Changed' ), 'message' => $email_change_text, 'headers' => '', ); /** * Filters the contents of the email sent when the user's email is changed. * * @since 4.3.0 * * @param array $email_change_email { * Used to build wp_mail(). * * @type string $to The intended recipients. * @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: * - `###USERNAME###` The current user's username. * - `###ADMIN_EMAIL###` The admin email in case this was unexpected. * - `###NEW_EMAIL###` The new email address. * - `###EMAIL###` The old email address. * - `###SITENAME###` The name of the site. * - `###SITEURL###` The URL to the site. * @type string $headers Headers. * } * @param array $user The original user array. * @param array $userdata The updated user array. */ $email_change_email = apply_filters( 'email_change_email', $email_change_email, $user, $userdata ); $email_change_email['message'] = str_replace( '###USERNAME###', $user['user_login'], $email_change_email['message'] ); $email_change_email['message'] = str_replace( '###ADMIN_EMAIL###', get_option( 'admin_email' ), $email_change_email['message'] ); $email_change_email['message'] = str_replace( '###NEW_EMAIL###', $userdata['user_email'], $email_change_email['message'] ); $email_change_email['message'] = str_replace( '###EMAIL###', $user['user_email'], $email_change_email['message'] ); $email_change_email['message'] = str_replace( '###SITENAME###', $blog_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'], $blog_name ), $email_change_email['message'], $email_change_email['headers'] ); } if ( $switched_locale ) { restore_previous_locale(); } // Update the cookies if the password changed. $current_user = wp_get_current_user(); if ( $current_user->ID === $user_id ) { if ( isset( $plaintext_pass ) ) { /* * Here we calculate the expiration length of the current auth cookie and compare it to the default expiration. * If it's greater than this, then we know the user checked 'Remember Me' when they logged in. */ $logged_in_cookie = wp_parse_auth_cookie( '', 'logged_in' ); /** This filter is documented in wp-includes/pluggable.php */ $default_cookie_life = apply_filters( 'auth_cookie_expiration', ( 2 * DAY_IN_SECONDS ), $user_id, false ); wp_clear_auth_cookie(); $remember = false; $token = ''; if ( false !== $logged_in_cookie ) { $token = $logged_in_cookie['token']; } if ( false !== $logged_in_cookie && ( (int) $logged_in_cookie['expiration'] - time() ) > $default_cookie_life ) { $remember = true; } wp_set_auth_cookie( $user_id, $remember, '', $token ); } } /** * Fires after the user has been updated and emails have been sent. * * @since 6.3.0 * * @param int $user_id The ID of the user that was just updated. * @param array $userdata The array of user data that was updated. * @param array $userdata_raw The unedited array of user data that was updated. */ do_action( 'wp_update_user', $user_id, $userdata, $userdata_raw ); return $user_id; } /** * Provides a simpler way of inserting a user into the database. * * Creates a new user with just the username, password, and email. For more * complex user creation use wp_insert_user() to specify more information. * * @since 2.0.0 * * @see wp_insert_user() More complete way to create a new user. * * @param string $username The user's username. * @param string $password The user's password. * @param string $email Optional. The user's email. Default empty. * @return int|WP_Error The newly created user's ID or a WP_Error object if the user could not * be created. */ function wp_create_user( $username, #[\SensitiveParameter] $password, $email = '' ) { $user_login = wp_slash( $username ); $user_email = wp_slash( $email ); $user_pass = $password; $userdata = compact( 'user_login', 'user_email', 'user_pass' ); return wp_insert_user( $userdata ); } /** * Returns a list of meta keys to be (maybe) populated in wp_update_user(). * * The list of keys returned via this function are dependent on the presence * of those keys in the user meta data to be set. * * @since 3.3.0 * @access private * * @param WP_User $user WP_User instance. * @return string[] List of user keys to be populated in wp_update_user(). */ function _get_additional_user_keys( $user ) { $keys = array( 'first_name', 'last_name', 'nickname', 'description', 'rich_editing', 'syntax_highlighting', 'comment_shortcuts', 'admin_color', 'use_ssl', 'show_admin_bar_front', 'locale' ); return array_merge( $keys, array_keys( wp_get_user_contact_methods( $user ) ) ); } /** * Sets up the user contact methods. * * Default contact methods were removed for new installations in WordPress 3.6 * and completely removed from the codebase in WordPress 6.9. * * Use the {@see 'user_contactmethods'} filter to add or remove contact methods. * * @since 3.7.0 * @since 6.9.0 Removed references to `aim`, `jabber`, and `yim` contact methods. * * @param WP_User|null $user Optional. WP_User object. * @return string[] Array of contact method labels keyed by contact method. */ function wp_get_user_contact_methods( $user = null ) { $methods = array(); /** * Filters the user contact methods. * * @since 2.9.0 * * @param string[] $methods Array of contact method labels keyed by contact method. * @param WP_User|null $user WP_User object or null if none was provided. */ return apply_filters( 'user_contactmethods', $methods, $user ); } /** * The old private function for setting up user contact methods. * * Use wp_get_user_contact_methods() instead. * * @since 2.9.0 * @access private * * @param WP_User|null $user Optional. WP_User object. Default null. * @return string[] Array of contact method labels keyed by contact method. */ function _wp_get_user_contactmethods( $user = null ) { return wp_get_user_contact_methods( $user ); } /** * Gets the text suggesting how to create strong passwords. * * @since 4.1.0 * * @return string The password hint text. */ function wp_get_password_hint() { $hint = __( 'Hint: The password should be at least twelve characters long. To make it stronger, use upper and lower case letters, numbers, and symbols like ! " ? $ % ^ & ).' ); /** * Filters the text describing the site's password complexity policy. * * @since 4.1.0 * * @param string $hint The password hint text. */ return apply_filters( 'password_hint', $hint ); } /** * Creates, stores, then returns a password reset key for user. * * @since 4.4.0 * * @param WP_User $user User to retrieve password reset key for. * @return string|WP_Error Password reset key on success. WP_Error on error. */ function get_password_reset_key( $user ) { if ( ! ( $user instanceof WP_User ) ) { return new WP_Error( 'invalidcombo', __( '<strong>Error:</strong> There is no account with that username or email address.' ) ); } /** * Fires before a new password is retrieved. * * Use the {@see 'retrieve_password'} hook instead. * * @since 1.5.0 * @deprecated 1.5.1 Misspelled. Use {@see 'retrieve_password'} hook instead. * * @param string $user_login The user login name. */ do_action_deprecated( 'retreive_password', array( $user->user_login ), '1.5.1', 'retrieve_password' ); /** * Fires before a new password is retrieved. * * @since 1.5.1 * * @param string $user_login The user login name. */ do_action( 'retrieve_password', $user->user_login ); $password_reset_allowed = wp_is_password_reset_allowed_for_user( $user ); if ( ! $password_reset_allowed ) { return new WP_Error( 'no_password_reset', __( 'Password reset is not allowed for this user' ) ); } elseif ( is_wp_error( $password_reset_allowed ) ) { return $password_reset_allowed; } // Generate something random for a password reset key. $key = wp_generate_password( 20, false ); /** * Fires when a password reset key is generated. * * @since 2.5.0 * * @param string $user_login The username for the user. * @param string $key The generated password reset key. */ do_action( 'retrieve_password_key', $user->user_login, $key ); $hashed = time() . ':' . wp_fast_hash( $key ); $key_saved = wp_update_user( array( 'ID' => $user->ID, 'user_activation_key' => $hashed, ) ); if ( is_wp_error( $key_saved ) ) { return $key_saved; } return $key; } /** * Retrieves a user row based on password reset key and login. * * A key is considered 'expired' if it exactly matches the value of the * user_activation_key field, rather than being matched after going through the * hashing process. This field is now hashed; old values are no longer accepted * but have a different WP_Error code so good user feedback can be provided. * * @since 3.1.0 * * @param string $key The password reset key. * @param string $login The user login. * @return WP_User|WP_Error WP_User object on success, WP_Error object for invalid or expired keys. */ function check_password_reset_key( #[\SensitiveParameter] $key, $login ) { $key = preg_replace( '/[^a-z0-9]/i', '', $key ); if ( empty( $key ) || ! is_string( $key ) ) { return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } if ( empty( $login ) || ! is_string( $login ) ) { return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } $user = get_user_by( 'login', $login ); if ( ! $user ) { return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } /** * Filters the expiration time of password reset keys. * * @since 4.3.0 * * @param int $expiration The expiration time in seconds. */ $expiration_duration = apply_filters( 'password_reset_expiration', DAY_IN_SECONDS ); if ( str_contains( $user->user_activation_key, ':' ) ) { list( $pass_request_time, $pass_key ) = explode( ':', $user->user_activation_key, 2 ); $expiration_time = $pass_request_time + $expiration_duration; } else { $pass_key = $user->user_activation_key; $expiration_time = false; } if ( ! $pass_key ) { return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } $hash_is_correct = wp_verify_fast_hash( $key, $pass_key ); if ( $hash_is_correct && $expiration_time && time() < $expiration_time ) { return $user; } elseif ( $hash_is_correct && $expiration_time ) { // Key has an expiration time that's passed. return new WP_Error( 'expired_key', __( 'Invalid key.' ) ); } if ( hash_equals( $user->user_activation_key, $key ) || ( $hash_is_correct && ! $expiration_time ) ) { $return = new WP_Error( 'expired_key', __( 'Invalid key.' ) ); $user_id = $user->ID; /** * Filters the return value of check_password_reset_key() when an * old-style key or an expired key is used. * * @since 3.7.0 Previously plain-text keys were stored in the database. * @since 4.3.0 Previously key hashes were stored without an expiration time. * * @param WP_Error $return A WP_Error object denoting an expired key. * Return a WP_User object to validate the key. * @param int $user_id The matched user ID. */ return apply_filters( 'password_reset_key_expired', $return, $user_id ); } return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } /** * Handles sending a password retrieval email to a user. * * @since 2.5.0 * @since 5.7.0 Added `$user_login` parameter. * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $user_login Optional. Username to send a password retrieval email for. * Defaults to `$_POST['user_login']` if not set. * @return true|WP_Error True when finished, WP_Error object on error. */ function retrieve_password( $user_login = '' ) { $errors = new WP_Error(); $user_data = false; // Use the passed $user_login if available, otherwise use $_POST['user_login']. if ( ! $user_login && ! empty( $_POST['user_login'] ) && is_string( $_POST['user_login'] ) ) { $user_login = $_POST['user_login']; } $user_login = trim( wp_unslash( $user_login ) ); if ( empty( $user_login ) ) { $errors->add( 'empty_username', __( '<strong>Error:</strong> Please enter a username or email address.' ) ); } elseif ( strpos( $user_login, '@' ) ) { $user_data = get_user_by( 'email', $user_login ); if ( empty( $user_data ) ) { $user_data = get_user_by( 'login', $user_login ); } if ( empty( $user_data ) ) { $errors->add( 'invalid_email', __( '<strong>Error:</strong> There is no account with that username or email address.' ) ); } } else { $user_data = get_user_by( 'login', $user_login ); } /** * Filters the user data during a password reset request. * * Allows, for example, custom validation using data other than username or email address. * * @since 5.7.0 * * @param WP_User|false $user_data WP_User object if found, false if the user does not exist. * @param WP_Error $errors A WP_Error object containing any errors generated * by using invalid credentials. */ $user_data = apply_filters( 'lostpassword_user_data', $user_data, $errors ); /** * Fires before errors are returned from a password reset request. * * @since 2.1.0 * @since 4.4.0 Added the `$errors` parameter. * @since 5.4.0 Added the `$user_data` parameter. * * @param WP_Error $errors A WP_Error object containing any errors generated * by using invalid credentials. * @param WP_User|false $user_data WP_User object if found, false if the user does not exist. */ do_action( 'lostpassword_post', $errors, $user_data ); /** * Filters the errors encountered on a password reset request. * * The filtered WP_Error object may, for example, contain errors for an invalid * username or email address. A WP_Error object should always be returned, * but may or may not contain errors. * * If any errors are present in $errors, this will abort the password reset request. * * @since 5.5.0 * * @param WP_Error $errors A WP_Error object containing any errors generated * by using invalid credentials. * @param WP_User|false $user_data WP_User object if found, false if the user does not exist. */ $errors = apply_filters( 'lostpassword_errors', $errors, $user_data ); if ( $errors->has_errors() ) { return $errors; } if ( ! $user_data ) { $errors->add( 'invalidcombo', __( '<strong>Error:</strong> There is no account with that username or email address.' ) ); return $errors; } /** * Filters whether to send the retrieve password email. * * Return false to disable sending the email. * * @since 6.0.0 * * @param bool $send Whether to send the email. * @param string $user_login The username for the user. * @param WP_User $user_data WP_User object. */ if ( ! apply_filters( 'send_retrieve_password_email', true, $user_login, $user_data ) ) { return true; } // Redefining user_login ensures we return the right case in the email. $user_login = $user_data->user_login; $user_email = $user_data->user_email; $key = get_password_reset_key( $user_data ); if ( is_wp_error( $key ) ) { return $key; } // Localize password reset message content for user. $locale = get_user_locale( $user_data ); $switched_locale = switch_to_user_locale( $user_data->ID ); if ( is_multisite() ) { $site_name = get_network()->site_name; } else { /* * The blogname option is escaped with esc_html on the way into the database * in sanitize_option. We want to reverse this for the plain text arena of emails. */ $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); } $message = __( 'Someone has requested a password reset for the following account:' ) . "\r\n\r\n"; /* translators: %s: Site name. */ $message .= sprintf( __( 'Site Name: %s' ), $site_name ) . "\r\n\r\n"; /* translators: %s: User login. */ $message .= sprintf( __( 'Username: %s' ), $user_login ) . "\r\n\r\n"; $message .= __( 'If this was a mistake, ignore this email and nothing will happen.' ) . "\r\n\r\n"; $message .= __( 'To reset your password, visit the following address:' ) . "\r\n\r\n"; /* * Since some user login names end in a period, this could produce ambiguous URLs that * end in a period. To avoid the ambiguity, ensure that the login is not the last query * arg in the URL. If moving it to the end, a trailing period will need to be escaped. * * @see https://core.trac.wordpress.org/tickets/42957 */ $message .= network_site_url( 'wp-login.php?login=' . rawurlencode( $user_login ) . "&key=$key&action=rp", 'login' ) . '&wp_lang=' . $locale . "\r\n\r\n"; if ( ! is_user_logged_in() ) { $requester_ip = $_SERVER['REMOTE_ADDR']; if ( $requester_ip ) { $message .= sprintf( /* translators: %s: IP address of password reset requester. */ __( 'This password reset request originated from the IP address %s.' ), $requester_ip ) . "\r\n"; } } /* translators: Password reset notification email subject. %s: Site title. */ $title = sprintf( __( '[%s] Password Reset' ), $site_name ); /** * Filters the subject of the password reset email. * * @since 2.8.0 * @since 4.4.0 Added the `$user_login` and `$user_data` parameters. * * @param string $title Email subject. * @param string $user_login The username for the user. * @param WP_User $user_data WP_User object. */ $title = apply_filters( 'retrieve_password_title', $title, $user_login, $user_data ); /** * Filters the message body of the password reset mail. * * If the filtered message is empty, the password reset email will not be sent. * * @since 2.8.0 * @since 4.1.0 Added `$user_login` and `$user_data` parameters. * * @param string $message Email message. * @param string $key The activation key. * @param string $user_login The username for the user. * @param WP_User $user_data WP_User object. */ $message = apply_filters( 'retrieve_password_message', $message, $key, $user_login, $user_data ); // Short-circuit on falsey $message value for backwards compatibility. if ( ! $message ) { return true; } /* * Wrap the single notification email arguments in an array * to pass them to the retrieve_password_notification_email filter. */ $defaults = array( 'to' => $user_email, 'subject' => $title, 'message' => $message, 'headers' => '', ); /** * Filters the contents of the reset password notification email sent to the user. * * @since 6.0.0 * * @param array $defaults { * The default notification email arguments. Used to build wp_mail(). * * @type string $to The intended recipient - user email address. * @type string $subject The subject of the email. * @type string $message The body of the email. * @type string $headers The headers of the email. * } * @param string $key The activation key. * @param string $user_login The username for the user. * @param WP_User $user_data WP_User object. */ $notification_email = apply_filters( 'retrieve_password_notification_email', $defaults, $key, $user_login, $user_data ); if ( $switched_locale ) { restore_previous_locale(); } if ( is_array( $notification_email ) ) { // Force key order and merge defaults in case any value is missing in the filtered array. $notification_email = array_merge( $defaults, $notification_email ); } else { $notification_email = $defaults; } list( $to, $subject, $message, $headers ) = array_values( $notification_email ); $subject = wp_specialchars_decode( $subject ); if ( ! wp_mail( $to, $subject, $message, $headers ) ) { $errors->add( 'retrieve_password_email_failure', sprintf( /* translators: %s: Documentation URL. */ __( '<strong>Error:</strong> The email could not be sent. Your site may not be correctly configured to send emails. <a href="%s">Get support for resetting your password</a>.' ), esc_url( __( 'https://wordpress.org/documentation/article/reset-your-password/' ) ) ) ); return $errors; } return true; } /** * Handles resetting the user's password. * * @since 2.5.0 * * @param WP_User $user The user * @param string $new_pass New password for the user in plaintext */ function reset_password( $user, #[\SensitiveParameter] $new_pass ) { /** * Fires before the user's password is reset. * * @since 1.5.0 * * @param WP_User $user The user. * @param string $new_pass New user password. */ do_action( 'password_reset', $user, $new_pass ); wp_set_password( $new_pass, $user->ID ); update_user_meta( $user->ID, 'default_password_nag', false ); /** * Fires after the user's password is reset. * * @since 4.4.0 * * @param WP_User $user The user. * @param string $new_pass New user password. */ do_action( 'after_password_reset', $user, $new_pass ); } /** * Handles registering a new user. * * @since 2.5.0 * * @param string $user_login User's username for logging in * @param string $user_email User's email address to send password and add * @return int|WP_Error Either user's ID or error on failure. */ function register_new_user( $user_login, $user_email ) { $errors = new WP_Error(); $sanitized_user_login = sanitize_user( $user_login ); /** * Filters the email address of a user being registered. * * @since 2.1.0 * * @param string $user_email The email address of the new user. */ $user_email = apply_filters( 'user_registration_email', $user_email ); // Check the username. if ( '' === $sanitized_user_login ) { $errors->add( 'empty_username', __( '<strong>Error:</strong> Please enter a username.' ) ); } elseif ( ! validate_username( $user_login ) ) { $errors->add( 'invalid_username', __( '<strong>Error:</strong> This username is invalid because it uses illegal characters. Please enter a valid username.' ) ); $sanitized_user_login = ''; } elseif ( username_exists( $sanitized_user_login ) ) { $errors->add( 'username_exists', __( '<strong>Error:</strong> This username is already registered. Please choose another one.' ) ); } else { /** This filter is documented in wp-includes/user.php */ $illegal_user_logins = (array) apply_filters( 'illegal_user_logins', array() ); if ( in_array( strtolower( $sanitized_user_login ), array_map( 'strtolower', $illegal_user_logins ), true ) ) { $errors->add( 'invalid_username', __( '<strong>Error:</strong> Sorry, that username is not allowed.' ) ); } } // Check the email address. if ( '' === $user_email ) { $errors->add( 'empty_email', __( '<strong>Error:</strong> Please type your email address.' ) ); } elseif ( ! is_email( $user_email ) ) { $errors->add( 'invalid_email', __( '<strong>Error:</strong> The email address is not correct.' ) ); $user_email = ''; } elseif ( email_exists( $user_email ) ) { $errors->add( 'email_exists', sprintf( /* translators: %s: Link to the login page. */ __( '<strong>Error:</strong> This email address is already registered. <a href="%s">Log in</a> with this address or choose another one.' ), wp_login_url() ) ); } /** * Fires when submitting registration form data, before the user is created. * * @since 2.1.0 * * @param string $sanitized_user_login The submitted username after being sanitized. * @param string $user_email The submitted email. * @param WP_Error $errors Contains any errors with submitted username and email, * e.g., an empty field, an invalid username or email, * or an existing username or email. */ do_action( 'register_post', $sanitized_user_login, $user_email, $errors ); /** * Filters the errors encountered when a new user is being registered. * * The filtered WP_Error object may, for example, contain errors for an invalid * or existing username or email address. A WP_Error object should always be returned, * but may or may not contain errors. * * If any errors are present in $errors, this will abort the user's registration. * * @since 2.1.0 * * @param WP_Error $errors A WP_Error object containing any errors encountered * during registration. * @param string $sanitized_user_login User's username after it has been sanitized. * @param string $user_email User's email. */ $errors = apply_filters( 'registration_errors', $errors, $sanitized_user_login, $user_email ); if ( $errors->has_errors() ) { return $errors; } $user_pass = wp_generate_password( 12, false ); $user_id = wp_create_user( $sanitized_user_login, $user_pass, $user_email ); if ( ! $user_id || is_wp_error( $user_id ) ) { $errors->add( 'registerfail', sprintf( /* translators: %s: Admin email address. */ __( '<strong>Error:</strong> Could not register you… please contact the <a href="mailto:%s">site admin</a>!' ), get_option( 'admin_email' ) ) ); return $errors; } update_user_meta( $user_id, 'default_password_nag', true ); // Set up the password change nag. if ( ! empty( $_COOKIE['wp_lang'] ) ) { $wp_lang = sanitize_text_field( $_COOKIE['wp_lang'] ); if ( in_array( $wp_lang, get_available_languages(), true ) ) { update_user_meta( $user_id, 'locale', $wp_lang ); // Set user locale if defined on registration. } } /** * Fires after a new user registration has been recorded. * * @since 4.4.0 * * @param int $user_id ID of the newly registered user. */ do_action( 'register_new_user', $user_id ); return $user_id; } /** * Initiates email notifications related to the creation of new users. * * Notifications are sent both to the site admin and to the newly created user. * * @since 4.4.0 * @since 4.6.0 Converted the `$notify` parameter to accept 'user' for sending * notifications only to the user created. * * @param int $user_id ID of the newly created user. * @param string $notify Optional. Type of notification that should happen. Accepts 'admin' * or an empty string (admin only), 'user', or 'both' (admin and user). * Default 'both'. */ function wp_send_new_user_notifications( $user_id, $notify = 'both' ) { wp_new_user_notification( $user_id, null, $notify ); } /** * Retrieves the current session token from the logged_in cookie. * * @since 4.0.0 * * @return string Token. */ function wp_get_session_token() { $cookie = wp_parse_auth_cookie( '', 'logged_in' ); return ! empty( $cookie['token'] ) ? $cookie['token'] : ''; } /** * Retrieves a list of sessions for the current user. * * @since 4.0.0 * * @return array Array of sessions. */ function wp_get_all_sessions() { $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); return $manager->get_all(); } /** * Removes the current session token from the database. * * @since 4.0.0 */ function wp_destroy_current_session() { $token = wp_get_session_token(); if ( $token ) { $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); $manager->destroy( $token ); } } /** * Removes all but the current session token for the current user for the database. * * @since 4.0.0 */ function wp_destroy_other_sessions() { $token = wp_get_session_token(); if ( $token ) { $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); $manager->destroy_others( $token ); } } /** * Removes all session tokens for the current user from the database. * * @since 4.0.0 */ function wp_destroy_all_sessions() { $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); $manager->destroy_all(); } /** * Gets the user IDs of all users with no role on this site. * * @since 4.4.0 * @since 4.9.0 The `$site_id` parameter was added to support multisite. * * @global wpdb $wpdb WordPress database abstraction object. * * @param int|null $site_id Optional. The site ID to get users with no role for. Defaults to the current site. * @return string[] Array of user IDs as strings. */ function wp_get_users_with_no_role( $site_id = null ) { global $wpdb; if ( ! $site_id ) { $site_id = get_current_blog_id(); } $prefix = $wpdb->get_blog_prefix( $site_id ); if ( is_multisite() && get_current_blog_id() !== $site_id ) { switch_to_blog( $site_id ); $role_names = wp_roles()->get_names(); restore_current_blog(); } else { $role_names = wp_roles()->get_names(); } $regex = implode( '|', array_keys( $role_names ) ); $regex = preg_replace( '/[^a-zA-Z_\|-]/', '', $regex ); $users = $wpdb->get_col( $wpdb->prepare( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$prefix}capabilities' AND meta_value NOT REGEXP %s", $regex ) ); return $users; } /** * Retrieves the current user object. * * Will set the current user, if the current user is not set. The current user * will be set to the logged-in person. If no user is logged-in, then it will * set the current user to 0, which is invalid and won't have any permissions. * * This function is used by the pluggable functions wp_get_current_user() and * get_currentuserinfo(), the latter of which is deprecated but used for backward * compatibility. * * @since 4.5.0 * @access private * * @see wp_get_current_user() * @global WP_User $current_user Checks if the current user is set. * * @return WP_User Current WP_User instance. */ function _wp_get_current_user() { global $current_user; if ( ! empty( $current_user ) ) { if ( $current_user instanceof WP_User ) { return $current_user; } // Upgrade stdClass to WP_User. if ( is_object( $current_user ) && isset( $current_user->ID ) ) { $cur_id = $current_user->ID; $current_user = null; wp_set_current_user( $cur_id ); return $current_user; } // $current_user has a junk value. Force to WP_User with ID 0. $current_user = null; wp_set_current_user( 0 ); return $current_user; } if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) { wp_set_current_user( 0 ); return $current_user; } /** * Filters the current user. * * The default filters use this to determine the current user from the * request's cookies, if available. * * Returning a value of false will effectively short-circuit setting * the current user. * * @since 3.9.0 * * @param int|false $user_id User ID if one has been determined, false otherwise. */ $user_id = apply_filters( 'determine_current_user', false ); if ( ! $user_id ) { wp_set_current_user( 0 ); return $current_user; } wp_set_current_user( $user_id ); return $current_user; } /** * Sends a confirmation request email when a change of user email address is attempted. * * @since 3.0.0 * @since 4.9.0 This function was moved from wp-admin/includes/ms.php so it's no longer Multisite specific. * * @global WP_Error $errors WP_Error object. */ function send_confirmation_on_profile_email() { global $errors; $current_user = wp_get_current_user(); if ( ! is_object( $errors ) ) { $errors = new WP_Error(); } if ( $current_user->ID !== (int) $_POST['user_id'] ) { return false; } if ( $current_user->user_email !== $_POST['email'] ) { if ( ! is_email( $_POST['email'] ) ) { $errors->add( 'user_email', __( '<strong>Error:</strong> The email address is not correct.' ), array( 'form-field' => 'email', ) ); return; } if ( email_exists( $_POST['email'] ) ) { $errors->add( 'user_email', __( '<strong>Error:</strong> The email address is already used.' ), array( 'form-field' => 'email', ) ); delete_user_meta( $current_user->ID, '_new_email' ); return; } $hash = md5( $_POST['email'] . time() . wp_rand() ); $new_user_email = array( 'hash' => $hash, 'newemail' => $_POST['email'], ); update_user_meta( $current_user->ID, '_new_email', $new_user_email ); $sitename = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); /* translators: Do not translate USERNAME, ADMIN_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */ $email_text = __( 'Howdy ###USERNAME###, You recently requested to have the email address on your account changed. If this is correct, please click on the following link to change it: ###ADMIN_URL### You can safely ignore and delete this email if you do not want to take this action. This email has been sent to ###EMAIL### Regards, All at ###SITENAME### ###SITEURL###' ); /** * Filters the text of the email sent when a change of user email address is attempted. * * The following strings have a special meaning and will get replaced dynamically: * * - `###USERNAME###` The current user's username. * - `###ADMIN_URL###` The link to click on to confirm the email change. * - `###EMAIL###` The new email. * - `###SITENAME###` The name of the site. * - `###SITEURL###` The URL to the site. * * @since MU (3.0.0) * @since 4.9.0 This filter is no longer Multisite specific. * * @param string $email_text Text in the email. * @param array $new_user_email { * Data relating to the new user email address. * * @type string $hash The secure hash used in the confirmation link URL. * @type string $newemail The proposed new email address. * } */ $content = apply_filters( 'new_user_email_content', $email_text, $new_user_email ); $content = str_replace( '###USERNAME###', $current_user->user_login, $content ); $content = str_replace( '###ADMIN_URL###', esc_url( self_admin_url( 'profile.php?newuseremail=' . $hash ) ), $content ); $content = str_replace( '###EMAIL###', $_POST['email'], $content ); $content = str_replace( '###SITENAME###', $sitename, $content ); $content = str_replace( '###SITEURL###', home_url(), $content ); /* translators: New email address notification email subject. %s: Site title. */ wp_mail( $_POST['email'], sprintf( __( '[%s] Email Change Request' ), $sitename ), $content ); $_POST['email'] = $current_user->user_email; } } /** * Adds an admin notice alerting the user to check for confirmation request email * after email address change. * * @since 3.0.0 * @since 4.9.0 This function was moved from wp-admin/includes/ms.php so it's no longer Multisite specific. * * @global string $pagenow The filename of the current screen. */ function new_user_email_admin_notice() { global $pagenow; if ( 'profile.php' === $pagenow && isset( $_GET['updated'] ) ) { $email = get_user_meta( get_current_user_id(), '_new_email', true ); if ( $email ) { $message = sprintf( /* translators: %s: New email address. */ __( 'Your email address has not been updated yet. Please check your inbox at %s for a confirmation email.' ), '<code>' . esc_html( $email['newemail'] ) . '</code>' ); wp_admin_notice( $message, array( 'type' => 'info' ) ); } } } /** * Gets all personal data request types. * * @since 4.9.6 * @access private * * @return string[] List of core privacy action types. */ function _wp_privacy_action_request_types() { return array( 'export_personal_data', 'remove_personal_data', ); } /** * Registers the personal data exporter for users. * * @since 4.9.6 * * @param array[] $exporters An array of personal data exporters. * @return array[] An array of personal data exporters. */ function wp_register_user_personal_data_exporter( $exporters ) { $exporters['wordpress-user'] = array( 'exporter_friendly_name' => __( 'WordPress User' ), 'callback' => 'wp_user_personal_data_exporter', ); return $exporters; } /** * Finds and exports personal data associated with an email address from the user and user_meta table. * * @since 4.9.6 * @since 5.4.0 Added 'Community Events Location' group to the export data. * @since 5.4.0 Added 'Session Tokens' group to the export data. * * @param string $email_address The user's email address. * @return array { * An array of personal data. * * @type array[] $data An array of personal data arrays. * @type bool $done Whether the exporter is finished. * } */ function wp_user_personal_data_exporter( $email_address ) { $email_address = trim( $email_address ); $data_to_export = array(); $user = get_user_by( 'email', $email_address ); if ( ! $user ) { return array( 'data' => array(), 'done' => true, ); } $user_meta = get_user_meta( $user->ID ); $user_props_to_export = array( 'ID' => __( 'User ID' ), 'user_login' => __( 'User Login Name' ), 'user_nicename' => __( 'User Nice Name' ), 'user_email' => __( 'User Email' ), 'user_url' => __( 'User URL' ), 'user_registered' => __( 'User Registration Date' ), 'display_name' => __( 'User Display Name' ), 'nickname' => __( 'User Nickname' ), 'first_name' => __( 'User First Name' ), 'last_name' => __( 'User Last Name' ), 'description' => __( 'User Description' ), ); $user_data_to_export = array(); foreach ( $user_props_to_export as $key => $name ) { $value = ''; switch ( $key ) { case 'ID': case 'user_login': case 'user_nicename': case 'user_email': case 'user_url': case 'user_registered': case 'display_name': $value = $user->data->$key; break; case 'nickname': case 'first_name': case 'last_name': case 'description': $value = $user_meta[ $key ][0]; break; } if ( ! empty( $value ) ) { $user_data_to_export[] = array( 'name' => $name, 'value' => $value, ); } } // Get the list of reserved names. $reserved_names = array_values( $user_props_to_export ); /** * Filters the user's profile data for the privacy exporter. * * @since 5.4.0 * * @param array $additional_user_profile_data { * An array of name-value pairs of additional user data items. Default empty array. * * @type string $name The user-facing name of an item name-value pair,e.g. 'IP Address'. * @type string $value The user-facing value of an item data pair, e.g. '50.60.70.0'. * } * @param WP_User $user The user whose data is being exported. * @param string[] $reserved_names An array of reserved names. Any item in `$additional_user_data` * that uses one of these for its `name` will not be included in the export. */ $_extra_data = apply_filters( 'wp_privacy_additional_user_profile_data', array(), $user, $reserved_names ); if ( is_array( $_extra_data ) && ! empty( $_extra_data ) ) { // Remove items that use reserved names. $extra_data = array_filter( $_extra_data, static function ( $item ) use ( $reserved_names ) { return ! in_array( $item['name'], $reserved_names, true ); } ); if ( count( $extra_data ) !== count( $_extra_data ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: %s: wp_privacy_additional_user_profile_data */ __( 'Filter %s returned items with reserved names.' ), '<code>wp_privacy_additional_user_profile_data</code>' ), '5.4.0' ); } if ( ! empty( $extra_data ) ) { $user_data_to_export = array_merge( $user_data_to_export, $extra_data ); } } $data_to_export[] = array( 'group_id' => 'user', 'group_label' => __( 'User' ), 'group_description' => __( 'User’s profile data.' ), 'item_id' => "user-{$user->ID}", 'data' => $user_data_to_export, ); if ( isset( $user_meta['community-events-location'] ) ) { $location = maybe_unserialize( $user_meta['community-events-location'][0] ); $location_props_to_export = array( 'description' => __( 'City' ), 'country' => __( 'Country' ), 'latitude' => __( 'Latitude' ), 'longitude' => __( 'Longitude' ), 'ip' => __( 'IP' ), ); $location_data_to_export = array(); foreach ( $location_props_to_export as $key => $name ) { if ( ! empty( $location[ $key ] ) ) { $location_data_to_export[] = array( 'name' => $name, 'value' => $location[ $key ], ); } } $data_to_export[] = array( 'group_id' => 'community-events-location', 'group_label' => __( 'Community Events Location' ), 'group_description' => __( 'User’s location data used for the Community Events in the WordPress Events and News dashboard widget.' ), 'item_id' => "community-events-location-{$user->ID}", 'data' => $location_data_to_export, ); } if ( isset( $user_meta['session_tokens'] ) ) { $session_tokens = maybe_unserialize( $user_meta['session_tokens'][0] ); $session_tokens_props_to_export = array( 'expiration' => __( 'Expiration' ), 'ip' => __( 'IP' ), 'ua' => __( 'User Agent' ), 'login' => __( 'Last Login' ), ); foreach ( $session_tokens as $token_key => $session_token ) { $session_tokens_data_to_export = array(); foreach ( $session_tokens_props_to_export as $key => $name ) { if ( ! empty( $session_token[ $key ] ) ) { $value = $session_token[ $key ]; if ( in_array( $key, array( 'expiration', 'login' ), true ) ) { $value = date_i18n( 'F d, Y H:i A', $value ); } $session_tokens_data_to_export[] = array( 'name' => $name, 'value' => $value, ); } } $data_to_export[] = array( 'group_id' => 'session-tokens', 'group_label' => __( 'Session Tokens' ), 'group_description' => __( 'User’s Session Tokens data.' ), 'item_id' => "session-tokens-{$user->ID}-{$token_key}", 'data' => $session_tokens_data_to_export, ); } } return array( 'data' => $data_to_export, 'done' => true, ); } /** * Updates log when privacy request is confirmed. * * @since 4.9.6 * @access private * * @param int $request_id ID of the request. */ function _wp_privacy_account_request_confirmed( $request_id ) { $request = wp_get_user_request( $request_id ); if ( ! $request ) { return; } if ( ! in_array( $request->status, array( 'request-pending', 'request-failed' ), true ) ) { return; } update_post_meta( $request_id, '_wp_user_request_confirmed_timestamp', time() ); wp_update_post( array( 'ID' => $request_id, 'post_status' => 'request-confirmed', ) ); } /** * Notifies the site administrator via email when a request is confirmed. * * Without this, the admin would have to manually check the site to see if any * action was needed on their part yet. * * @since 4.9.6 * * @param int $request_id The ID of the request. */ function _wp_privacy_send_request_confirmation_notification( $request_id ) { $request = wp_get_user_request( $request_id ); if ( ! ( $request instanceof WP_User_Request ) || 'request-confirmed' !== $request->status ) { return; } $already_notified = (bool) get_post_meta( $request_id, '_wp_admin_notified', true ); if ( $already_notified ) { return; } if ( 'export_personal_data' === $request->action_name ) { $manage_url = admin_url( 'export-personal-data.php' ); } elseif ( 'remove_personal_data' === $request->action_name ) { $manage_url = admin_url( 'erase-personal-data.php' ); } $action_description = wp_user_request_action_description( $request->action_name ); /** * Filters the recipient of the data request confirmation notification. * * In a Multisite environment, this will default to the email address of the * network admin because, by default, single site admins do not have the * capabilities required to process requests. Some networks may wish to * delegate those capabilities to a single-site admin, or a dedicated person * responsible for managing privacy requests. * * @since 4.9.6 * * @param string $admin_email The email address of the notification recipient. * @param WP_User_Request $request The request that is initiating the notification. */ $admin_email = apply_filters( 'user_request_confirmed_email_to', get_site_option( 'admin_email' ), $request ); $email_data = array( 'request' => $request, 'user_email' => $request->email, 'description' => $action_description, 'manage_url' => $manage_url, 'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), 'siteurl' => home_url(), 'admin_email' => $admin_email, ); $subject = sprintf( /* translators: Privacy data request confirmed notification email subject. 1: Site title, 2: Name of the confirmed action. */ __( '[%1$s] Action Confirmed: %2$s' ), $email_data['sitename'], $action_description ); /** * Filters the subject of the user request confirmation email. * * @since 4.9.8 * * @param string $subject The email subject. * @param string $sitename The name of the site. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $user_email The email address confirming a request. * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $manage_url The link to click manage privacy requests of this type. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * @type string $admin_email The administrator email receiving the mail. * } */ $subject = apply_filters( 'user_request_confirmed_email_subject', $subject, $email_data['sitename'], $email_data ); /* translators: Do not translate SITENAME, USER_EMAIL, DESCRIPTION, MANAGE_URL, SITEURL; those are placeholders. */ $content = __( 'Howdy, A user data privacy request has been confirmed on ###SITENAME###: User: ###USER_EMAIL### Request: ###DESCRIPTION### You can view and manage these data privacy requests here: ###MANAGE_URL### Regards, All at ###SITENAME### ###SITEURL###' ); /** * Filters the body of the user request confirmation email. * * The email is sent to an administrator when a user request is confirmed. * * The following strings have a special meaning and will get replaced dynamically: * * - `###SITENAME###` The name of the site. * - `###USER_EMAIL###` The user email for the request. * - `###DESCRIPTION###` Description of the action being performed so the user knows what the email is for. * - `###MANAGE_URL###` The URL to manage requests. * - `###SITEURL###` The URL to the site. * * @since 4.9.6 * @deprecated 5.8.0 Use {@see 'user_request_confirmed_email_content'} instead. * For user erasure fulfillment email content * use {@see 'user_erasure_fulfillment_email_content'} instead. * * @param string $content The email content. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $user_email The email address confirming a request. * @type string $description Description of the action being performed * so the user knows what the email is for. * @type string $manage_url The link to click manage privacy requests of this type. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * @type string $admin_email The administrator email receiving the mail. * } */ $content = apply_filters_deprecated( 'user_confirmed_action_email_content', array( $content, $email_data ), '5.8.0', sprintf( /* translators: 1 & 2: Deprecation replacement options. */ __( '%1$s or %2$s' ), 'user_request_confirmed_email_content', 'user_erasure_fulfillment_email_content' ) ); /** * Filters the body of the user request confirmation email. * * The email is sent to an administrator when a user request is confirmed. * The following strings have a special meaning and will get replaced dynamically: * * - `###SITENAME###` The name of the site. * - `###USER_EMAIL###` The user email for the request. * - `###DESCRIPTION###` Description of the action being performed so the user knows what the email is for. * - `###MANAGE_URL###` The URL to manage requests. * - `###SITEURL###` The URL to the site. * * @since 5.8.0 * * @param string $content The email content. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $user_email The email address confirming a request. * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $manage_url The link to click manage privacy requests of this type. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * @type string $admin_email The administrator email receiving the mail. * } */ $content = apply_filters( 'user_request_confirmed_email_content', $content, $email_data ); $content = str_replace( '###SITENAME###', $email_data['sitename'], $content ); $content = str_replace( '###USER_EMAIL###', $email_data['user_email'], $content ); $content = str_replace( '###DESCRIPTION###', $email_data['description'], $content ); $content = str_replace( '###MANAGE_URL###', sanitize_url( $email_data['manage_url'] ), $content ); $content = str_replace( '###SITEURL###', sanitize_url( $email_data['siteurl'] ), $content ); $headers = ''; /** * Filters the headers of the user request confirmation email. * * @since 5.4.0 * * @param string|array $headers The email headers. * @param string $subject The email subject. * @param string $content The email content. * @param int $request_id The request ID. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $user_email The email address confirming a request. * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $manage_url The link to click manage privacy requests of this type. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * @type string $admin_email The administrator email receiving the mail. * } */ $headers = apply_filters( 'user_request_confirmed_email_headers', $headers, $subject, $content, $request_id, $email_data ); $email_sent = wp_mail( $email_data['admin_email'], $subject, $content, $headers ); if ( $email_sent ) { update_post_meta( $request_id, '_wp_admin_notified', true ); } } /** * Notifies the user when their erasure request is fulfilled. * * Without this, the user would never know if their data was actually erased. * * @since 4.9.6 * * @param int $request_id The privacy request post ID associated with this request. */ function _wp_privacy_send_erasure_fulfillment_notification( $request_id ) { $request = wp_get_user_request( $request_id ); if ( ! ( $request instanceof WP_User_Request ) || 'request-completed' !== $request->status ) { return; } $already_notified = (bool) get_post_meta( $request_id, '_wp_user_notified', true ); if ( $already_notified ) { return; } // Localize message content for user; fallback to site default for visitors. if ( ! empty( $request->user_id ) ) { $switched_locale = switch_to_user_locale( $request->user_id ); } else { $switched_locale = switch_to_locale( get_locale() ); } /** * Filters the recipient of the data erasure fulfillment notification. * * @since 4.9.6 * * @param string $user_email The email address of the notification recipient. * @param WP_User_Request $request The request that is initiating the notification. */ $user_email = apply_filters( 'user_erasure_fulfillment_email_to', $request->email, $request ); $email_data = array( 'request' => $request, 'message_recipient' => $user_email, 'privacy_policy_url' => get_privacy_policy_url(), 'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), 'siteurl' => home_url(), ); $subject = sprintf( /* translators: Erasure request fulfilled notification email subject. %s: Site title. */ __( '[%s] Erasure Request Fulfilled' ), $email_data['sitename'] ); /** * Filters the subject of the email sent when an erasure request is completed. * * @since 4.9.8 * @deprecated 5.8.0 Use {@see 'user_erasure_fulfillment_email_subject'} instead. * * @param string $subject The email subject. * @param string $sitename The name of the site. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $message_recipient The address that the email will be sent to. Defaults * to the value of `$request->email`, but can be changed * by the `user_erasure_fulfillment_email_to` filter. * @type string $privacy_policy_url Privacy policy URL. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $subject = apply_filters_deprecated( 'user_erasure_complete_email_subject', array( $subject, $email_data['sitename'], $email_data ), '5.8.0', 'user_erasure_fulfillment_email_subject' ); /** * Filters the subject of the email sent when an erasure request is completed. * * @since 5.8.0 * * @param string $subject The email subject. * @param string $sitename The name of the site. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $message_recipient The address that the email will be sent to. Defaults * to the value of `$request->email`, but can be changed * by the `user_erasure_fulfillment_email_to` filter. * @type string $privacy_policy_url Privacy policy URL. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $subject = apply_filters( 'user_erasure_fulfillment_email_subject', $subject, $email_data['sitename'], $email_data ); /* translators: Do not translate SITENAME, SITEURL; those are placeholders. */ $content = __( 'Howdy, Your request to erase your personal data on ###SITENAME### has been completed. If you have any follow-up questions or concerns, please contact the site administrator. Regards, All at ###SITENAME### ###SITEURL###' ); if ( ! empty( $email_data['privacy_policy_url'] ) ) { /* translators: Do not translate SITENAME, SITEURL, PRIVACY_POLICY_URL; those are placeholders. */ $content = __( 'Howdy, Your request to erase your personal data on ###SITENAME### has been completed. If you have any follow-up questions or concerns, please contact the site administrator. For more information, you can also read our privacy policy: ###PRIVACY_POLICY_URL### Regards, All at ###SITENAME### ###SITEURL###' ); } /** * Filters the body of the data erasure fulfillment notification. * * The email is sent to a user when their data erasure request is fulfilled * by an administrator. * * The following strings have a special meaning and will get replaced dynamically: * * - `###SITENAME###` The name of the site. * - `###PRIVACY_POLICY_URL###` Privacy policy page URL. * - `###SITEURL###` The URL to the site. * * @since 4.9.6 * @deprecated 5.8.0 Use {@see 'user_erasure_fulfillment_email_content'} instead. * For user request confirmation email content * use {@see 'user_request_confirmed_email_content'} instead. * * @param string $content The email content. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $message_recipient The address that the email will be sent to. Defaults * to the value of `$request->email`, but can be changed * by the `user_erasure_fulfillment_email_to` filter. * @type string $privacy_policy_url Privacy policy URL. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $content = apply_filters_deprecated( 'user_confirmed_action_email_content', array( $content, $email_data ), '5.8.0', sprintf( /* translators: 1 & 2: Deprecation replacement options. */ __( '%1$s or %2$s' ), 'user_erasure_fulfillment_email_content', 'user_request_confirmed_email_content' ) ); /** * Filters the body of the data erasure fulfillment notification. * * The email is sent to a user when their data erasure request is fulfilled * by an administrator. * * The following strings have a special meaning and will get replaced dynamically: * * - `###SITENAME###` The name of the site. * - `###PRIVACY_POLICY_URL###` Privacy policy page URL. * - `###SITEURL###` The URL to the site. * * @since 5.8.0 * * @param string $content The email content. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $message_recipient The address that the email will be sent to. Defaults * to the value of `$request->email`, but can be changed * by the `user_erasure_fulfillment_email_to` filter. * @type string $privacy_policy_url Privacy policy URL. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $content = apply_filters( 'user_erasure_fulfillment_email_content', $content, $email_data ); $content = str_replace( '###SITENAME###', $email_data['sitename'], $content ); $content = str_replace( '###PRIVACY_POLICY_URL###', $email_data['privacy_policy_url'], $content ); $content = str_replace( '###SITEURL###', sanitize_url( $email_data['siteurl'] ), $content ); $headers = ''; /** * Filters the headers of the data erasure fulfillment notification. * * @since 5.4.0 * @deprecated 5.8.0 Use {@see 'user_erasure_fulfillment_email_headers'} instead. * * @param string|array $headers The email headers. * @param string $subject The email subject. * @param string $content The email content. * @param int $request_id The request ID. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $message_recipient The address that the email will be sent to. Defaults * to the value of `$request->email`, but can be changed * by the `user_erasure_fulfillment_email_to` filter. * @type string $privacy_policy_url Privacy policy URL. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $headers = apply_filters_deprecated( 'user_erasure_complete_email_headers', array( $headers, $subject, $content, $request_id, $email_data ), '5.8.0', 'user_erasure_fulfillment_email_headers' ); /** * Filters the headers of the data erasure fulfillment notification. * * @since 5.8.0 * * @param string|array $headers The email headers. * @param string $subject The email subject. * @param string $content The email content. * @param int $request_id The request ID. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $message_recipient The address that the email will be sent to. Defaults * to the value of `$request->email`, but can be changed * by the `user_erasure_fulfillment_email_to` filter. * @type string $privacy_policy_url Privacy policy URL. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $headers = apply_filters( 'user_erasure_fulfillment_email_headers', $headers, $subject, $content, $request_id, $email_data ); $email_sent = wp_mail( $user_email, $subject, $content, $headers ); if ( $switched_locale ) { restore_previous_locale(); } if ( $email_sent ) { update_post_meta( $request_id, '_wp_user_notified', true ); } } /** * Returns request confirmation message HTML. * * @since 4.9.6 * @access private * * @param int $request_id The request ID being confirmed. * @return string The confirmation message. */ function _wp_privacy_account_request_confirmed_message( $request_id ) { $request = wp_get_user_request( $request_id ); $message = '<p class="success">' . __( 'Action has been confirmed.' ) . '</p>'; $message .= '<p>' . __( 'The site administrator has been notified and will fulfill your request as soon as possible.' ) . '</p>'; if ( $request && in_array( $request->action_name, _wp_privacy_action_request_types(), true ) ) { if ( 'export_personal_data' === $request->action_name ) { $message = '<p class="success">' . __( 'Thanks for confirming your export request.' ) . '</p>'; $message .= '<p>' . __( 'The site administrator has been notified. You will receive a link to download your export via email when they fulfill your request.' ) . '</p>'; } elseif ( 'remove_personal_data' === $request->action_name ) { $message = '<p class="success">' . __( 'Thanks for confirming your erasure request.' ) . '</p>'; $message .= '<p>' . __( 'The site administrator has been notified. You will receive an email confirmation when they erase your data.' ) . '</p>'; } } /** * Filters the message displayed to a user when they confirm a data request. * * @since 4.9.6 * * @param string $message The message to the user. * @param int $request_id The ID of the request being confirmed. */ $message = apply_filters( 'user_request_action_confirmed_message', $message, $request_id ); return $message; } /** * Creates and logs a user request to perform a specific action. * * Requests are stored inside a post type named `user_request` since they can apply to both * users on the site, or guests without a user account. * * @since 4.9.6 * @since 5.7.0 Added the `$status` parameter. * * @param string $email_address User email address. This can be the address of a registered * or non-registered user. * @param string $action_name Name of the action that is being confirmed. Required. * @param array $request_data Misc data you want to send with the verification request and pass * to the actions once the request is confirmed. * @param string $status Optional request status (pending or confirmed). Default 'pending'. * @return int|WP_Error Returns the request ID if successful, or a WP_Error object on failure. */ function wp_create_user_request( $email_address = '', $action_name = '', $request_data = array(), $status = 'pending' ) { $email_address = sanitize_email( $email_address ); $action_name = sanitize_key( $action_name ); if ( ! is_email( $email_address ) ) { return new WP_Error( 'invalid_email', __( 'Invalid email address.' ) ); } if ( ! in_array( $action_name, _wp_privacy_action_request_types(), true ) ) { return new WP_Error( 'invalid_action', __( 'Invalid action name.' ) ); } if ( ! in_array( $status, array( 'pending', 'confirmed' ), true ) ) { return new WP_Error( 'invalid_status', __( 'Invalid request status.' ) ); } $user = get_user_by( 'email', $email_address ); $user_id = $user && ! is_wp_error( $user ) ? $user->ID : 0; // Check for duplicates. $requests_query = new WP_Query( array( 'post_type' => 'user_request', 'post_name__in' => array( $action_name ), // Action name stored in post_name column. 'title' => $email_address, // Email address stored in post_title column. 'post_status' => array( 'request-pending', 'request-confirmed', ), 'fields' => 'ids', ) ); if ( $requests_query->found_posts ) { return new WP_Error( 'duplicate_request', __( 'An incomplete personal data request for this email address already exists.' ) ); } $request_id = wp_insert_post( array( 'post_author' => $user_id, 'post_name' => $action_name, 'post_title' => $email_address, 'post_content' => wp_json_encode( $request_data ), 'post_status' => 'request-' . $status, 'post_type' => 'user_request', 'post_date' => current_time( 'mysql', false ), 'post_date_gmt' => current_time( 'mysql', true ), ), true ); return $request_id; } /** * Gets action description from the name and return a string. * * @since 4.9.6 * * @param string $action_name Action name of the request. * @return string Human readable action name. */ function wp_user_request_action_description( $action_name ) { switch ( $action_name ) { case 'export_personal_data': $description = __( 'Export Personal Data' ); break; case 'remove_personal_data': $description = __( 'Erase Personal Data' ); break; default: /* translators: %s: Action name. */ $description = sprintf( __( 'Confirm the "%s" action' ), $action_name ); break; } /** * Filters the user action description. * * @since 4.9.6 * * @param string $description The default description. * @param string $action_name The name of the request. */ return apply_filters( 'user_request_action_description', $description, $action_name ); } /** * Send a confirmation request email to confirm an action. * * If the request is not already pending, it will be updated. * * @since 4.9.6 * * @param int $request_id ID of the request created via wp_create_user_request(). * @return true|WP_Error True on success, `WP_Error` on failure. */ function wp_send_user_request( $request_id ) { $request_id = absint( $request_id ); $request = wp_get_user_request( $request_id ); if ( ! $request ) { return new WP_Error( 'invalid_request', __( 'Invalid personal data request.' ) ); } // Localize message content for user; fallback to site default for visitors. if ( ! empty( $request->user_id ) ) { $switched_locale = switch_to_user_locale( $request->user_id ); } else { $switched_locale = switch_to_locale( get_locale() ); } /* * Generate the new user request key first, as it is used by both the $request * object and the confirm_url array. * See https://core.trac.wordpress.org/ticket/44940 */ $request->confirm_key = wp_generate_user_request_key( $request_id ); $email_data = array( 'request' => $request, 'email' => $request->email, 'description' => wp_user_request_action_description( $request->action_name ), 'confirm_url' => add_query_arg( array( 'action' => 'confirmaction', 'request_id' => $request_id, 'confirm_key' => $request->confirm_key, ), wp_login_url() ), 'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), 'siteurl' => home_url(), ); /* translators: Confirm privacy data request notification email subject. 1: Site title, 2: Name of the action. */ $subject = sprintf( __( '[%1$s] Confirm Action: %2$s' ), $email_data['sitename'], $email_data['description'] ); /** * Filters the subject of the email sent when an account action is attempted. * * @since 4.9.6 * * @param string $subject The email subject. * @param string $sitename The name of the site. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $email The email address this is being sent to. * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $confirm_url The link to click on to confirm the account action. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $subject = apply_filters( 'user_request_action_email_subject', $subject, $email_data['sitename'], $email_data ); /* translators: Do not translate DESCRIPTION, CONFIRM_URL, SITENAME, SITEURL: those are placeholders. */ $content = __( 'Howdy, A request has been made to perform the following action on your account: ###DESCRIPTION### To confirm this, please click on the following link: ###CONFIRM_URL### You can safely ignore and delete this email if you do not want to take this action. Regards, All at ###SITENAME### ###SITEURL###' ); /** * Filters the text of the email sent when an account action is attempted. * * The following strings have a special meaning and will get replaced dynamically: * * - `###DESCRIPTION###` Description of the action being performed so the user knows what the email is for. * - `###CONFIRM_URL###` The link to click on to confirm the account action. * - `###SITENAME###` The name of the site. * - `###SITEURL###` The URL to the site. * * @since 4.9.6 * * @param string $content Text in the email. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $email The email address this is being sent to. * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $confirm_url The link to click on to confirm the account action. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $content = apply_filters( 'user_request_action_email_content', $content, $email_data ); $content = str_replace( '###DESCRIPTION###', $email_data['description'], $content ); $content = str_replace( '###CONFIRM_URL###', sanitize_url( $email_data['confirm_url'] ), $content ); $content = str_replace( '###EMAIL###', $email_data['email'], $content ); $content = str_replace( '###SITENAME###', $email_data['sitename'], $content ); $content = str_replace( '###SITEURL###', sanitize_url( $email_data['siteurl'] ), $content ); $headers = ''; /** * Filters the headers of the email sent when an account action is attempted. * * @since 5.4.0 * * @param string|array $headers The email headers. * @param string $subject The email subject. * @param string $content The email content. * @param int $request_id The request ID. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $email The email address this is being sent to. * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $confirm_url The link to click on to confirm the account action. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $headers = apply_filters( 'user_request_action_email_headers', $headers, $subject, $content, $request_id, $email_data ); $email_sent = wp_mail( $email_data['email'], $subject, $content, $headers ); if ( $switched_locale ) { restore_previous_locale(); } if ( ! $email_sent ) { return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export confirmation email.' ) ); } return true; } /** * Returns a confirmation key for a user action and stores the hashed version for future comparison. * * @since 4.9.6 * * @param int $request_id Request ID. * @return string Confirmation key. */ function wp_generate_user_request_key( $request_id ) { // Generate something random for a confirmation key. $key = wp_generate_password( 20, false ); // Save the key, hashed. wp_update_post( array( 'ID' => $request_id, 'post_status' => 'request-pending', 'post_password' => wp_fast_hash( $key ), ) ); return $key; } /** * Validates a user request by comparing the key with the request's key. * * @since 4.9.6 * * @param int $request_id ID of the request being confirmed. * @param string $key Provided key to validate. * @return true|WP_Error True on success, WP_Error on failure. */ function wp_validate_user_request_key( $request_id, #[\SensitiveParameter] $key ) { $request_id = absint( $request_id ); $request = wp_get_user_request( $request_id ); $saved_key = $request->confirm_key; $key_request_time = $request->modified_timestamp; if ( ! $request || ! $saved_key || ! $key_request_time ) { return new WP_Error( 'invalid_request', __( 'Invalid personal data request.' ) ); } if ( ! in_array( $request->status, array( 'request-pending', 'request-failed' ), true ) ) { return new WP_Error( 'expired_request', __( 'This personal data request has expired.' ) ); } if ( empty( $key ) ) { return new WP_Error( 'missing_key', __( 'The confirmation key is missing from this personal data request.' ) ); } /** * Filters the expiration time of confirm keys. * * @since 4.9.6 * * @param int $expiration The expiration time in seconds. */ $expiration_duration = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS ); $expiration_time = $key_request_time + $expiration_duration; if ( ! wp_verify_fast_hash( $key, $saved_key ) ) { return new WP_Error( 'invalid_key', __( 'The confirmation key is invalid for this personal data request.' ) ); } if ( ! $expiration_time || time() > $expiration_time ) { return new WP_Error( 'expired_key', __( 'The confirmation key has expired for this personal data request.' ) ); } return true; } /** * Returns the user request object for the specified request ID. * * @since 4.9.6 * * @param int $request_id The ID of the user request. * @return WP_User_Request|false */ function wp_get_user_request( $request_id ) { $request_id = absint( $request_id ); $post = get_post( $request_id ); if ( ! $post || 'user_request' !== $post->post_type ) { return false; } return new WP_User_Request( $post ); } /** * Checks if Application Passwords is supported. * * Application Passwords is supported only by sites using SSL or local environments * but may be made available using the {@see 'wp_is_application_passwords_available'} filter. * * @since 5.9.0 * * @return bool */ function wp_is_application_passwords_supported() { return is_ssl() || 'local' === wp_get_environment_type(); } /** * Checks if Application Passwords is globally available. * * By default, Application Passwords is available to all sites using SSL or to local environments. * Use the {@see 'wp_is_application_passwords_available'} filter to adjust its availability. * * @since 5.6.0 * * @return bool */ function wp_is_application_passwords_available() { /** * Filters whether Application Passwords is available. * * @since 5.6.0 * * @param bool $available True if available, false otherwise. */ return apply_filters( 'wp_is_application_passwords_available', wp_is_application_passwords_supported() ); } /** * Checks if Application Passwords is available for a specific user. * * By default all users can use Application Passwords. Use {@see 'wp_is_application_passwords_available_for_user'} * to restrict availability to certain users. * * @since 5.6.0 * * @param int|WP_User $user The user to check. * @return bool */ function wp_is_application_passwords_available_for_user( $user ) { if ( ! wp_is_application_passwords_available() ) { return false; } if ( ! is_object( $user ) ) { $user = get_userdata( $user ); } if ( ! $user || ! $user->exists() ) { return false; } /** * Filters whether Application Passwords is available for a specific user. * * @since 5.6.0 * * @param bool $available True if available, false otherwise. * @param WP_User $user The user to check. */ return apply_filters( 'wp_is_application_passwords_available_for_user', true, $user ); } /** * Registers the user meta property for persisted preferences. * * This property is used to store user preferences across page reloads and is * currently used by the block editor for preferences like 'fullscreenMode' and * 'fixedToolbar'. * * @since 6.1.0 * @access private * * @global wpdb $wpdb WordPress database abstraction object. */ function wp_register_persisted_preferences_meta() { /* * Create a meta key that incorporates the blog prefix so that each site * on a multisite can have distinct user preferences. */ global $wpdb; $meta_key = $wpdb->get_blog_prefix() . 'persisted_preferences'; register_meta( 'user', $meta_key, array( 'type' => 'object', 'single' => true, 'show_in_rest' => array( 'name' => 'persisted_preferences', 'type' => 'object', 'schema' => array( 'type' => 'object', 'context' => array( 'edit' ), 'properties' => array( '_modified' => array( 'description' => __( 'The date and time the preferences were updated.' ), 'type' => 'string', 'format' => 'date-time', 'readonly' => false, ), ), 'additionalProperties' => true, ), ), ) ); } /** * Sets the last changed time for the 'users' cache group. * * @since 6.3.0 */ function wp_cache_set_users_last_changed() { wp_cache_set_last_changed( 'users' ); } /** * Checks if password reset is allowed for a specific user. * * @since 6.3.0 * * @param int|WP_User $user The user to check. * @return bool|WP_Error True if allowed, false or WP_Error otherwise. */ function wp_is_password_reset_allowed_for_user( $user ) { if ( ! is_object( $user ) ) { $user = get_userdata( $user ); } if ( ! $user || ! $user->exists() ) { return false; } $allow = true; if ( is_multisite() && is_user_spammy( $user ) ) { $allow = false; } /** * Filters whether to allow a password to be reset. * * @since 2.7.0 * * @param bool $allow Whether to allow the password to be reset. Default true. * @param int $user_id The ID of the user attempting to reset a password. */ return apply_filters( 'allow_password_reset', $allow, $user->ID ); } https-detection-20260605120939.php 0000666 00000013341 15226762357 0012012 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } .customize.php 0000666 00000000000 15226762357 0007366 0 ustar 00 class-walker-comment.php 0000666 00000033615 15226762357 0011340 0 ustar 00 <?php /** * Comment API: Walker_Comment class * * @package WordPress * @subpackage Comments * @since 4.4.0 */ /** * Core walker class used to create an HTML list of comments. * * @since 2.7.0 * * @see Walker */ class Walker_Comment extends Walker { /** * What the class handles. * * @since 2.7.0 * @var string * * @see Walker::$tree_type */ public $tree_type = 'comment'; /** * Database fields to use. * * @since 2.7.0 * @var string[] * * @see Walker::$db_fields * @todo Decouple this */ public $db_fields = array( 'parent' => 'comment_parent', 'id' => 'comment_ID', ); /** * Starts the list before the elements are added. * * @since 2.7.0 * * @see Walker::start_lvl() * @global int $comment_depth * * @param string $output Used to append additional content (passed by reference). * @param int $depth Optional. Depth of the current comment. Default 0. * @param array $args Optional. Uses 'style' argument for type of HTML list. Default empty array. */ public function start_lvl( &$output, $depth = 0, $args = array() ) { $GLOBALS['comment_depth'] = $depth + 1; switch ( $args['style'] ) { case 'div': break; case 'ol': $output .= '<ol class="children">' . "\n"; break; case 'ul': default: $output .= '<ul class="children">' . "\n"; break; } } /** * Ends the list of items after the elements are added. * * @since 2.7.0 * * @see Walker::end_lvl() * @global int $comment_depth * * @param string $output Used to append additional content (passed by reference). * @param int $depth Optional. Depth of the current comment. Default 0. * @param array $args Optional. Will only append content if style argument value is 'ol' or 'ul'. * Default empty array. */ public function end_lvl( &$output, $depth = 0, $args = array() ) { $GLOBALS['comment_depth'] = $depth + 1; switch ( $args['style'] ) { case 'div': break; case 'ol': $output .= "</ol><!-- .children -->\n"; break; case 'ul': default: $output .= "</ul><!-- .children -->\n"; break; } } /** * Traverses elements to create list from elements. * * This function is designed to enhance Walker::display_element() to * display children of higher nesting levels than selected inline on * the highest depth level displayed. This prevents them being orphaned * at the end of the comment list. * * Example: max_depth = 2, with 5 levels of nested content. * 1 * 1.1 * 1.1.1 * 1.1.1.1 * 1.1.1.1.1 * 1.1.2 * 1.1.2.1 * 2 * 2.2 * * @since 2.7.0 * * @see Walker::display_element() * @see wp_list_comments() * * @param WP_Comment $element Comment data object. * @param array $children_elements List of elements to continue traversing. Passed by reference. * @param int $max_depth Max depth to traverse. * @param int $depth Depth of the current element. * @param array $args An array of arguments. * @param string $output Used to append additional content. Passed by reference. */ public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) { if ( ! $element ) { return; } $id_field = $this->db_fields['id']; $id = $element->$id_field; parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output ); /* * If at the max depth, and the current element still has children, loop over those * and display them at this level. This is to prevent them being orphaned to the end * of the list. */ if ( $max_depth <= $depth + 1 && isset( $children_elements[ $id ] ) ) { foreach ( $children_elements[ $id ] as $child ) { $this->display_element( $child, $children_elements, $max_depth, $depth, $args, $output ); } unset( $children_elements[ $id ] ); } } /** * Starts the element output. * * @since 2.7.0 * @since 5.9.0 Renamed `$comment` to `$data_object` and `$id` to `$current_object_id` * to match parent class for PHP 8 named parameter support. * * @see Walker::start_el() * @see wp_list_comments() * @global int $comment_depth * @global WP_Comment $comment Global comment object. * * @param string $output Used to append additional content. Passed by reference. * @param WP_Comment $data_object Comment data object. * @param int $depth Optional. Depth of the current comment in reference to parents. Default 0. * @param array $args Optional. An array of arguments. Default empty array. * @param int $current_object_id Optional. ID of the current comment. Default 0. */ public function start_el( &$output, $data_object, $depth = 0, $args = array(), $current_object_id = 0 ) { // Restores the more descriptive, specific name for use within this method. $comment = $data_object; ++$depth; $GLOBALS['comment_depth'] = $depth; $GLOBALS['comment'] = $comment; if ( ! empty( $args['callback'] ) ) { ob_start(); call_user_func( $args['callback'], $comment, $args, $depth ); $output .= ob_get_clean(); return; } if ( 'comment' === $comment->comment_type ) { add_filter( 'comment_text', array( $this, 'filter_comment_text' ), 40, 2 ); } if ( ( 'pingback' === $comment->comment_type || 'trackback' === $comment->comment_type ) && $args['short_ping'] ) { ob_start(); $this->ping( $comment, $depth, $args ); $output .= ob_get_clean(); } elseif ( 'html5' === $args['format'] ) { ob_start(); $this->html5_comment( $comment, $depth, $args ); $output .= ob_get_clean(); } else { ob_start(); $this->comment( $comment, $depth, $args ); $output .= ob_get_clean(); } if ( 'comment' === $comment->comment_type ) { remove_filter( 'comment_text', array( $this, 'filter_comment_text' ), 40 ); } } /** * Ends the element output, if needed. * * @since 2.7.0 * @since 5.9.0 Renamed `$comment` to `$data_object` to match parent class for PHP 8 named parameter support. * * @see Walker::end_el() * @see wp_list_comments() * * @param string $output Used to append additional content. Passed by reference. * @param WP_Comment $data_object Comment data object. * @param int $depth Optional. Depth of the current comment. Default 0. * @param array $args Optional. An array of arguments. Default empty array. */ public function end_el( &$output, $data_object, $depth = 0, $args = array() ) { if ( ! empty( $args['end-callback'] ) ) { ob_start(); call_user_func( $args['end-callback'], $data_object, // The current comment object. $args, $depth ); $output .= ob_get_clean(); return; } if ( 'div' === $args['style'] ) { $output .= "</div><!-- #comment-## -->\n"; } else { $output .= "</li><!-- #comment-## -->\n"; } } /** * Outputs a pingback comment. * * @since 3.6.0 * * @see wp_list_comments() * * @param WP_Comment $comment The comment object. * @param int $depth Depth of the current comment. * @param array $args An array of arguments. */ protected function ping( $comment, $depth, $args ) { $tag = ( 'div' === $args['style'] ) ? 'div' : 'li'; ?> <<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( '', $comment ); ?>> <div class="comment-body"> <?php _e( 'Pingback:' ); ?> <?php comment_author_link( $comment ); ?> <?php edit_comment_link( __( 'Edit' ), '<span class="edit-link">', '</span>' ); ?> </div> <?php } /** * Filters the comment text. * * Removes links from the pending comment's text if the commenter did not consent * to the comment cookies. * * @since 5.4.2 * * @param string $comment_text Text of the current comment. * @param WP_Comment|null $comment The comment object. Null if not found. * @return string Filtered text of the current comment. */ public function filter_comment_text( $comment_text, $comment ) { $commenter = wp_get_current_commenter(); $show_pending_links = ! empty( $commenter['comment_author'] ); if ( $comment && '0' === $comment->comment_approved && ! $show_pending_links ) { $comment_text = wp_kses( $comment_text, array() ); } return $comment_text; } /** * Outputs a single comment. * * @since 3.6.0 * * @see wp_list_comments() * * @param WP_Comment $comment Comment to display. * @param int $depth Depth of the current comment. * @param array $args An array of arguments. */ protected function comment( $comment, $depth, $args ) { if ( 'div' === $args['style'] ) { $tag = 'div'; $add_below = 'comment'; } else { $tag = 'li'; $add_below = 'div-comment'; } $commenter = wp_get_current_commenter(); $show_pending_links = isset( $commenter['comment_author'] ) && $commenter['comment_author']; if ( $commenter['comment_author_email'] ) { $moderation_note = __( 'Your comment is awaiting moderation.' ); } else { $moderation_note = __( 'Your comment is awaiting moderation. This is a preview; your comment will be visible after it has been approved.' ); } ?> <<?php echo $tag; ?> <?php comment_class( $this->has_children ? 'parent' : '', $comment ); ?> id="comment-<?php comment_ID(); ?>"> <?php if ( 'div' !== $args['style'] ) : ?> <div id="div-comment-<?php comment_ID(); ?>" class="comment-body"> <?php endif; ?> <div class="comment-author vcard"> <?php if ( 0 !== $args['avatar_size'] ) { echo get_avatar( $comment, $args['avatar_size'] ); } ?> <?php $comment_author = get_comment_author_link( $comment ); if ( '0' === $comment->comment_approved && ! $show_pending_links ) { $comment_author = get_comment_author( $comment ); } printf( /* translators: %s: Comment author link. */ __( '%s <span class="says">says:</span>' ), sprintf( '<cite class="fn">%s</cite>', $comment_author ) ); ?> </div> <?php if ( '0' === $comment->comment_approved ) : ?> <em class="comment-awaiting-moderation"><?php echo $moderation_note; ?></em> <br /> <?php endif; ?> <div class="comment-meta commentmetadata"> <?php printf( '<a href="%s">%s</a>', esc_url( get_comment_link( $comment, $args ) ), sprintf( /* translators: 1: Comment date, 2: Comment time. */ __( '%1$s at %2$s' ), get_comment_date( '', $comment ), get_comment_time() ) ); edit_comment_link( __( '(Edit)' ), ' ', '' ); ?> </div> <?php comment_text( $comment, array_merge( $args, array( 'add_below' => $add_below, 'depth' => $depth, 'max_depth' => $args['max_depth'], ) ) ); ?> <?php comment_reply_link( array_merge( $args, array( 'add_below' => $add_below, 'depth' => $depth, 'max_depth' => $args['max_depth'], 'before' => '<div class="reply">', 'after' => '</div>', ) ) ); ?> <?php if ( 'div' !== $args['style'] ) : ?> </div> <?php endif; ?> <?php } /** * Outputs a comment in the HTML5 format. * * @since 3.6.0 * * @see wp_list_comments() * * @param WP_Comment $comment Comment to display. * @param int $depth Depth of the current comment. * @param array $args An array of arguments. */ protected function html5_comment( $comment, $depth, $args ) { $tag = ( 'div' === $args['style'] ) ? 'div' : 'li'; $commenter = wp_get_current_commenter(); $show_pending_links = ! empty( $commenter['comment_author'] ); if ( $commenter['comment_author_email'] ) { $moderation_note = __( 'Your comment is awaiting moderation.' ); } else { $moderation_note = __( 'Your comment is awaiting moderation. This is a preview; your comment will be visible after it has been approved.' ); } ?> <<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( $this->has_children ? 'parent' : '', $comment ); ?>> <article id="div-comment-<?php comment_ID(); ?>" class="comment-body"> <footer class="comment-meta"> <div class="comment-author vcard"> <?php if ( 0 !== $args['avatar_size'] ) { echo get_avatar( $comment, $args['avatar_size'] ); } ?> <?php $comment_author = get_comment_author_link( $comment ); if ( '0' === $comment->comment_approved && ! $show_pending_links ) { $comment_author = get_comment_author( $comment ); } printf( /* translators: %s: Comment author link. */ __( '%s <span class="says">says:</span>' ), sprintf( '<b class="fn">%s</b>', $comment_author ) ); ?> </div><!-- .comment-author --> <div class="comment-metadata"> <?php printf( '<a href="%s"><time datetime="%s">%s</time></a>', esc_url( get_comment_link( $comment, $args ) ), get_comment_time( 'c' ), sprintf( /* translators: 1: Comment date, 2: Comment time. */ __( '%1$s at %2$s' ), get_comment_date( '', $comment ), get_comment_time() ) ); edit_comment_link( __( 'Edit' ), ' <span class="edit-link">', '</span>' ); ?> </div><!-- .comment-metadata --> <?php if ( '0' === $comment->comment_approved ) : ?> <em class="comment-awaiting-moderation"><?php echo $moderation_note; ?></em> <?php endif; ?> </footer><!-- .comment-meta --> <div class="comment-content"> <?php comment_text(); ?> </div><!-- .comment-content --> <?php if ( '1' === $comment->comment_approved || $show_pending_links ) { comment_reply_link( array_merge( $args, array( 'add_below' => 'div-comment', 'depth' => $depth, 'max_depth' => $args['max_depth'], 'before' => '<div class="reply">', 'after' => '</div>', ) ) ); } ?> </article><!-- .comment-body --> <?php } } class-wp-navigation-fallback.php 0000666 00000021751 15226762357 0012731 0 ustar 00 <?php /** * WP_Navigation_Fallback class * * Manages fallback behavior for Navigation menus. * * @package WordPress * @subpackage Navigation * @since 6.3.0 */ /** * Manages fallback behavior for Navigation menus. * * @since 6.3.0 */ class WP_Navigation_Fallback { /** * Updates the wp_navigation custom post type schema, in order to expose * additional fields in the embeddable links of WP_REST_Navigation_Fallback_Controller. * * The Navigation Fallback endpoint may embed the full Navigation Menu object * into the response as the `self` link. By default, the Posts Controller * will only expose a limited subset of fields but the editor requires * additional fields to be available in order to utilize the menu. * * Used with the `rest_wp_navigation_item_schema` hook. * * @since 6.4.0 * * @param array $schema The schema for the `wp_navigation` post. * @return array The modified schema. */ public static function update_wp_navigation_post_schema( $schema ) { // Expose top level fields. $schema['properties']['status']['context'] = array_merge( $schema['properties']['status']['context'], array( 'embed' ) ); $schema['properties']['content']['context'] = array_merge( $schema['properties']['content']['context'], array( 'embed' ) ); /* * Exposes sub properties of content field. * These sub properties aren't exposed by the posts controller by default, * for requests where context is `embed`. * * @see WP_REST_Posts_Controller::get_item_schema() */ $schema['properties']['content']['properties']['raw']['context'] = array_merge( $schema['properties']['content']['properties']['raw']['context'], array( 'embed' ) ); $schema['properties']['content']['properties']['rendered']['context'] = array_merge( $schema['properties']['content']['properties']['rendered']['context'], array( 'embed' ) ); $schema['properties']['content']['properties']['block_version']['context'] = array_merge( $schema['properties']['content']['properties']['block_version']['context'], array( 'embed' ) ); /* * Exposes sub properties of title field. * These sub properties aren't exposed by the posts controller by default, * for requests where context is `embed`. * * @see WP_REST_Posts_Controller::get_item_schema() */ $schema['properties']['title']['properties']['raw']['context'] = array_merge( $schema['properties']['title']['properties']['raw']['context'], array( 'embed' ) ); return $schema; } /** * Gets (and/or creates) an appropriate fallback Navigation Menu. * * @since 6.3.0 * * @return WP_Post|null the fallback Navigation Post or null. */ public static function get_fallback() { /** * Filters whether or not a fallback should be created. * * @since 6.3.0 * * @param bool $create Whether to create a fallback navigation menu. Default true. */ $should_create_fallback = apply_filters( 'wp_navigation_should_create_fallback', true ); $fallback = static::get_most_recently_published_navigation(); if ( $fallback || ! $should_create_fallback ) { return $fallback; } $fallback = static::create_classic_menu_fallback(); if ( $fallback && ! is_wp_error( $fallback ) ) { // Return the newly created fallback post object which will now be the most recently created navigation menu. return $fallback instanceof WP_Post ? $fallback : static::get_most_recently_published_navigation(); } $fallback = static::create_default_fallback(); if ( $fallback && ! is_wp_error( $fallback ) ) { // Return the newly created fallback post object which will now be the most recently created navigation menu. return $fallback instanceof WP_Post ? $fallback : static::get_most_recently_published_navigation(); } return null; } /** * Finds the most recently published `wp_navigation` post type. * * @since 6.3.0 * * @return WP_Post|null the first non-empty Navigation or null. */ private static function get_most_recently_published_navigation() { $parsed_args = array( 'post_type' => 'wp_navigation', 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'order' => 'DESC', 'orderby' => 'date', 'post_status' => 'publish', 'posts_per_page' => 1, ); $navigation_post = new WP_Query( $parsed_args ); if ( count( $navigation_post->posts ) > 0 ) { return $navigation_post->posts[0]; } return null; } /** * Creates a Navigation Menu post from a Classic Menu. * * @since 6.3.0 * * @return int|WP_Error The post ID of the default fallback menu or a WP_Error object. */ private static function create_classic_menu_fallback() { // See if we have a classic menu. $classic_nav_menu = static::get_fallback_classic_menu(); if ( ! $classic_nav_menu ) { return new WP_Error( 'no_classic_menus', __( 'No Classic Menus found.' ) ); } // If there is a classic menu then convert it to blocks. $classic_nav_menu_blocks = WP_Classic_To_Block_Menu_Converter::convert( $classic_nav_menu ); if ( is_wp_error( $classic_nav_menu_blocks ) ) { return $classic_nav_menu_blocks; } if ( empty( $classic_nav_menu_blocks ) ) { return new WP_Error( 'cannot_convert_classic_menu', __( 'Unable to convert Classic Menu to blocks.' ) ); } // Create a new navigation menu from the classic menu. $classic_menu_fallback = wp_insert_post( array( 'post_content' => $classic_nav_menu_blocks, 'post_title' => $classic_nav_menu->name, 'post_name' => $classic_nav_menu->slug, 'post_status' => 'publish', 'post_type' => 'wp_navigation', ), true // So that we can check whether the result is an error. ); return $classic_menu_fallback; } /** * Determines the most appropriate classic navigation menu to use as a fallback. * * @since 6.3.0 * * @return WP_Term|null The most appropriate classic navigation menu to use as a fallback. */ private static function get_fallback_classic_menu() { $classic_nav_menus = wp_get_nav_menus(); if ( ! $classic_nav_menus || is_wp_error( $classic_nav_menus ) ) { return null; } $nav_menu = static::get_nav_menu_at_primary_location(); if ( $nav_menu ) { return $nav_menu; } $nav_menu = static::get_nav_menu_with_primary_slug( $classic_nav_menus ); if ( $nav_menu ) { return $nav_menu; } return static::get_most_recently_created_nav_menu( $classic_nav_menus ); } /** * Sorts the classic menus and returns the most recently created one. * * @since 6.3.0 * * @param WP_Term[] $classic_nav_menus Array of classic nav menu term objects. * @return WP_Term The most recently created classic nav menu. */ private static function get_most_recently_created_nav_menu( $classic_nav_menus ) { usort( $classic_nav_menus, static function ( $a, $b ) { return $b->term_id - $a->term_id; } ); return $classic_nav_menus[0]; } /** * Returns the classic menu with the slug `primary` if it exists. * * @since 6.3.0 * * @param WP_Term[] $classic_nav_menus Array of classic nav menu term objects. * @return WP_Term|null The classic nav menu with the slug `primary` or null. */ private static function get_nav_menu_with_primary_slug( $classic_nav_menus ) { foreach ( $classic_nav_menus as $classic_nav_menu ) { if ( 'primary' === $classic_nav_menu->slug ) { return $classic_nav_menu; } } return null; } /** * Gets the classic menu assigned to the `primary` navigation menu location * if it exists. * * @since 6.3.0 * * @return WP_Term|null The classic nav menu assigned to the `primary` location or null. */ private static function get_nav_menu_at_primary_location() { $locations = get_nav_menu_locations(); if ( isset( $locations['primary'] ) ) { $primary_menu = wp_get_nav_menu_object( $locations['primary'] ); if ( $primary_menu ) { return $primary_menu; } } return null; } /** * Creates a default Navigation Block Menu fallback. * * @since 6.3.0 * * @return int|WP_Error The post ID of the default fallback menu or a WP_Error object. */ private static function create_default_fallback() { $default_blocks = static::get_default_fallback_blocks(); // Create a new navigation menu from the fallback blocks. $default_fallback = wp_insert_post( array( 'post_content' => $default_blocks, 'post_title' => _x( 'Navigation', 'Title of a Navigation menu' ), 'post_name' => 'navigation', 'post_status' => 'publish', 'post_type' => 'wp_navigation', ), true // So that we can check whether the result is an error. ); return $default_fallback; } /** * Gets the rendered markup for the default fallback blocks. * * @since 6.3.0 * * @return string default blocks markup to use a the fallback. */ private static function get_default_fallback_blocks() { $registry = WP_Block_Type_Registry::get_instance(); // If `core/page-list` is not registered then use empty blocks. return $registry->is_registered( 'core/page-list' ) ? '<!-- wp:page-list /-->' : ''; } } class-wp-exception.php 0000666 00000000375 15226762357 0011032 0 ustar 00 <?php /** * WP_Exception class * * @package WordPress * @since 6.7.0 */ /** * Core base Exception class. * * Future, more specific, Exceptions should always extend this base class. * * @since 6.7.0 */ class WP_Exception extends Exception {} class-wp-user-20260621195951.php 0000666 00000055004 15226762357 0011407 0 ustar 00 <?php /** * User API: WP_User class * * @package WordPress * @subpackage Users * @since 4.4.0 */ /** * Core class used to implement the WP_User object. * * @since 2.0.0 * @since 6.8.0 The `user_pass` property is now hashed using bcrypt by default instead of phpass. * Existing passwords may still be hashed using phpass. * * @property string $nickname * @property string $description * @property string $user_description * @property string $first_name * @property string $user_firstname * @property string $last_name * @property string $user_lastname * @property string $user_login * @property string $user_pass * @property string $user_nicename * @property string $user_email * @property string $user_url * @property string $user_registered * @property string $user_activation_key * @property string $user_status * @property int $user_level * @property string $display_name * @property string $spam * @property string $deleted * @property string $locale * @property string $rich_editing * @property string $syntax_highlighting * @property string $use_ssl */ #[AllowDynamicProperties] class WP_User { /** * User data container. * * @since 2.0.0 * @var stdClass */ public $data; /** * The user's ID. * * @since 2.1.0 * @var int */ public $ID = 0; /** * Capabilities that the individual user has been granted outside of those inherited from their role. * * @since 2.0.0 * @var bool[] Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. */ public $caps = array(); /** * User metadata option name. * * @since 2.0.0 * @var string */ public $cap_key; /** * The roles the user is part of. * * @since 2.0.0 * @var string[] */ public $roles = array(); /** * All capabilities the user has, including individual and role based. * * @since 2.0.0 * @var bool[] Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. */ public $allcaps = array(); /** * The filter context applied to user data fields. * * @since 2.9.0 * @var string */ public $filter = null; /** * The site ID the capabilities of this user are initialized for. * * @since 4.9.0 * @var int */ private $site_id = 0; /** * @since 3.3.0 * @var array */ private static $back_compat_keys; /** * Constructor. * * Retrieves the userdata and passes it to WP_User::init(). * * @since 2.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int|string|stdClass|WP_User $id User's ID, a WP_User object, or a user object from the DB. * @param string $name Optional. User's username * @param int $site_id Optional Site ID, defaults to current site. */ public function __construct( $id = 0, $name = '', $site_id = 0 ) { global $wpdb; if ( ! isset( self::$back_compat_keys ) ) { $prefix = $wpdb->prefix; self::$back_compat_keys = array( 'user_firstname' => 'first_name', 'user_lastname' => 'last_name', 'user_description' => 'description', 'user_level' => $prefix . 'user_level', $prefix . 'usersettings' => $prefix . 'user-settings', $prefix . 'usersettingstime' => $prefix . 'user-settings-time', ); } if ( $id instanceof WP_User ) { $this->init( $id->data, $site_id ); return; } elseif ( is_object( $id ) ) { $this->init( $id, $site_id ); return; } if ( ! empty( $id ) && ! is_numeric( $id ) ) { $name = $id; $id = 0; } if ( $id ) { $data = self::get_data_by( 'id', $id ); } else { $data = self::get_data_by( 'login', $name ); } if ( $data ) { $this->init( $data, $site_id ); } else { $this->data = new stdClass(); } } /** * Sets up object properties, including capabilities. * * @since 3.3.0 * * @param object $data User DB row object. * @param int $site_id Optional. The site ID to initialize for. */ public function init( $data, $site_id = 0 ) { if ( ! isset( $data->ID ) ) { $data->ID = 0; } $this->data = $data; $this->ID = (int) $data->ID; $this->for_site( $site_id ); } /** * Returns only the main user fields. * * @since 3.3.0 * @since 4.4.0 Added 'ID' as an alias of 'id' for the `$field` parameter. * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $field The field to query against: Accepts 'id', 'ID', 'slug', 'email' or 'login'. * @param string|int $value The field value. * @return object|false Raw user object. */ public static function get_data_by( $field, $value ) { global $wpdb; // 'ID' is an alias of 'id'. if ( 'ID' === $field ) { $field = 'id'; } if ( 'id' === $field ) { // Make sure the value is numeric to avoid casting objects, for example, to int 1. if ( ! is_numeric( $value ) ) { return false; } $value = (int) $value; if ( $value < 1 ) { return false; } } else { $value = trim( $value ); } if ( ! $value ) { return false; } switch ( $field ) { case 'id': $user_id = $value; $db_field = 'ID'; break; case 'slug': $user_id = wp_cache_get( $value, 'userslugs' ); $db_field = 'user_nicename'; break; case 'email': $user_id = wp_cache_get( $value, 'useremail' ); $db_field = 'user_email'; break; case 'login': $value = sanitize_user( $value ); $user_id = wp_cache_get( $value, 'userlogins' ); $db_field = 'user_login'; break; default: return false; } if ( false !== $user_id ) { $user = wp_cache_get( $user_id, 'users' ); if ( $user ) { return $user; } } $user = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE $db_field = %s LIMIT 1", $value ) ); if ( ! $user ) { return false; } update_user_caches( $user ); return $user; } /** * Magic method for checking the existence of a certain custom field. * * @since 3.3.0 * * @param string $key User meta key to check if set. * @return bool Whether the given user meta key is set. */ public function __isset( $key ) { if ( 'id' === $key ) { _deprecated_argument( 'WP_User->id', '2.1.0', sprintf( /* translators: %s: WP_User->ID */ __( 'Use %s instead.' ), '<code>WP_User->ID</code>' ) ); $key = 'ID'; } if ( isset( $this->data->$key ) ) { return true; } if ( isset( self::$back_compat_keys[ $key ] ) ) { $key = self::$back_compat_keys[ $key ]; } return metadata_exists( 'user', $this->ID, $key ); } /** * Magic method for accessing custom fields. * * @since 3.3.0 * * @param string $key User meta key to retrieve. * @return mixed Value of the given user meta key (if set). If `$key` is 'id', the user ID. */ public function __get( $key ) { if ( 'id' === $key ) { _deprecated_argument( 'WP_User->id', '2.1.0', sprintf( /* translators: %s: WP_User->ID */ __( 'Use %s instead.' ), '<code>WP_User->ID</code>' ) ); return $this->ID; } if ( isset( $this->data->$key ) ) { $value = $this->data->$key; } else { if ( isset( self::$back_compat_keys[ $key ] ) ) { $key = self::$back_compat_keys[ $key ]; } $value = get_user_meta( $this->ID, $key, true ); } if ( $this->filter ) { $value = sanitize_user_field( $key, $value, $this->ID, $this->filter ); } return $value; } /** * Magic method for setting custom user fields. * * This method does not update custom fields in the database. It only stores * the value on the WP_User instance. * * @since 3.3.0 * * @param string $key User meta key. * @param mixed $value User meta value. */ public function __set( $key, $value ) { if ( 'id' === $key ) { _deprecated_argument( 'WP_User->id', '2.1.0', sprintf( /* translators: %s: WP_User->ID */ __( 'Use %s instead.' ), '<code>WP_User->ID</code>' ) ); $this->ID = $value; return; } $this->data->$key = $value; } /** * Magic method for unsetting a certain custom field. * * @since 4.4.0 * * @param string $key User meta key to unset. */ public function __unset( $key ) { if ( 'id' === $key ) { _deprecated_argument( 'WP_User->id', '2.1.0', sprintf( /* translators: %s: WP_User->ID */ __( 'Use %s instead.' ), '<code>WP_User->ID</code>' ) ); } if ( isset( $this->data->$key ) ) { unset( $this->data->$key ); } if ( isset( self::$back_compat_keys[ $key ] ) ) { unset( self::$back_compat_keys[ $key ] ); } } /** * Determines whether the user exists in the database. * * @since 3.4.0 * * @return bool True if user exists in the database, false if not. */ public function exists() { return ! empty( $this->ID ); } /** * Retrieves the value of a property or meta key. * * Retrieves from the users and usermeta table. * * @since 3.3.0 * * @param string $key Property * @return mixed */ public function get( $key ) { return $this->__get( $key ); } /** * Determines whether a property or meta key is set. * * Consults the users and usermeta tables. * * @since 3.3.0 * * @param string $key Property. * @return bool */ public function has_prop( $key ) { return $this->__isset( $key ); } /** * Returns an array representation. * * @since 3.5.0 * * @return array Array representation. */ public function to_array() { return get_object_vars( $this->data ); } /** * Makes private/protected methods readable for backward compatibility. * * @since 4.3.0 * * @param string $name Method to call. * @param array $arguments Arguments to pass when calling. * @return mixed|false Return value of the callback, false otherwise. */ public function __call( $name, $arguments ) { if ( '_init_caps' === $name ) { return $this->_init_caps( ...$arguments ); } return false; } /** * Sets up capability object properties. * * Will set the value for the 'cap_key' property to current database table * prefix, followed by 'capabilities'. Will then check to see if the * property matching the 'cap_key' exists and is an array. If so, it will be * used. * * @since 2.1.0 * @deprecated 4.9.0 Use WP_User::for_site() * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $cap_key Optional capability key */ protected function _init_caps( $cap_key = '' ) { global $wpdb; _deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' ); if ( empty( $cap_key ) ) { $this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities'; } else { $this->cap_key = $cap_key; } $this->caps = $this->get_caps_data(); $this->get_role_caps(); } /** * Retrieves all of the capabilities of the user's roles, and merges them with * individual user capabilities. * * All of the capabilities of the user's roles are merged with the user's individual * capabilities. This means that the user can be denied specific capabilities that * their role might have, but the user is specifically denied. * * @since 2.0.0 * * @return bool[] Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. */ public function get_role_caps() { $switch_site = false; if ( is_multisite() && get_current_blog_id() !== $this->site_id ) { $switch_site = true; switch_to_blog( $this->site_id ); } $wp_roles = wp_roles(); // Select caps that are role names and assign to $this->roles. if ( is_array( $this->caps ) ) { $this->roles = array(); foreach ( $this->caps as $key => $value ) { if ( $wp_roles->is_role( $key ) ) { $this->roles[] = $key; } } } // Build $allcaps from role caps, overlay user's $caps. $this->allcaps = array(); foreach ( (array) $this->roles as $role ) { $the_role = $wp_roles->get_role( $role ); $this->allcaps = array_merge( (array) $this->allcaps, (array) $the_role->capabilities ); } $this->allcaps = array_merge( (array) $this->allcaps, (array) $this->caps ); if ( $switch_site ) { restore_current_blog(); } return $this->allcaps; } /** * Adds role to user. * * Updates the user's meta data option with capabilities and roles. * * @since 2.0.0 * * @param string $role Role name. */ public function add_role( $role ) { if ( empty( $role ) ) { return; } if ( in_array( $role, $this->roles, true ) ) { return; } $this->caps[ $role ] = true; update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); /** * Fires immediately after the user has been given a new role. * * @since 4.3.0 * * @param int $user_id The user ID. * @param string $role The new role. */ do_action( 'add_user_role', $this->ID, $role ); } /** * Removes role from user. * * @since 2.0.0 * * @param string $role Role name. */ public function remove_role( $role ) { if ( ! in_array( $role, $this->roles, true ) ) { return; } unset( $this->caps[ $role ] ); update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); /** * Fires immediately after a role as been removed from a user. * * @since 4.3.0 * * @param int $user_id The user ID. * @param string $role The removed role. */ do_action( 'remove_user_role', $this->ID, $role ); } /** * Sets the role of the user. * * This will remove the previous roles of the user and assign the user the * new one. You can set the role to an empty string and it will remove all * of the roles from the user. * * @since 2.0.0 * * @param string $role Role name. */ public function set_role( $role ) { if ( 1 === count( $this->roles ) && current( $this->roles ) === $role ) { return; } foreach ( (array) $this->roles as $oldrole ) { unset( $this->caps[ $oldrole ] ); } $old_roles = $this->roles; if ( ! empty( $role ) ) { $this->caps[ $role ] = true; $this->roles = array( $role => true ); } else { $this->roles = array(); } update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); foreach ( $old_roles as $old_role ) { if ( ! $old_role || $old_role === $role ) { continue; } /** This action is documented in wp-includes/class-wp-user.php */ do_action( 'remove_user_role', $this->ID, $old_role ); } if ( $role && ! in_array( $role, $old_roles, true ) ) { /** This action is documented in wp-includes/class-wp-user.php */ do_action( 'add_user_role', $this->ID, $role ); } /** * Fires after the user's role has changed. * * @since 2.9.0 * @since 3.6.0 Added $old_roles to include an array of the user's previous roles. * * @param int $user_id The user ID. * @param string $role The new role. * @param string[] $old_roles An array of the user's previous roles. */ do_action( 'set_user_role', $this->ID, $role, $old_roles ); } /** * Chooses the maximum level the user has. * * Will compare the level from the $item parameter against the $max * parameter. If the item is incorrect, then just the $max parameter value * will be returned. * * Used to get the max level based on the capabilities the user has. This * is also based on roles, so if the user is assigned the Administrator role * then the capability 'level_10' will exist and the user will get that * value. * * @since 2.0.0 * * @param int $max Max level of user. * @param string $item Level capability name. * @return int Max Level. */ public function level_reduction( $max, $item ) { if ( preg_match( '/^level_(10|[0-9])$/i', $item, $matches ) ) { $level = (int) $matches[1]; return max( $max, $level ); } else { return $max; } } /** * Updates the maximum user level for the user. * * Updates the 'user_level' user metadata (includes prefix that is the * database table prefix) with the maximum user level. Gets the value from * the all of the capabilities that the user has. * * @since 2.0.0 * * @global wpdb $wpdb WordPress database abstraction object. */ public function update_user_level_from_caps() { global $wpdb; $this->user_level = array_reduce( array_keys( $this->allcaps ), array( $this, 'level_reduction' ), 0 ); update_user_meta( $this->ID, $wpdb->get_blog_prefix() . 'user_level', $this->user_level ); } /** * Adds capability and grant or deny access to capability. * * @since 2.0.0 * * @param string $cap Capability name. * @param bool $grant Whether to grant capability to user. */ public function add_cap( $cap, $grant = true ) { $this->caps[ $cap ] = $grant; update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); } /** * Removes capability from user. * * @since 2.0.0 * * @param string $cap Capability name. */ public function remove_cap( $cap ) { if ( ! isset( $this->caps[ $cap ] ) ) { return; } unset( $this->caps[ $cap ] ); update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); } /** * Removes all of the capabilities of the user. * * @since 2.1.0 * * @global wpdb $wpdb WordPress database abstraction object. */ public function remove_all_caps() { global $wpdb; $this->caps = array(); delete_user_meta( $this->ID, $this->cap_key ); delete_user_meta( $this->ID, $wpdb->get_blog_prefix() . 'user_level' ); $this->get_role_caps(); } /** * Returns whether the user has the specified capability. * * This function also accepts an ID of an object to check against if the capability is a meta capability. Meta * capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to * map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`. * * Example usage: * * $user->has_cap( 'edit_posts' ); * $user->has_cap( 'edit_post', $post->ID ); * $user->has_cap( 'edit_post_meta', $post->ID, $meta_key ); * * While checking against a role in place of a capability is supported in part, this practice is discouraged as it * may produce unreliable results. * * @since 2.0.0 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by adding it to the function signature. * * @see map_meta_cap() * * @param string $cap Capability name. * @param mixed ...$args Optional further parameters, typically starting with an object ID. * @return bool Whether the user has the given capability, or, if an object ID is passed, whether the user has * the given capability for that object. */ public function has_cap( $cap, ...$args ) { if ( is_numeric( $cap ) ) { _deprecated_argument( __FUNCTION__, '2.0.0', __( 'Usage of user levels is deprecated. Use capabilities instead.' ) ); $cap = $this->translate_level_to_cap( $cap ); } $caps = map_meta_cap( $cap, $this->ID, ...$args ); // Multisite super admin has all caps by definition, Unless specifically denied. if ( is_multisite() && is_super_admin( $this->ID ) ) { if ( in_array( 'do_not_allow', $caps, true ) ) { return false; } return true; } // Maintain BC for the argument passed to the "user_has_cap" filter. $args = array_merge( array( $cap, $this->ID ), $args ); /** * Dynamically filter a user's capabilities. * * @since 2.0.0 * @since 3.7.0 Added the `$user` parameter. * * @param bool[] $allcaps Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. * @param string[] $caps Required primitive capabilities for the requested capability. * @param array $args { * Arguments that accompany the requested capability check. * * @type string $0 Requested capability. * @type int $1 Concerned user ID. * @type mixed ...$2 Optional second and further parameters, typically object ID. * } * @param WP_User $user The user object. */ $capabilities = apply_filters( 'user_has_cap', $this->allcaps, $caps, $args, $this ); // Everyone is allowed to exist. $capabilities['exist'] = true; // Nobody is allowed to do things they are not allowed to do. unset( $capabilities['do_not_allow'] ); // Must have ALL requested caps. foreach ( (array) $caps as $cap ) { if ( empty( $capabilities[ $cap ] ) ) { return false; } } return true; } /** * Converts numeric level to level capability name. * * Prepends 'level_' to level number. * * @since 2.0.0 * * @param int $level Level number, 1 to 10. * @return string */ public function translate_level_to_cap( $level ) { return 'level_' . $level; } /** * Sets the site to operate on. Defaults to the current site. * * @since 3.0.0 * @deprecated 4.9.0 Use WP_User::for_site() * * @param int $blog_id Optional. Site ID, defaults to current site. */ public function for_blog( $blog_id = 0 ) { _deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' ); $this->for_site( $blog_id ); } /** * Sets the site to operate on. Defaults to the current site. * * @since 4.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $site_id Site ID to initialize user capabilities for. Default is the current site. */ public function for_site( $site_id = 0 ) { global $wpdb; if ( ! empty( $site_id ) ) { $this->site_id = absint( $site_id ); } else { $this->site_id = get_current_blog_id(); } $this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities'; $this->caps = $this->get_caps_data(); $this->get_role_caps(); } /** * Gets the ID of the site for which the user's capabilities are currently initialized. * * @since 4.9.0 * * @return int Site ID. */ public function get_site_id() { return $this->site_id; } /** * Gets the available user capabilities data. * * @since 4.9.0 * * @return bool[] List of capabilities keyed by the capability name, * e.g. `array( 'edit_posts' => true, 'delete_posts' => false )`. */ private function get_caps_data() { $caps = get_user_meta( $this->ID, $this->cap_key, true ); if ( ! is_array( $caps ) ) { return array(); } return $caps; } } https-migration-20260605102011.php 0000666 00000011205 15226762357 0011777 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260606142425.php 0000666 00000013341 15226762357 0012005 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-widget-area-customize-control.php 0000666 00000003267 15226762357 0014726 0 ustar 00 <?php /** * Customize API: WP_Widget_Area_Customize_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Widget Area Customize Control class. * * @since 3.9.0 * * @see WP_Customize_Control */ class WP_Widget_Area_Customize_Control extends WP_Customize_Control { /** * Customize control type. * * @since 3.9.0 * @var string */ public $type = 'sidebar_widgets'; /** * Sidebar ID. * * @since 3.9.0 * @var int|string */ public $sidebar_id; /** * Refreshes the parameters passed to the JavaScript via JSON. * * @since 3.9.0 */ public function to_json() { parent::to_json(); $exported_properties = array( 'sidebar_id' ); foreach ( $exported_properties as $key ) { $this->json[ $key ] = $this->$key; } } /** * Renders the control's content. * * @since 3.9.0 */ public function render_content() { $id = 'reorder-widgets-desc-' . str_replace( array( '[', ']' ), array( '-', '' ), $this->id ); ?> <button type="button" class="button add-new-widget" aria-expanded="false" aria-controls="available-widgets"> <?php _e( 'Add a Widget' ); ?> </button> <button type="button" class="button-link reorder-toggle" aria-label="<?php esc_attr_e( 'Reorder widgets' ); ?>" aria-describedby="<?php echo esc_attr( $id ); ?>"> <span class="reorder"><?php _e( 'Reorder' ); ?></span> <span class="reorder-done"><?php _e( 'Done' ); ?></span> </button> <p class="screen-reader-text" id="<?php echo esc_attr( $id ); ?>"> <?php /* translators: Hidden accessibility text. */ _e( 'When in reorder mode, additional controls to reorder widgets will be available in the widgets list above.' ); ?> </p> <?php } } class-wp-http-proxy.php 0000666 00000013534 15226762357 0011173 0 ustar 00 <?php /** * HTTP API: WP_HTTP_Proxy class * * @package WordPress * @subpackage HTTP * @since 4.4.0 */ /** * Core class used to implement HTTP API proxy support. * * There are caveats to proxy support. It requires that defines be made in the wp-config.php file to * enable proxy support. There are also a few filters that plugins can hook into for some of the * constants. * * Please note that only BASIC authentication is supported by most transports. * cURL MAY support more methods (such as NTLM authentication) depending on your environment. * * The constants are as follows: * <ol> * <li>WP_PROXY_HOST - Enable proxy support and host for connecting.</li> * <li>WP_PROXY_PORT - Proxy port for connection. No default, must be defined.</li> * <li>WP_PROXY_USERNAME - Proxy username, if it requires authentication.</li> * <li>WP_PROXY_PASSWORD - Proxy password, if it requires authentication.</li> * <li>WP_PROXY_BYPASS_HOSTS - Will prevent the hosts in this list from going through the proxy. * You do not need to have localhost and the site host in this list, because they will not be passed * through the proxy. The list should be presented in a comma separated list, wildcards using * are supported. Example: *.wordpress.org</li> * </ol> * * An example can be as seen below. * * define('WP_PROXY_HOST', '192.168.84.101'); * define('WP_PROXY_PORT', '8080'); * define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org'); * * @link https://core.trac.wordpress.org/ticket/4011 Proxy support ticket in WordPress. * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_PROXY_BYPASS_HOSTS * * @since 2.8.0 */ #[AllowDynamicProperties] class WP_HTTP_Proxy { /** * Whether proxy connection should be used. * * Constants which control this behavior: * * - `WP_PROXY_HOST` * - `WP_PROXY_PORT` * * @since 2.8.0 * * @return bool */ public function is_enabled() { return defined( 'WP_PROXY_HOST' ) && defined( 'WP_PROXY_PORT' ); } /** * Whether authentication should be used. * * Constants which control this behavior: * * - `WP_PROXY_USERNAME` * - `WP_PROXY_PASSWORD` * * @since 2.8.0 * * @return bool */ public function use_authentication() { return defined( 'WP_PROXY_USERNAME' ) && defined( 'WP_PROXY_PASSWORD' ); } /** * Retrieve the host for the proxy server. * * @since 2.8.0 * * @return string */ public function host() { if ( defined( 'WP_PROXY_HOST' ) ) { return WP_PROXY_HOST; } return ''; } /** * Retrieve the port for the proxy server. * * @since 2.8.0 * * @return string */ public function port() { if ( defined( 'WP_PROXY_PORT' ) ) { return WP_PROXY_PORT; } return ''; } /** * Retrieve the username for proxy authentication. * * @since 2.8.0 * * @return string */ public function username() { if ( defined( 'WP_PROXY_USERNAME' ) ) { return WP_PROXY_USERNAME; } return ''; } /** * Retrieve the password for proxy authentication. * * @since 2.8.0 * * @return string */ public function password() { if ( defined( 'WP_PROXY_PASSWORD' ) ) { return WP_PROXY_PASSWORD; } return ''; } /** * Retrieve authentication string for proxy authentication. * * @since 2.8.0 * * @return string */ public function authentication() { return $this->username() . ':' . $this->password(); } /** * Retrieve header string for proxy authentication. * * @since 2.8.0 * * @return string */ public function authentication_header() { return 'Proxy-Authorization: Basic ' . base64_encode( $this->authentication() ); } /** * Determines whether the request should be sent through a proxy. * * We want to keep localhost and the site URL from being sent through the proxy, because * some proxies can not handle this. We also have the constant available for defining other * hosts that won't be sent through the proxy. * * @since 2.8.0 * * @param string $uri URL of the request. * @return bool Whether to send the request through the proxy. */ public function send_through_proxy( $uri ) { $check = parse_url( $uri ); // Malformed URL, can not process, but this could mean ssl, so let through anyway. if ( false === $check ) { return true; } $home = parse_url( get_option( 'siteurl' ) ); /** * Filters whether to preempt sending the request through the proxy. * * Returning false will bypass the proxy; returning true will send * the request through the proxy. Returning null bypasses the filter. * * @since 3.5.0 * * @param bool|null $override Whether to send the request through the proxy. Default null. * @param string $uri URL of the request. * @param array $check Associative array result of parsing the request URL with `parse_url()`. * @param array $home Associative array result of parsing the site URL with `parse_url()`. */ $result = apply_filters( 'pre_http_send_through_proxy', null, $uri, $check, $home ); if ( ! is_null( $result ) ) { return $result; } if ( 'localhost' === $check['host'] || ( isset( $home['host'] ) && $home['host'] === $check['host'] ) ) { return false; } if ( ! defined( 'WP_PROXY_BYPASS_HOSTS' ) ) { return true; } static $bypass_hosts = null; static $wildcard_regex = array(); if ( null === $bypass_hosts ) { $bypass_hosts = preg_split( '|,\s*|', WP_PROXY_BYPASS_HOSTS ); if ( str_contains( WP_PROXY_BYPASS_HOSTS, '*' ) ) { $wildcard_regex = array(); foreach ( $bypass_hosts as $host ) { $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) ); } $wildcard_regex = '/^(' . implode( '|', $wildcard_regex ) . ')$/i'; } } if ( ! empty( $wildcard_regex ) ) { return ! preg_match( $wildcard_regex, $check['host'] ); } else { return ! in_array( $check['host'], $bypass_hosts, true ); } } } https-migration-20260605201537.php 0000666 00000011205 15226762357 0012014 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } pluggable.php 0000666 00000370741 15226762357 0007256 0 ustar 00 <?php /** * These functions can be replaced via plugins. If plugins do not redefine these * functions, then these will be used instead. * * @package WordPress */ if ( ! function_exists( 'wp_set_current_user' ) ) : /** * Changes the current user by ID or name. * * Set $id to null and specify a name if you do not know a user's ID. * * Some WordPress functionality is based on the current user and not based on * the signed in user. Therefore, it opens the ability to edit and perform * actions on users who aren't signed in. * * @since 2.0.3 * * @global WP_User $current_user The current user object which holds the user data. * * @param int|null $id User ID. * @param string $name User's username. * @return WP_User Current user User object. */ function wp_set_current_user( $id, $name = '' ) { global $current_user; // If `$id` matches the current user, there is nothing to do. if ( isset( $current_user ) && ( $current_user instanceof WP_User ) && ( $id === $current_user->ID ) && ( null !== $id ) ) { return $current_user; } $current_user = new WP_User( $id, $name ); setup_userdata( $current_user->ID ); /** * Fires after the current user is set. * * @since 2.0.1 */ do_action( 'set_current_user' ); return $current_user; } endif; if ( ! function_exists( 'wp_get_current_user' ) ) : /** * Retrieves the current user object. * * Will set the current user, if the current user is not set. The current user * will be set to the logged-in person. If no user is logged-in, then it will * set the current user to 0, which is invalid and won't have any permissions. * * @since 2.0.3 * * @see _wp_get_current_user() * @global WP_User $current_user Checks if the current user is set. * * @return WP_User Current WP_User instance. */ function wp_get_current_user() { return _wp_get_current_user(); } endif; if ( ! function_exists( 'get_userdata' ) ) : /** * Retrieves user info by user ID. * * @since 0.71 * * @param int $user_id User ID * @return WP_User|false WP_User object on success, false on failure. */ function get_userdata( $user_id ) { return get_user_by( 'id', $user_id ); } endif; if ( ! function_exists( 'get_user_by' ) ) : /** * Retrieves user info by a given field. * * @since 2.8.0 * @since 4.4.0 Added 'ID' as an alias of 'id' for the `$field` parameter. * * @global WP_User $current_user The current user object which holds the user data. * * @param string $field The field to retrieve the user with. id | ID | slug | email | login. * @param int|string $value A value for $field. A user ID, slug, email address, or login name. * @return WP_User|false WP_User object on success, false on failure. */ function get_user_by( $field, $value ) { $userdata = WP_User::get_data_by( $field, $value ); if ( ! $userdata ) { return false; } $user = new WP_User(); $user->init( $userdata ); return $user; } endif; if ( ! function_exists( 'cache_users' ) ) : /** * Retrieves info for user lists to prevent multiple queries by get_userdata(). * * @since 3.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int[] $user_ids User ID numbers list */ function cache_users( $user_ids ) { global $wpdb; update_meta_cache( 'user', $user_ids ); $clean = _get_non_cached_ids( $user_ids, 'users' ); if ( empty( $clean ) ) { return; } $list = implode( ',', $clean ); $users = $wpdb->get_results( "SELECT * FROM $wpdb->users WHERE ID IN ($list)" ); foreach ( $users as $user ) { update_user_caches( $user ); } } endif; if ( ! function_exists( 'wp_mail' ) ) : /** * Sends an email, similar to PHP's mail function. * * A true return value does not automatically mean that the user received the * email successfully. It just only means that the method used was able to * process the request without any errors. * * The default content type is `text/plain` which does not allow using HTML. * However, you can set the content type of the email by using the * {@see 'wp_mail_content_type'} filter. * * The default charset is based on the charset used on the blog. The charset can * be set using the {@see 'wp_mail_charset'} filter. * * When using the `$embeds` parameter to embed images for use in HTML emails, * reference the embedded file in your HTML with a `cid:` URL whose value * matches the file's Content-ID. By default, the Content-ID (`cid`) used for * each embedded file is the key in the embeds array, unless modified via the * {@see 'wp_mail_embed_args'} filter. For example: * * `<img src="cid:0" alt="Logo">` * `<img src="cid:my-image" alt="Image">` * * You may also customize the Content-ID for each file by using the * {@see 'wp_mail_embed_args'} filter and setting the `cid` value. * * @since 1.2.1 * @since 5.5.0 is_email() is used for email validation, * instead of PHPMailer's default validator. * @since 6.9.0 Added $embeds parameter. * @since 6.9.0 Improved Content-Type header handling for multipart messages. * * @global PHPMailer\PHPMailer\PHPMailer $phpmailer * * @param string|string[] $to Array or comma-separated list of email addresses to send message. * @param string $subject Email subject. * @param string $message Message contents. * @param string|string[] $headers Optional. Additional headers. * @param string|string[] $attachments Optional. Paths to files to attach. * @param string|string[] $embeds Optional. Paths to files to embed. * @return bool Whether the email was sent successfully. */ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array(), $embeds = array() ) { // Compact the input, apply the filters, and extract them back out. /** * Filters the wp_mail() arguments. * * @since 2.2.0 * * @param array $args { * Array of the `wp_mail()` arguments. * * @type string|string[] $to Array or comma-separated list of email addresses to send message. * @type string $subject Email subject. * @type string $message Message contents. * @type string|string[] $headers Additional headers. * @type string|string[] $attachments Paths to files to attach. * @type string|string[] $embeds Paths to files to embed. * } */ $atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments', 'embeds' ) ); /** * Filters whether to preempt sending an email. * * Returning a non-null value will short-circuit {@see wp_mail()}, returning * that value instead. A boolean return value should be used to indicate whether * the email was successfully sent. * * @since 5.7.0 * * @param null|bool $return Short-circuit return value. * @param array $atts { * Array of the `wp_mail()` arguments. * * @type string|string[] $to Array or comma-separated list of email addresses to send message. * @type string $subject Email subject. * @type string $message Message contents. * @type string|string[] $headers Additional headers. * @type string|string[] $attachments Paths to files to attach. * @type string|string[] $embeds Paths to files to embed. * } */ $pre_wp_mail = apply_filters( 'pre_wp_mail', null, $atts ); if ( null !== $pre_wp_mail ) { return $pre_wp_mail; } if ( isset( $atts['to'] ) ) { $to = $atts['to']; } if ( ! is_array( $to ) ) { $to = explode( ',', $to ); } if ( isset( $atts['subject'] ) ) { $subject = $atts['subject']; } if ( isset( $atts['message'] ) ) { $message = $atts['message']; } if ( isset( $atts['headers'] ) ) { $headers = $atts['headers']; } if ( isset( $atts['attachments'] ) ) { $attachments = $atts['attachments']; } if ( ! is_array( $attachments ) ) { $attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) ); } if ( isset( $atts['embeds'] ) ) { $embeds = $atts['embeds']; } if ( ! is_array( $embeds ) ) { $embeds = explode( "\n", str_replace( "\r\n", "\n", $embeds ) ); } global $phpmailer; // (Re)create it, if it's gone missing. if ( ! ( $phpmailer instanceof PHPMailer\PHPMailer\PHPMailer ) ) { require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php'; require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php'; require_once ABSPATH . WPINC . '/PHPMailer/Exception.php'; require_once ABSPATH . WPINC . '/class-wp-phpmailer.php'; $phpmailer = new WP_PHPMailer( true ); $phpmailer::$validator = static function ( $email ) { return (bool) is_email( $email ); }; } // Headers. $cc = array(); $bcc = array(); $reply_to = array(); if ( empty( $headers ) ) { $headers = array(); } else { if ( ! is_array( $headers ) ) { /* * Explode the headers out, so this function can take * both string headers and an array of headers. */ $tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) ); } else { $tempheaders = $headers; } $headers = array(); // If it's actually got contents. if ( ! empty( $tempheaders ) ) { // Iterate through the raw headers. foreach ( (array) $tempheaders as $header ) { if ( ! str_contains( $header, ':' ) ) { if ( false !== stripos( $header, 'boundary=' ) ) { $parts = preg_split( '/boundary=/i', trim( $header ) ); $boundary = trim( str_replace( array( "'", '"' ), '', $parts[1] ) ); } continue; } // Explode them out. list( $name, $content ) = explode( ':', trim( $header ), 2 ); // Cleanup crew. $name = trim( $name ); $content = trim( $content ); switch ( strtolower( $name ) ) { // Mainly for legacy -- process a "From:" header if it's there. case 'from': $bracket_pos = strpos( $content, '<' ); if ( false !== $bracket_pos ) { // Text before the bracketed email is the "From" name. if ( $bracket_pos > 0 ) { $from_name = substr( $content, 0, $bracket_pos ); $from_name = str_replace( '"', '', $from_name ); $from_name = trim( $from_name ); } $from_email = substr( $content, $bracket_pos + 1 ); $from_email = str_replace( '>', '', $from_email ); $from_email = trim( $from_email ); // Avoid setting an empty $from_email. } elseif ( '' !== trim( $content ) ) { $from_email = trim( $content ); } break; case 'content-type': if ( str_contains( $content, ';' ) ) { list( $type, $charset_content ) = explode( ';', $content ); $content_type = trim( $type ); if ( false !== stripos( $charset_content, 'charset=' ) ) { $charset = trim( str_replace( array( 'charset=', '"' ), '', $charset_content ) ); } elseif ( false !== stripos( $charset_content, 'boundary=' ) ) { $boundary = trim( str_replace( array( 'BOUNDARY=', 'boundary=', '"' ), '', $charset_content ) ); $charset = ''; if ( preg_match( '~^multipart/(\S+)~', $content_type, $matches ) ) { $content_type = 'multipart/' . strtolower( $matches[1] ) . '; boundary="' . $boundary . '"'; } } // Avoid setting an empty $content_type. } elseif ( '' !== trim( $content ) ) { $content_type = trim( $content ); } break; case 'cc': $cc = array_merge( (array) $cc, explode( ',', $content ) ); break; case 'bcc': $bcc = array_merge( (array) $bcc, explode( ',', $content ) ); break; case 'reply-to': $reply_to = array_merge( (array) $reply_to, explode( ',', $content ) ); break; default: // Add it to our grand headers array. $headers[ trim( $name ) ] = trim( $content ); break; } } } } // Empty out the values that may be set. $phpmailer->clearAllRecipients(); $phpmailer->clearAttachments(); $phpmailer->clearCustomHeaders(); $phpmailer->clearReplyTos(); $phpmailer->Body = ''; $phpmailer->AltBody = ''; /* * Reset encoding to 8-bit, as it may have been automatically downgraded * to 7-bit by PHPMailer (based on the body contents) in a previous call * to wp_mail(). * * See https://core.trac.wordpress.org/ticket/33972 */ $phpmailer->Encoding = PHPMailer\PHPMailer\PHPMailer::ENCODING_8BIT; // Set "From" name and email. // If we don't have a name from the input headers. if ( ! isset( $from_name ) ) { $from_name = 'WordPress'; } /* * If we don't have an email from the input headers, default to wordpress@$sitename * Some hosts will block outgoing mail from this address if it doesn't exist, * but there's no easy alternative. Defaulting to admin_email might appear to be * another option, but some hosts may refuse to relay mail from an unknown domain. * See https://core.trac.wordpress.org/ticket/5007. */ if ( ! isset( $from_email ) ) { // Get the site domain and get rid of www. $sitename = wp_parse_url( network_home_url(), PHP_URL_HOST ); $from_email = 'wordpress@'; if ( null !== $sitename ) { if ( str_starts_with( $sitename, 'www.' ) ) { $sitename = substr( $sitename, 4 ); } $from_email .= $sitename; } } /** * Filters the email address to send from. * * @since 2.2.0 * * @param string $from_email Email address to send from. */ $from_email = apply_filters( 'wp_mail_from', $from_email ); /** * Filters the name to associate with the "from" email address. * * @since 2.3.0 * * @param string $from_name Name associated with the "from" email address. */ $from_name = apply_filters( 'wp_mail_from_name', $from_name ); try { $phpmailer->setFrom( $from_email, $from_name, false ); } catch ( PHPMailer\PHPMailer\Exception $e ) { $mail_error_data = compact( 'to', 'subject', 'message', 'headers', 'attachments' ); $mail_error_data['phpmailer_exception_code'] = $e->getCode(); /** This filter is documented in wp-includes/pluggable.php */ do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_failed', $e->getMessage(), $mail_error_data ) ); return false; } // Set mail's subject and body. $phpmailer->Subject = $subject; $phpmailer->Body = $message; // Set destination addresses, using appropriate methods for handling addresses. $address_headers = compact( 'to', 'cc', 'bcc', 'reply_to' ); foreach ( $address_headers as $address_header => $addresses ) { if ( empty( $addresses ) ) { continue; } foreach ( (array) $addresses as $address ) { try { // Break $recipient into name and address parts if in the format "Foo <bar@baz.com>". $recipient_name = ''; if ( preg_match( '/(.*)<(.+)>/', $address, $matches ) ) { if ( count( $matches ) === 3 ) { $recipient_name = $matches[1]; $address = $matches[2]; } } switch ( $address_header ) { case 'to': $phpmailer->addAddress( $address, $recipient_name ); break; case 'cc': $phpmailer->addCC( $address, $recipient_name ); break; case 'bcc': $phpmailer->addBCC( $address, $recipient_name ); break; case 'reply_to': $phpmailer->addReplyTo( $address, $recipient_name ); break; } } catch ( PHPMailer\PHPMailer\Exception $e ) { continue; } } } // Set to use PHP's mail(). $phpmailer->isMail(); // Set Content-Type and charset. // If we don't have a Content-Type from the input headers. if ( ! isset( $content_type ) ) { $content_type = 'text/plain'; } /** * Filters the wp_mail() content type. * * @since 2.3.0 * * @param string $content_type Default wp_mail() content type. */ $content_type = apply_filters( 'wp_mail_content_type', $content_type ); $phpmailer->ContentType = $content_type; // Set whether it's plaintext, depending on $content_type. if ( 'text/html' === $content_type ) { $phpmailer->isHTML( true ); } // If we don't have a charset from the input headers. if ( ! isset( $charset ) ) { $charset = get_bloginfo( 'charset' ); } /** * Filters the default wp_mail() charset. * * @since 2.3.0 * * @param string $charset Default email charset. */ $phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset ); // Set custom headers. if ( ! empty( $headers ) ) { foreach ( (array) $headers as $name => $content ) { // Only add custom headers not added automatically by PHPMailer. if ( ! in_array( $name, array( 'MIME-Version', 'X-Mailer' ), true ) ) { try { $phpmailer->addCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) ); } catch ( PHPMailer\PHPMailer\Exception $e ) { continue; } } } } if ( ! empty( $attachments ) ) { foreach ( $attachments as $filename => $attachment ) { $filename = is_string( $filename ) ? $filename : ''; try { $phpmailer->addAttachment( $attachment, $filename ); } catch ( PHPMailer\PHPMailer\Exception $e ) { continue; } } } if ( ! empty( $embeds ) ) { foreach ( $embeds as $key => $embed_path ) { /** * Filters the arguments for PHPMailer's addEmbeddedImage() method. * * @since 6.9.0 * * @param array $args { * An array of arguments for `addEmbeddedImage()`. * @type string $path The path to the file. * @type string $cid The Content-ID of the image. Default: The key in the embeds array. * @type string $name The filename of the image. * @type string $encoding The encoding of the image. Default: 'base64'. * @type string $type The MIME type of the image. Default: empty string, which lets PHPMailer auto-detect. * @type string $disposition The disposition of the image. Default: 'inline'. * } */ $embed_args = apply_filters( 'wp_mail_embed_args', array( 'path' => $embed_path, 'cid' => (string) $key, 'name' => basename( $embed_path ), 'encoding' => 'base64', 'type' => '', 'disposition' => 'inline', ) ); try { $phpmailer->addEmbeddedImage( $embed_args['path'], $embed_args['cid'], $embed_args['name'], $embed_args['encoding'], $embed_args['type'], $embed_args['disposition'] ); } catch ( PHPMailer\PHPMailer\Exception $e ) { continue; } } } /** * Fires after PHPMailer is initialized. * * @since 2.2.0 * * @param PHPMailer $phpmailer The PHPMailer instance (passed by reference). */ do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) ); $mail_data = compact( 'to', 'subject', 'message', 'headers', 'attachments', 'embeds' ); // Send! try { $send = $phpmailer->send(); /** * Fires after PHPMailer has successfully sent an email. * * The firing of this action does not necessarily mean that the recipient(s) received the * email successfully. It only means that the `send` method above was able to * process the request without any errors. * * @since 5.9.0 * * @param array $mail_data { * An array containing the email recipient(s), subject, message, headers, and attachments. * * @type string[] $to Email addresses to send message. * @type string $subject Email subject. * @type string $message Message contents. * @type string[] $headers Additional headers. * @type string[] $attachments Paths to files to attach. * @type string[] $embeds Paths to files to embed. * } */ do_action( 'wp_mail_succeeded', $mail_data ); return $send; } catch ( PHPMailer\PHPMailer\Exception $e ) { $mail_data['phpmailer_exception_code'] = $e->getCode(); /** * Fires after a PHPMailer\PHPMailer\Exception is caught. * * @since 4.4.0 * * @param WP_Error $error A WP_Error object with the PHPMailer\PHPMailer\Exception message, and an array * containing the mail recipient, subject, message, headers, and attachments. */ do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_failed', $e->getMessage(), $mail_data ) ); return false; } } endif; if ( ! function_exists( 'wp_authenticate' ) ) : /** * Authenticates a user, confirming the login credentials are valid. * * @since 2.5.0 * @since 4.5.0 `$username` now accepts an email address. * * @param string $username User's username or email address. * @param string $password User's password. * @return WP_User|WP_Error WP_User object if the credentials are valid, * otherwise WP_Error. */ function wp_authenticate( $username, #[\SensitiveParameter] $password ) { $username = sanitize_user( $username ); $password = trim( $password ); /** * Filters whether a set of user login credentials are valid. * * A WP_User object is returned if the credentials authenticate a user. * WP_Error or null otherwise. * * @since 2.8.0 * @since 4.5.0 `$username` now accepts an email address. * * @param null|WP_User|WP_Error $user WP_User if the user is authenticated. * WP_Error or null otherwise. * @param string $username Username or email address. * @param string $password User password. */ $user = apply_filters( 'authenticate', null, $username, $password ); if ( null === $user || false === $user ) { /* * TODO: What should the error message be? (Or would these even happen?) * Only needed if all authentication handlers fail to return anything. */ $user = new WP_Error( 'authentication_failed', __( '<strong>Error:</strong> Invalid username, email address or incorrect password.' ) ); } $ignore_codes = array( 'empty_username', 'empty_password' ); if ( is_wp_error( $user ) && ! in_array( $user->get_error_code(), $ignore_codes, true ) ) { $error = $user; /** * Fires after a user login has failed. * * @since 2.5.0 * @since 4.5.0 The value of `$username` can now be an email address. * @since 5.4.0 The `$error` parameter was added. * * @param string $username Username or email address. * @param WP_Error $error A WP_Error object with the authentication failure details. */ do_action( 'wp_login_failed', $username, $error ); } return $user; } endif; if ( ! function_exists( 'wp_logout' ) ) : /** * Logs the current user out. * * @since 2.5.0 */ function wp_logout() { $user_id = get_current_user_id(); wp_destroy_current_session(); wp_clear_auth_cookie(); wp_set_current_user( 0 ); /** * Fires after a user is logged out. * * @since 1.5.0 * @since 5.5.0 Added the `$user_id` parameter. * * @param int $user_id ID of the user that was logged out. */ do_action( 'wp_logout', $user_id ); } endif; if ( ! function_exists( 'wp_validate_auth_cookie' ) ) : /** * Validates authentication cookie. * * The checks include making sure that the authentication cookie is set and * pulling in the contents (if $cookie is not used). * * Makes sure the cookie is not expired. Verifies the hash in cookie is what is * should be and compares the two. * * @since 2.5.0 * * @global int $login_grace_period * * @param string $cookie Optional. If used, will validate contents instead of cookie's. * @param string $scheme Optional. The cookie scheme to use: 'auth', 'secure_auth', or 'logged_in'. * Note: This does *not* default to 'auth' like other cookie functions. * @return int|false User ID if valid cookie, false if invalid. */ function wp_validate_auth_cookie( $cookie = '', $scheme = '' ) { $cookie_elements = wp_parse_auth_cookie( $cookie, $scheme ); if ( ! $cookie_elements ) { /** * Fires if an authentication cookie is malformed. * * @since 2.7.0 * * @param string $cookie Malformed auth cookie. * @param string $scheme Authentication scheme. Values include 'auth', 'secure_auth', * or 'logged_in'. */ do_action( 'auth_cookie_malformed', $cookie, $scheme ); return false; } $scheme = $cookie_elements['scheme']; $username = $cookie_elements['username']; $hmac = $cookie_elements['hmac']; $token = $cookie_elements['token']; $expiration = $cookie_elements['expiration']; $expired = (int) $expiration; // Allow a grace period for POST and Ajax requests. if ( wp_doing_ajax() || 'POST' === $_SERVER['REQUEST_METHOD'] ) { $expired += HOUR_IN_SECONDS; } // Quick check to see if an honest cookie has expired. if ( $expired < time() ) { /** * Fires once an authentication cookie has expired. * * @since 2.7.0 * * @param string[] $cookie_elements { * Authentication cookie components. None of the components should be assumed * to be valid as they come directly from a client-provided cookie value. * * @type string $username User's username. * @type string $expiration The time the cookie expires as a UNIX timestamp. * @type string $token User's session token used. * @type string $hmac The security hash for the cookie. * @type string $scheme The cookie scheme to use. * } */ do_action( 'auth_cookie_expired', $cookie_elements ); return false; } $user = get_user_by( 'login', $username ); if ( ! $user ) { /** * Fires if a bad username is entered in the user authentication process. * * @since 2.7.0 * * @param string[] $cookie_elements { * Authentication cookie components. None of the components should be assumed * to be valid as they come directly from a client-provided cookie value. * * @type string $username User's username. * @type string $expiration The time the cookie expires as a UNIX timestamp. * @type string $token User's session token used. * @type string $hmac The security hash for the cookie. * @type string $scheme The cookie scheme to use. * } */ do_action( 'auth_cookie_bad_username', $cookie_elements ); return false; } if ( str_starts_with( $user->user_pass, '$P$' ) || str_starts_with( $user->user_pass, '$2y$' ) ) { // Retain previous behaviour of phpass or vanilla bcrypt hashed passwords. $pass_frag = substr( $user->user_pass, 8, 4 ); } else { // Otherwise, use a substring from the end of the hash to avoid dealing with potentially long hash prefixes. $pass_frag = substr( $user->user_pass, -4 ); } $key = wp_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme ); $hash = hash_hmac( 'sha256', $username . '|' . $expiration . '|' . $token, $key ); if ( ! hash_equals( $hash, $hmac ) ) { /** * Fires if a bad authentication cookie hash is encountered. * * @since 2.7.0 * * @param string[] $cookie_elements { * Authentication cookie components. None of the components should be assumed * to be valid as they come directly from a client-provided cookie value. * * @type string $username User's username. * @type string $expiration The time the cookie expires as a UNIX timestamp. * @type string $token User's session token used. * @type string $hmac The security hash for the cookie. * @type string $scheme The cookie scheme to use. * } */ do_action( 'auth_cookie_bad_hash', $cookie_elements ); return false; } $manager = WP_Session_Tokens::get_instance( $user->ID ); if ( ! $manager->verify( $token ) ) { /** * Fires if a bad session token is encountered. * * @since 4.0.0 * * @param string[] $cookie_elements { * Authentication cookie components. None of the components should be assumed * to be valid as they come directly from a client-provided cookie value. * * @type string $username User's username. * @type string $expiration The time the cookie expires as a UNIX timestamp. * @type string $token User's session token used. * @type string $hmac The security hash for the cookie. * @type string $scheme The cookie scheme to use. * } */ do_action( 'auth_cookie_bad_session_token', $cookie_elements ); return false; } // Ajax/POST grace period set above. if ( $expiration < time() ) { $GLOBALS['login_grace_period'] = 1; } /** * Fires once an authentication cookie has been validated. * * @since 2.7.0 * * @param string[] $cookie_elements { * Authentication cookie components. * * @type string $username User's username. * @type string $expiration The time the cookie expires as a UNIX timestamp. * @type string $token User's session token used. * @type string $hmac The security hash for the cookie. * @type string $scheme The cookie scheme to use. * } * @param WP_User $user User object. */ do_action( 'auth_cookie_valid', $cookie_elements, $user ); return $user->ID; } endif; if ( ! function_exists( 'wp_generate_auth_cookie' ) ) : /** * Generates authentication cookie contents. * * @since 2.5.0 * @since 4.0.0 The `$token` parameter was added. * * @param int $user_id User ID. * @param int $expiration The time the cookie expires as a UNIX timestamp. * @param string $scheme Optional. The cookie scheme to use: 'auth', 'secure_auth', or 'logged_in'. * Default 'auth'. * @param string $token User's session token to use for this cookie. * @return string Authentication cookie contents. Empty string if user does not exist. */ function wp_generate_auth_cookie( $user_id, $expiration, $scheme = 'auth', $token = '' ) { $user = get_userdata( $user_id ); if ( ! $user ) { return ''; } if ( ! $token ) { $manager = WP_Session_Tokens::get_instance( $user_id ); $token = $manager->create( $expiration ); } if ( str_starts_with( $user->user_pass, '$P$' ) || str_starts_with( $user->user_pass, '$2y$' ) ) { // Retain previous behaviour of phpass or vanilla bcrypt hashed passwords. $pass_frag = substr( $user->user_pass, 8, 4 ); } else { // Otherwise, use a substring from the end of the hash to avoid dealing with potentially long hash prefixes. $pass_frag = substr( $user->user_pass, -4 ); } $key = wp_hash( $user->user_login . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme ); $hash = hash_hmac( 'sha256', $user->user_login . '|' . $expiration . '|' . $token, $key ); $cookie = $user->user_login . '|' . $expiration . '|' . $token . '|' . $hash; /** * Filters the authentication cookie. * * @since 2.5.0 * @since 4.0.0 The `$token` parameter was added. * * @param string $cookie Authentication cookie. * @param int $user_id User ID. * @param int $expiration The time the cookie expires as a UNIX timestamp. * @param string $scheme Cookie scheme used. Accepts 'auth', 'secure_auth', or 'logged_in'. * @param string $token User's session token used. */ return apply_filters( 'auth_cookie', $cookie, $user_id, $expiration, $scheme, $token ); } endif; if ( ! function_exists( 'wp_parse_auth_cookie' ) ) : /** * Parses a cookie into its components. * * @since 2.7.0 * @since 4.0.0 The `$token` element was added to the return value. * * @param string $cookie Authentication cookie. * @param string $scheme Optional. The cookie scheme to use: 'auth', 'secure_auth', or 'logged_in'. * @return string[]|false { * Authentication cookie components. None of the components should be assumed * to be valid as they come directly from a client-provided cookie value. If * the cookie value is malformed, false is returned. * * @type string $username User's username. * @type string $expiration The time the cookie expires as a UNIX timestamp. * @type string $token User's session token used. * @type string $hmac The security hash for the cookie. * @type string $scheme The cookie scheme to use. * } */ function wp_parse_auth_cookie( $cookie = '', $scheme = '' ) { if ( empty( $cookie ) ) { switch ( $scheme ) { case 'auth': $cookie_name = AUTH_COOKIE; break; case 'secure_auth': $cookie_name = SECURE_AUTH_COOKIE; break; case 'logged_in': $cookie_name = LOGGED_IN_COOKIE; break; default: if ( is_ssl() ) { $cookie_name = SECURE_AUTH_COOKIE; $scheme = 'secure_auth'; } else { $cookie_name = AUTH_COOKIE; $scheme = 'auth'; } } if ( empty( $_COOKIE[ $cookie_name ] ) ) { return false; } $cookie = $_COOKIE[ $cookie_name ]; } $cookie_elements = explode( '|', $cookie ); if ( count( $cookie_elements ) !== 4 ) { return false; } list( $username, $expiration, $token, $hmac ) = $cookie_elements; return compact( 'username', 'expiration', 'token', 'hmac', 'scheme' ); } endif; if ( ! function_exists( 'wp_set_auth_cookie' ) ) : /** * Sets the authentication cookies for a given user ID. * * The `$remember` parameter controls cookie persistence: * - If true, the cookie is persistent (default 14 days, filterable via {@see 'auth_cookie_expiration'}). * - If false, the cookie is a browser session cookie (expires when the browser closes). * Internally, {@see 'auth_cookie_expiration'} is still applied, to expire the login after * two days or when the browser is closed, whichever occurs first. * * @since 2.5.0 * @since 4.3.0 Added the `$token` parameter. * * @param int $user_id User ID. * @param bool $remember Whether to remember the user. * @param bool|string $secure Whether the auth cookie should only be sent over HTTPS. Default is an empty * string which means the value of `is_ssl()` will be used. * @param string $token Optional. User's session token to use for this cookie. */ function wp_set_auth_cookie( $user_id, $remember = false, $secure = '', $token = '' ) { if ( $remember ) { /** * Filters the duration of the authentication cookie expiration period. * * @since 2.8.0 * * @param int $length Duration of the expiration period in seconds. * @param int $user_id User ID. * @param bool $remember Whether to remember the user login. Default false. */ $expiration = time() + apply_filters( 'auth_cookie_expiration', 14 * DAY_IN_SECONDS, $user_id, $remember ); /* * Ensure the browser will continue to send the cookie after the expiration time is reached. * Needed for the login grace period in wp_validate_auth_cookie(). */ $expire = $expiration + ( 12 * HOUR_IN_SECONDS ); } else { /** This filter is documented in wp-includes/pluggable.php */ $expiration = time() + apply_filters( 'auth_cookie_expiration', 2 * DAY_IN_SECONDS, $user_id, $remember ); $expire = 0; } if ( '' === $secure ) { $secure = is_ssl(); } // Front-end cookie is secure when the auth cookie is secure and the site's home URL uses HTTPS. $secure_logged_in_cookie = $secure && 'https' === parse_url( get_option( 'home' ), PHP_URL_SCHEME ); /** * Filters whether the auth cookie should only be sent over HTTPS. * * @since 3.1.0 * * @param bool $secure Whether the cookie should only be sent over HTTPS. * @param int $user_id User ID. */ $secure = apply_filters( 'secure_auth_cookie', $secure, $user_id ); /** * Filters whether the logged in cookie should only be sent over HTTPS. * * @since 3.1.0 * * @param bool $secure_logged_in_cookie Whether the logged in cookie should only be sent over HTTPS. * @param int $user_id User ID. * @param bool $secure Whether the auth cookie should only be sent over HTTPS. */ $secure_logged_in_cookie = apply_filters( 'secure_logged_in_cookie', $secure_logged_in_cookie, $user_id, $secure ); if ( $secure ) { $auth_cookie_name = SECURE_AUTH_COOKIE; $scheme = 'secure_auth'; } else { $auth_cookie_name = AUTH_COOKIE; $scheme = 'auth'; } if ( '' === $token ) { $manager = WP_Session_Tokens::get_instance( $user_id ); $token = $manager->create( $expiration ); } $auth_cookie = wp_generate_auth_cookie( $user_id, $expiration, $scheme, $token ); $logged_in_cookie = wp_generate_auth_cookie( $user_id, $expiration, 'logged_in', $token ); /** * Fires immediately before the authentication cookie is set. * * @since 2.5.0 * @since 4.9.0 The `$token` parameter was added. * * @param string $auth_cookie Authentication cookie value. * @param int $expire The time the login grace period expires as a UNIX timestamp. * Default is 12 hours past the cookie's expiration time. * @param int $expiration The time when the authentication cookie expires as a UNIX timestamp. * Default is 14 days from now. * @param int $user_id User ID. * @param string $scheme Authentication scheme. Values include 'auth' or 'secure_auth'. * @param string $token User's session token to use for this cookie. */ do_action( 'set_auth_cookie', $auth_cookie, $expire, $expiration, $user_id, $scheme, $token ); /** * Fires immediately before the logged-in authentication cookie is set. * * @since 2.6.0 * @since 4.9.0 The `$token` parameter was added. * * @param string $logged_in_cookie The logged-in cookie value. * @param int $expire The time the login grace period expires as a UNIX timestamp. * Default is 12 hours past the cookie's expiration time. * @param int $expiration The time when the logged-in authentication cookie expires as a UNIX timestamp. * Default is 14 days from now. * @param int $user_id User ID. * @param string $scheme Authentication scheme. Default 'logged_in'. * @param string $token User's session token to use for this cookie. */ do_action( 'set_logged_in_cookie', $logged_in_cookie, $expire, $expiration, $user_id, 'logged_in', $token ); /** * Allows preventing auth cookies from actually being sent to the client. * * @since 4.7.4 * @since 6.2.0 The `$expire`, `$expiration`, `$user_id`, `$scheme`, and `$token` parameters were added. * * @param bool $send Whether to send auth cookies to the client. Default true. * @param int $expire The time the login grace period expires as a UNIX timestamp. * Default is 12 hours past the cookie's expiration time. Zero when clearing cookies. * @param int $expiration The time when the logged-in authentication cookie expires as a UNIX timestamp. * Default is 14 days from now. Zero when clearing cookies. * @param int $user_id User ID. Zero when clearing cookies. * @param string $scheme Authentication scheme. Values include 'auth' or 'secure_auth'. * Empty string when clearing cookies. * @param string $token User's session token to use for this cookie. Empty string when clearing cookies. */ if ( ! apply_filters( 'send_auth_cookies', true, $expire, $expiration, $user_id, $scheme, $token ) ) { return; } setcookie( $auth_cookie_name, $auth_cookie, $expire, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN, $secure, true ); setcookie( $auth_cookie_name, $auth_cookie, $expire, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, $secure, true ); setcookie( LOGGED_IN_COOKIE, $logged_in_cookie, $expire, COOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true ); if ( COOKIEPATH !== SITECOOKIEPATH ) { setcookie( LOGGED_IN_COOKIE, $logged_in_cookie, $expire, SITECOOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true ); } } endif; if ( ! function_exists( 'wp_clear_auth_cookie' ) ) : /** * Removes all of the cookies associated with authentication. * * @since 2.5.0 */ function wp_clear_auth_cookie() { /** * Fires just before the authentication cookies are cleared. * * @since 2.7.0 */ do_action( 'clear_auth_cookie' ); /** This filter is documented in wp-includes/pluggable.php */ if ( ! apply_filters( 'send_auth_cookies', true, 0, 0, 0, '', '' ) ) { return; } // Auth cookies. setcookie( AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN ); setcookie( SECURE_AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN ); setcookie( AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN ); setcookie( SECURE_AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN ); setcookie( LOGGED_IN_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); setcookie( LOGGED_IN_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN ); // Settings cookies. setcookie( 'wp-settings-' . get_current_user_id(), ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH ); setcookie( 'wp-settings-time-' . get_current_user_id(), ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH ); // Old cookies. setcookie( AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); setcookie( AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN ); setcookie( SECURE_AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); setcookie( SECURE_AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN ); // Even older cookies. setcookie( USER_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); setcookie( PASS_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); setcookie( USER_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN ); setcookie( PASS_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN ); // Post password cookie. setcookie( 'wp-postpass_' . COOKIEHASH, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); } endif; if ( ! function_exists( 'is_user_logged_in' ) ) : /** * Determines whether the current visitor is a logged in user. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.0.0 * * @return bool True if user is logged in, false if not logged in. */ function is_user_logged_in() { $user = wp_get_current_user(); return $user->exists(); } endif; if ( ! function_exists( 'auth_redirect' ) ) : /** * Checks if a user is logged in, if not it redirects them to the login page. * * When this code is called from a page, it checks to see if the user viewing the page is logged in. * If the user is not logged in, they are redirected to the login page. The user is redirected * in such a way that, upon logging in, they will be sent directly to the page they were originally * trying to access. * * @since 1.5.0 */ function auth_redirect() { $secure = ( is_ssl() || force_ssl_admin() ); /** * Filters whether to use a secure authentication redirect. * * @since 3.1.0 * * @param bool $secure Whether to use a secure authentication redirect. Default false. */ $secure = apply_filters( 'secure_auth_redirect', $secure ); // If https is required and request is http, redirect. if ( $secure && ! is_ssl() && str_contains( $_SERVER['REQUEST_URI'], 'wp-admin' ) ) { if ( str_starts_with( $_SERVER['REQUEST_URI'], 'http' ) ) { wp_redirect( set_url_scheme( $_SERVER['REQUEST_URI'], 'https' ) ); exit; } else { wp_redirect( 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); exit; } } /** * Filters the authentication redirect scheme. * * @since 2.9.0 * * @param string $scheme Authentication redirect scheme. Default empty. */ $scheme = apply_filters( 'auth_redirect_scheme', '' ); $user_id = wp_validate_auth_cookie( '', $scheme ); if ( $user_id ) { /** * Fires before the authentication redirect. * * @since 2.8.0 * * @param int $user_id User ID. */ do_action( 'auth_redirect', $user_id ); // If the user wants ssl but the session is not ssl, redirect. if ( ! $secure && get_user_option( 'use_ssl', $user_id ) && str_contains( $_SERVER['REQUEST_URI'], 'wp-admin' ) ) { if ( str_starts_with( $_SERVER['REQUEST_URI'], 'http' ) ) { wp_redirect( set_url_scheme( $_SERVER['REQUEST_URI'], 'https' ) ); exit; } else { wp_redirect( 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); exit; } } return; // The cookie is good, so we're done. } // The cookie is no good, so force login. nocache_headers(); if ( str_contains( $_SERVER['REQUEST_URI'], '/options.php' ) && wp_get_referer() ) { $redirect = wp_get_referer(); } else { $redirect = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); } $login_url = wp_login_url( $redirect, true ); wp_redirect( $login_url ); exit; } endif; if ( ! function_exists( 'check_admin_referer' ) ) : /** * Ensures intent by verifying that a user was referred from another admin page with the correct security nonce. * * This function ensures the user intends to perform a given action, which helps protect against clickjacking style * attacks. It verifies intent, not authorization, therefore it does not verify the user's capabilities. This should * be performed with `current_user_can()` or similar. * * If the nonce value is invalid, the function will exit with an "Are You Sure?" style message. * * @since 1.2.0 * @since 2.5.0 The `$query_arg` parameter was added. * * @param int|string $action The nonce action. * @param string $query_arg Optional. Key to check for nonce in `$_REQUEST`. Default '_wpnonce'. * @return int|false 1 if the nonce is valid and generated between 0-12 hours ago, * 2 if the nonce is valid and generated between 12-24 hours ago. * False if the nonce is invalid. */ function check_admin_referer( $action = -1, $query_arg = '_wpnonce' ) { if ( -1 === $action ) { _doing_it_wrong( __FUNCTION__, __( 'You should specify an action to be verified by using the first parameter.' ), '3.2.0' ); } $adminurl = strtolower( admin_url() ); $referer = strtolower( wp_get_referer() ); $result = isset( $_REQUEST[ $query_arg ] ) ? wp_verify_nonce( $_REQUEST[ $query_arg ], $action ) : false; /** * Fires once the admin request has been validated or not. * * @since 1.5.1 * * @param string $action The nonce action. * @param false|int $result False if the nonce is invalid, 1 if the nonce is valid and generated between * 0-12 hours ago, 2 if the nonce is valid and generated between 12-24 hours ago. */ do_action( 'check_admin_referer', $action, $result ); if ( ! $result && ! ( -1 === $action && str_starts_with( $referer, $adminurl ) ) ) { wp_nonce_ays( $action ); die(); } return $result; } endif; if ( ! function_exists( 'check_ajax_referer' ) ) : /** * Verifies the Ajax request to prevent processing requests external of the blog. * * @since 2.0.3 * * @param int|string $action Action nonce. * @param false|string $query_arg Optional. Key to check for the nonce in `$_REQUEST` (since 2.5). If false, * `$_REQUEST` values will be evaluated for '_ajax_nonce', and '_wpnonce' * (in that order). Default false. * @param bool $stop Optional. Whether to stop early when the nonce cannot be verified. * Default true. * @return int|false 1 if the nonce is valid and generated between 0-12 hours ago, * 2 if the nonce is valid and generated between 12-24 hours ago. * False if the nonce is invalid. */ function check_ajax_referer( $action = -1, $query_arg = false, $stop = true ) { if ( -1 === $action ) { _doing_it_wrong( __FUNCTION__, __( 'You should specify an action to be verified by using the first parameter.' ), '4.7.0' ); } $nonce = ''; if ( $query_arg && isset( $_REQUEST[ $query_arg ] ) ) { $nonce = $_REQUEST[ $query_arg ]; } elseif ( isset( $_REQUEST['_ajax_nonce'] ) ) { $nonce = $_REQUEST['_ajax_nonce']; } elseif ( isset( $_REQUEST['_wpnonce'] ) ) { $nonce = $_REQUEST['_wpnonce']; } $result = wp_verify_nonce( $nonce, $action ); /** * Fires once the Ajax request has been validated or not. * * @since 2.1.0 * * @param string $action The Ajax nonce action. * @param false|int $result False if the nonce is invalid, 1 if the nonce is valid and generated between * 0-12 hours ago, 2 if the nonce is valid and generated between 12-24 hours ago. */ do_action( 'check_ajax_referer', $action, $result ); if ( $stop && false === $result ) { if ( wp_doing_ajax() ) { wp_die( -1, 403 ); } else { die( '-1' ); } } return $result; } endif; if ( ! function_exists( 'wp_redirect' ) ) : /** * Redirects to another page. * * Note: wp_redirect() does not exit automatically, and should almost always be * followed by a call to `exit;`: * * wp_redirect( $url ); * exit; * * Exiting can also be selectively manipulated by using wp_redirect() as a conditional * in conjunction with the {@see 'wp_redirect'} and {@see 'wp_redirect_status'} filters: * * if ( wp_redirect( $url ) ) { * exit; * } * * @since 1.5.1 * @since 5.1.0 The `$x_redirect_by` parameter was added. * @since 5.4.0 On invalid status codes, wp_die() is called. * * @global bool $is_IIS * * @param string $location The path or URL to redirect to. * @param int $status Optional. HTTP response status code to use. Default '302' (Moved Temporarily). * @param string|false $x_redirect_by Optional. The application doing the redirect or false to omit. Default 'WordPress'. * @return bool False if the redirect was canceled, true otherwise. */ function wp_redirect( $location, $status = 302, $x_redirect_by = 'WordPress' ) { global $is_IIS; /** * Filters the redirect location. * * @since 2.1.0 * * @param string $location The path or URL to redirect to. * @param int $status The HTTP response status code to use. */ $location = apply_filters( 'wp_redirect', $location, $status ); /** * Filters the redirect HTTP response status code to use. * * @since 2.3.0 * * @param int $status The HTTP response status code to use. * @param string $location The path or URL to redirect to. */ $status = apply_filters( 'wp_redirect_status', $status, $location ); if ( ! $location ) { return false; } if ( $status < 300 || 399 < $status ) { wp_die( __( 'HTTP redirect status code must be a redirection code, 3xx.' ) ); } $location = wp_sanitize_redirect( $location ); if ( ! $is_IIS && 'cgi-fcgi' !== PHP_SAPI ) { status_header( $status ); // This causes problems on IIS and some FastCGI setups. } /** * Filters the X-Redirect-By header. * * Allows applications to identify themselves when they're doing a redirect. * * @since 5.1.0 * * @param string|false $x_redirect_by The application doing the redirect or false to omit the header. * @param int $status Status code to use. * @param string $location The path to redirect to. */ $x_redirect_by = apply_filters( 'x_redirect_by', $x_redirect_by, $status, $location ); if ( is_string( $x_redirect_by ) ) { header( "X-Redirect-By: $x_redirect_by" ); } header( "Location: $location", true, $status ); return true; } endif; if ( ! function_exists( 'wp_sanitize_redirect' ) ) : /** * Sanitizes a URL for use in a redirect. * * @since 2.3.0 * * @param string $location The path to redirect to. * @return string Redirect-sanitized URL. */ function wp_sanitize_redirect( $location ) { // Encode spaces. $location = str_replace( ' ', '%20', $location ); $regex = '/ ( (?: [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 | [\xE1-\xEC][\x80-\xBF]{2} | \xED[\x80-\x9F][\x80-\xBF] | [\xEE-\xEF][\x80-\xBF]{2} | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 | [\xF1-\xF3][\x80-\xBF]{3} | \xF4[\x80-\x8F][\x80-\xBF]{2} ){1,40} # ...one or more times )/x'; $location = preg_replace_callback( $regex, '_wp_sanitize_utf8_in_redirect', $location ); $location = preg_replace( '|[^a-z0-9-~+_.?#=&;,/:%!*\[\]()@]|i', '', $location ); $location = wp_kses_no_null( $location ); // Remove %0D and %0A from location. $strip = array( '%0d', '%0a', '%0D', '%0A' ); return _deep_replace( $strip, $location ); } /** * URL encodes UTF-8 characters in a URL. * * @ignore * @since 4.2.0 * @access private * * @see wp_sanitize_redirect() * * @param array $matches RegEx matches against the redirect location. * @return string URL-encoded version of the first RegEx match. */ function _wp_sanitize_utf8_in_redirect( $matches ) { return urlencode( $matches[0] ); } endif; if ( ! function_exists( 'wp_safe_redirect' ) ) : /** * Performs a safe (local) redirect, using wp_redirect(). * * Checks whether the $location is using an allowed host, if it has an absolute * path. A plugin can therefore set or remove allowed host(s) to or from the * list. * * If the host is not allowed, then the redirect defaults to wp-admin on the siteurl * instead. This prevents malicious redirects which redirect to another host, * but only used in a few places. * * Note: wp_safe_redirect() does not exit automatically, and should almost always be * followed by a call to `exit;`: * * wp_safe_redirect( $url ); * exit; * * Exiting can also be selectively manipulated by using wp_safe_redirect() as a conditional * in conjunction with the {@see 'wp_redirect'} and {@see 'wp_redirect_status'} filters: * * if ( wp_safe_redirect( $url ) ) { * exit; * } * * @since 2.3.0 * @since 5.1.0 The return value from wp_redirect() is now passed on, and the `$x_redirect_by` parameter was added. * * @param string $location The path or URL to redirect to. * @param int $status Optional. HTTP response status code to use. Default '302' (Moved Temporarily). * @param string|false $x_redirect_by Optional. The application doing the redirect or false to omit. Default 'WordPress'. * @return bool False if the redirect was canceled, true otherwise. */ function wp_safe_redirect( $location, $status = 302, $x_redirect_by = 'WordPress' ) { // Need to look at the URL the way it will end up in wp_redirect(). $location = wp_sanitize_redirect( $location ); /** * Filters the redirect fallback URL for when the provided redirect is not safe (local). * * @since 4.3.0 * * @param string $fallback_url The fallback URL to use by default. * @param int $status The HTTP response status code to use. */ $fallback_url = apply_filters( 'wp_safe_redirect_fallback', admin_url(), $status ); $location = wp_validate_redirect( $location, $fallback_url ); return wp_redirect( $location, $status, $x_redirect_by ); } endif; if ( ! function_exists( 'wp_validate_redirect' ) ) : /** * Validates a URL for use in a redirect. * * Checks whether the $location is using an allowed host, if it has an absolute * path. A plugin can therefore set or remove allowed host(s) to or from the * list. * * If the host is not allowed, then the redirect is to $fallback_url supplied. * * @since 2.8.1 * * @param string $location The redirect to validate. * @param string $fallback_url The value to return if $location is not allowed. * @return string Redirect-sanitized URL. */ function wp_validate_redirect( $location, $fallback_url = '' ) { $location = wp_sanitize_redirect( trim( $location, " \t\n\r\0\x08\x0B" ) ); // Browsers will assume 'http' is your protocol, and will obey a redirect to a URL starting with '//'. if ( str_starts_with( $location, '//' ) ) { $location = 'http:' . $location; } /* * In PHP 5 parse_url() may fail if the URL query part contains 'http://'. * See https://bugs.php.net/bug.php?id=38143 */ $cut = strpos( $location, '?' ); $test = $cut ? substr( $location, 0, $cut ) : $location; $lp = parse_url( $test ); // Give up if malformed URL. if ( false === $lp ) { return $fallback_url; } // Allow only 'http' and 'https' schemes. No 'data:', etc. if ( isset( $lp['scheme'] ) && ! ( 'http' === $lp['scheme'] || 'https' === $lp['scheme'] ) ) { return $fallback_url; } if ( ! isset( $lp['host'] ) && ! empty( $lp['path'] ) && '/' !== $lp['path'][0] ) { $path = ''; if ( ! empty( $_SERVER['REQUEST_URI'] ) ) { $path = dirname( parse_url( 'http://placeholder' . $_SERVER['REQUEST_URI'], PHP_URL_PATH ) . '?' ); $path = wp_normalize_path( $path ); } $location = '/' . ltrim( $path . '/', '/' ) . $location; } /* * Reject if certain components are set but host is not. * This catches URLs like https:host.com for which parse_url() does not set the host field. */ if ( ! isset( $lp['host'] ) && ( isset( $lp['scheme'] ) || isset( $lp['user'] ) || isset( $lp['pass'] ) || isset( $lp['port'] ) ) ) { return $fallback_url; } // Reject malformed components parse_url() can return on odd inputs. foreach ( array( 'user', 'pass', 'host' ) as $component ) { if ( isset( $lp[ $component ] ) && strpbrk( $lp[ $component ], ':/?#@' ) ) { return $fallback_url; } } $wpp = parse_url( home_url() ); /** * Filters the list of allowed hosts to redirect to. * * @since 2.3.0 * * @param string[] $hosts An array of allowed host names. * @param string $host The host name of the redirect destination; empty string if not set. */ $allowed_hosts = (array) apply_filters( 'allowed_redirect_hosts', array( $wpp['host'] ), isset( $lp['host'] ) ? $lp['host'] : '' ); if ( isset( $lp['host'] ) && ( ! in_array( $lp['host'], $allowed_hosts, true ) && strtolower( $wpp['host'] ) !== $lp['host'] ) ) { $location = $fallback_url; } return $location; } endif; if ( ! function_exists( 'wp_notify_postauthor' ) ) : /** * Notifies an author (and/or others) of a comment/trackback/pingback on a post. * * @since 1.0.0 * * @param int|WP_Comment $comment_id Comment ID or WP_Comment object. * @param string $deprecated Not used. * @return bool True on completion. False if no email addresses were specified. */ function wp_notify_postauthor( $comment_id, $deprecated = null ) { if ( null !== $deprecated ) { _deprecated_argument( __FUNCTION__, '3.8.0' ); } $comment = get_comment( $comment_id ); if ( empty( $comment ) || empty( $comment->comment_post_ID ) ) { return false; } $post = get_post( $comment->comment_post_ID ); $author = get_userdata( $post->post_author ); // Who to notify? By default, just the post author, but others can be added. $emails = array(); if ( $author ) { $emails[] = $author->user_email; } /** * Filters the list of email addresses to receive a comment notification. * * By default, only post authors are notified of comments. This filter allows * others to be added. * * @since 3.7.0 * * @param string[] $emails An array of email addresses to receive a comment notification. * @param string $comment_id The comment ID as a numeric string. */ $emails = apply_filters( 'comment_notification_recipients', $emails, $comment->comment_ID ); $emails = array_filter( $emails ); // If there are no addresses to send the comment to, bail. if ( ! count( $emails ) ) { return false; } // Facilitate unsetting below without knowing the keys. $emails = array_flip( $emails ); /** * Filters whether to notify comment authors of their comments on their own posts. * * By default, comment authors aren't notified of their comments on their own * posts. This filter allows you to override that. * * @since 3.8.0 * * @param bool $notify Whether to notify the post author of their own comment. * Default false. * @param string $comment_id The comment ID as a numeric string. */ $notify_author = apply_filters( 'comment_notification_notify_author', false, $comment->comment_ID ); // The comment was left by the author. if ( $author && ! $notify_author && (int) $comment->user_id === (int) $post->post_author ) { unset( $emails[ $author->user_email ] ); } // The author moderated a comment on their own post. if ( $author && ! $notify_author && get_current_user_id() === (int) $post->post_author ) { unset( $emails[ $author->user_email ] ); } // The post author is no longer a member of the blog. if ( $author && ! $notify_author && ! user_can( $post->post_author, 'read_post', $post->ID ) ) { unset( $emails[ $author->user_email ] ); } // If there's no email to send the comment to, bail, otherwise flip array back around for use below. if ( ! count( $emails ) ) { return false; } else { $emails = array_flip( $emails ); } $comment_author_domain = ''; if ( WP_Http::is_ip_address( $comment->comment_author_IP ) ) { $comment_author_domain = gethostbyaddr( $comment->comment_author_IP ); } /* * The blogname option is escaped with esc_html() on the way into the database in sanitize_option(). * We want to reverse this for the plain text arena of emails. */ $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); $comment_content = wp_specialchars_decode( $comment->comment_content ); $wp_email = 'wordpress@' . preg_replace( '#^www\.#', '', wp_parse_url( network_home_url(), PHP_URL_HOST ) ); if ( '' === $comment->comment_author ) { $from = "From: \"$blogname\" <$wp_email>"; if ( '' !== $comment->comment_author_email ) { $reply_to = "Reply-To: $comment->comment_author_email"; } } else { $from = "From: \"$comment->comment_author\" <$wp_email>"; if ( '' !== $comment->comment_author_email ) { $reply_to = "Reply-To: \"$comment->comment_author_email\" <$comment->comment_author_email>"; } } $message_headers = "$from\n" . 'Content-Type: text/plain; charset="' . get_option( 'blog_charset' ) . "\"\n"; if ( isset( $reply_to ) ) { $message_headers .= $reply_to . "\n"; } /** * Filters the comment notification email headers. * * @since 1.5.2 * * @param string $message_headers Headers for the comment notification email. * @param string $comment_id Comment ID as a numeric string. */ $message_headers = apply_filters( 'comment_notification_headers', $message_headers, $comment->comment_ID ); foreach ( $emails as $email ) { $user = get_user_by( 'email', $email ); if ( $user ) { $switched_locale = switch_to_user_locale( $user->ID ); } else { $switched_locale = switch_to_locale( get_locale() ); } switch ( $comment->comment_type ) { case 'trackback': /* translators: %s: Post title. */ $notify_message = sprintf( __( 'New trackback on your post "%s"' ), $post->post_title ) . "\r\n"; /* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */ $notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; /* translators: %s: Trackback/pingback/comment author URL. */ $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n"; /* translators: %s: Comment text. */ $notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n"; $notify_message .= __( 'You can see all trackbacks on this post here:' ) . "\r\n"; /* translators: Trackback notification email subject. 1: Site title, 2: Post title. */ $subject = sprintf( __( '[%1$s] Trackback: "%2$s"' ), $blogname, $post->post_title ); break; case 'pingback': /* translators: %s: Post title. */ $notify_message = sprintf( __( 'New pingback on your post "%s"' ), $post->post_title ) . "\r\n"; /* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */ $notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; /* translators: %s: Trackback/pingback/comment author URL. */ $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n"; /* translators: %s: Comment text. */ $notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n"; $notify_message .= __( 'You can see all pingbacks on this post here:' ) . "\r\n"; /* translators: Pingback notification email subject. 1: Site title, 2: Post title. */ $subject = sprintf( __( '[%1$s] Pingback: "%2$s"' ), $blogname, $post->post_title ); break; case 'note': /* translators: %s: Post title. */ $notify_message = sprintf( __( 'New note on your post "%s"' ), $post->post_title ) . "\r\n"; /* translators: 1: Note author's name, 2: Note author's IP address, 3: Note author's hostname. */ $notify_message .= sprintf( __( 'Author: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; /* translators: %s: Note author email. */ $notify_message .= sprintf( __( 'Email: %s' ), $comment->comment_author_email ) . "\r\n"; /* translators: %s: Note text. */ $notify_message .= sprintf( __( 'Note: %s' ), "\r\n" . ( empty( $comment_content ) ? __( 'resolved/reopened' ) : $comment_content ) ) . "\r\n\r\n"; $notify_message .= __( 'You can see all notes on this post here:' ) . "\r\n"; /* translators: Note notification email subject. 1: Site title, 2: Post title. */ $subject = sprintf( __( '[%1$s] Note: "%2$s"' ), $blogname, $post->post_title ); break; default: // Comments. /* translators: %s: Post title. */ $notify_message = sprintf( __( 'New comment on your post "%s"' ), $post->post_title ) . "\r\n"; /* translators: 1: Comment author's name, 2: Comment author's IP address, 3: Comment author's hostname. */ $notify_message .= sprintf( __( 'Author: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; /* translators: %s: Comment author email. */ $notify_message .= sprintf( __( 'Email: %s' ), $comment->comment_author_email ) . "\r\n"; /* translators: %s: Trackback/pingback/comment author URL. */ $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n"; if ( $comment->comment_parent && user_can( $post->post_author, 'edit_comment', $comment->comment_parent ) ) { /* translators: Comment moderation. %s: Parent comment edit URL. */ $notify_message .= sprintf( __( 'In reply to: %s' ), admin_url( "comment.php?action=editcomment&c={$comment->comment_parent}#wpbody-content" ) ) . "\r\n"; } /* translators: %s: Comment text. */ $notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n"; $notify_message .= __( 'You can see all comments on this post here:' ) . "\r\n"; /* translators: Comment notification email subject. 1: Site title, 2: Post title. */ $subject = sprintf( __( '[%1$s] Comment: "%2$s"' ), $blogname, $post->post_title ); break; } /* translators: %s: Comment URL. */ if ( 'note' === $comment->comment_type ) { $notify_message .= get_edit_post_link( $comment->comment_post_ID, 'url' ) . "\r\n"; } else { $notify_message .= get_permalink( $comment->comment_post_ID ) . "#comments\r\n\r\n"; $notify_message .= sprintf( __( 'Permalink: %s' ), get_comment_link( $comment ) ) . "\r\n"; } if ( 'note' !== $comment->comment_type && user_can( $post->post_author, 'edit_comment', $comment->comment_ID ) ) { if ( EMPTY_TRASH_DAYS ) { /* translators: Comment moderation. %s: Comment action URL. */ $notify_message .= sprintf( __( 'Trash it: %s' ), admin_url( "comment.php?action=trash&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n"; } else { /* translators: Comment moderation. %s: Comment action URL. */ $notify_message .= sprintf( __( 'Delete it: %s' ), admin_url( "comment.php?action=delete&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n"; } /* translators: Comment moderation. %s: Comment action URL. */ $notify_message .= sprintf( __( 'Spam it: %s' ), admin_url( "comment.php?action=spam&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n"; } /** * Filters the comment notification email text. * * @since 1.5.2 * * @param string $notify_message The comment notification email text. * @param string $comment_id Comment ID as a numeric string. */ $notify_message = apply_filters( 'comment_notification_text', $notify_message, $comment->comment_ID ); /** * Filters the comment notification email subject. * * @since 1.5.2 * * @param string $subject The comment notification email subject. * @param string $comment_id Comment ID as a numeric string. */ $subject = apply_filters( 'comment_notification_subject', $subject, $comment->comment_ID ); wp_mail( $email, wp_specialchars_decode( $subject ), $notify_message, $message_headers ); if ( $switched_locale ) { restore_previous_locale(); } } return true; } endif; if ( ! function_exists( 'wp_notify_moderator' ) ) : /** * Notifies the moderator of the site about a new comment that is awaiting approval. * * @since 1.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * * Uses the {@see 'notify_moderator'} filter to determine whether the site moderator * should be notified, overriding the site setting. * * @param int $comment_id Comment ID. * @return true Always returns true. */ function wp_notify_moderator( $comment_id ) { global $wpdb; $maybe_notify = get_option( 'moderation_notify' ); /** * Filters whether to send the site moderator email notifications, overriding the site setting. * * @since 4.4.0 * * @param bool $maybe_notify Whether to notify blog moderator. * @param int $comment_id The ID of the comment for the notification. */ $maybe_notify = apply_filters( 'notify_moderator', $maybe_notify, $comment_id ); if ( ! $maybe_notify ) { return true; } $comment = get_comment( $comment_id ); $post = get_post( $comment->comment_post_ID ); $user = get_userdata( $post->post_author ); // Send to the administration and to the post author if the author can modify the comment. $emails = array( get_option( 'admin_email' ) ); if ( $user && user_can( $user->ID, 'edit_comment', $comment_id ) && ! empty( $user->user_email ) ) { if ( 0 !== strcasecmp( $user->user_email, get_option( 'admin_email' ) ) ) { $emails[] = $user->user_email; } } $comment_author_domain = ''; if ( WP_Http::is_ip_address( $comment->comment_author_IP ) ) { $comment_author_domain = gethostbyaddr( $comment->comment_author_IP ); } $comments_waiting = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_approved = '0'" ); /* * The blogname option is escaped with esc_html() on the way into the database in sanitize_option(). * We want to reverse this for the plain text arena of emails. */ $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); $comment_content = wp_specialchars_decode( $comment->comment_content ); $message_headers = ''; /** * Filters the list of recipients for comment moderation emails. * * @since 3.7.0 * * @param string[] $emails List of email addresses to notify for comment moderation. * @param int $comment_id Comment ID. */ $emails = apply_filters( 'comment_moderation_recipients', $emails, $comment_id ); /** * Filters the comment moderation email headers. * * @since 2.8.0 * * @param string $message_headers Headers for the comment moderation email. * @param int $comment_id Comment ID. */ $message_headers = apply_filters( 'comment_moderation_headers', $message_headers, $comment_id ); foreach ( $emails as $email ) { $user = get_user_by( 'email', $email ); if ( $user ) { $switched_locale = switch_to_user_locale( $user->ID ); } else { $switched_locale = switch_to_locale( get_locale() ); } switch ( $comment->comment_type ) { case 'trackback': /* translators: %s: Post title. */ $notify_message = sprintf( __( 'A new trackback on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n"; $notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n"; /* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */ $notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; /* translators: %s: Trackback/pingback/comment author URL. */ $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n"; $notify_message .= __( 'Trackback excerpt: ' ) . "\r\n" . $comment_content . "\r\n\r\n"; break; case 'pingback': /* translators: %s: Post title. */ $notify_message = sprintf( __( 'A new pingback on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n"; $notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n"; /* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */ $notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; /* translators: %s: Trackback/pingback/comment author URL. */ $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n"; $notify_message .= __( 'Pingback excerpt: ' ) . "\r\n" . $comment_content . "\r\n\r\n"; break; default: // Comments. /* translators: %s: Post title. */ $notify_message = sprintf( __( 'A new comment on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n"; $notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n"; /* translators: 1: Comment author's name, 2: Comment author's IP address, 3: Comment author's hostname. */ $notify_message .= sprintf( __( 'Author: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; /* translators: %s: Comment author email. */ $notify_message .= sprintf( __( 'Email: %s' ), $comment->comment_author_email ) . "\r\n"; /* translators: %s: Trackback/pingback/comment author URL. */ $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n"; if ( $comment->comment_parent ) { /* translators: Comment moderation. %s: Parent comment edit URL. */ $notify_message .= sprintf( __( 'In reply to: %s' ), admin_url( "comment.php?action=editcomment&c={$comment->comment_parent}#wpbody-content" ) ) . "\r\n"; } /* translators: %s: Comment text. */ $notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n"; break; } /* translators: Comment moderation. %s: Comment action URL. */ $notify_message .= sprintf( __( 'Approve it: %s' ), admin_url( "comment.php?action=approve&c={$comment_id}#wpbody-content" ) ) . "\r\n"; if ( EMPTY_TRASH_DAYS ) { /* translators: Comment moderation. %s: Comment action URL. */ $notify_message .= sprintf( __( 'Trash it: %s' ), admin_url( "comment.php?action=trash&c={$comment_id}#wpbody-content" ) ) . "\r\n"; } else { /* translators: Comment moderation. %s: Comment action URL. */ $notify_message .= sprintf( __( 'Delete it: %s' ), admin_url( "comment.php?action=delete&c={$comment_id}#wpbody-content" ) ) . "\r\n"; } /* translators: Comment moderation. %s: Comment action URL. */ $notify_message .= sprintf( __( 'Spam it: %s' ), admin_url( "comment.php?action=spam&c={$comment_id}#wpbody-content" ) ) . "\r\n"; $notify_message .= sprintf( /* translators: Comment moderation. %s: Number of comments awaiting approval. */ _n( 'Currently %s comment is waiting for approval. Please visit the moderation panel:', 'Currently %s comments are waiting for approval. Please visit the moderation panel:', $comments_waiting ), number_format_i18n( $comments_waiting ) ) . "\r\n"; $notify_message .= admin_url( 'edit-comments.php?comment_status=moderated#wpbody-content' ) . "\r\n"; /* translators: Comment moderation notification email subject. 1: Site title, 2: Post title. */ $subject = sprintf( __( '[%1$s] Please moderate: "%2$s"' ), $blogname, $post->post_title ); /** * Filters the comment moderation email text. * * @since 1.5.2 * * @param string $notify_message Text of the comment moderation email. * @param int $comment_id Comment ID. */ $notify_message = apply_filters( 'comment_moderation_text', $notify_message, $comment_id ); /** * Filters the comment moderation email subject. * * @since 1.5.2 * * @param string $subject Subject of the comment moderation email. * @param int $comment_id Comment ID. */ $subject = apply_filters( 'comment_moderation_subject', $subject, $comment_id ); wp_mail( $email, wp_specialchars_decode( $subject ), $notify_message, $message_headers ); if ( $switched_locale ) { restore_previous_locale(); } } return true; } endif; if ( ! function_exists( 'wp_password_change_notification' ) ) : /** * Notifies the blog admin of a user changing password, normally via email. * * @since 2.7.0 * * @param WP_User $user User object. */ function wp_password_change_notification( $user ) { /* * Send a copy of password change notification to the admin, * but check to see if it's the admin whose password we're changing, and skip this. */ if ( 0 !== strcasecmp( $user->user_email, get_option( 'admin_email' ) ) ) { $admin_user = get_user_by( 'email', get_option( 'admin_email' ) ); if ( $admin_user ) { $switched_locale = switch_to_user_locale( $admin_user->ID ); } else { $switched_locale = switch_to_locale( get_locale() ); } /* translators: %s: User name. */ $message = sprintf( __( 'Password changed for user: %s' ), $user->user_login ) . "\r\n"; /* * The blogname option is escaped with esc_html() on the way into the database in sanitize_option(). * We want to reverse this for the plain text arena of emails. */ $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); $wp_password_change_notification_email = array( 'to' => get_option( 'admin_email' ), /* translators: Password change notification email subject. %s: Site title. */ 'subject' => __( '[%s] Password Changed' ), 'message' => $message, 'headers' => '', ); /** * Filters the contents of the password change notification email sent to the site admin. * * @since 4.9.0 * * @param array $wp_password_change_notification_email { * Used to build wp_mail(). * * @type string $to The intended recipient - site admin email address. * @type string $subject The subject of the email. * @type string $message The body of the email. * @type string $headers The headers of the email. * } * @param WP_User $user User object for user whose password was changed. * @param string $blogname The site title. */ $wp_password_change_notification_email = apply_filters( 'wp_password_change_notification_email', $wp_password_change_notification_email, $user, $blogname ); wp_mail( $wp_password_change_notification_email['to'], wp_specialchars_decode( sprintf( $wp_password_change_notification_email['subject'], $blogname ) ), $wp_password_change_notification_email['message'], $wp_password_change_notification_email['headers'] ); if ( $switched_locale ) { restore_previous_locale(); } } } endif; if ( ! function_exists( 'wp_new_user_notification' ) ) : /** * Emails login credentials to a newly-registered user. * * A new user registration notification is also sent to admin email. * * @since 2.0.0 * @since 4.3.0 The `$plaintext_pass` parameter was changed to `$notify`. * @since 4.3.1 The `$plaintext_pass` parameter was deprecated. `$notify` added as a third parameter. * @since 4.6.0 The `$notify` parameter accepts 'user' for sending notification only to the user created. * * @param int $user_id User ID. * @param null $deprecated Not used (argument deprecated). * @param string $notify Optional. Type of notification that should happen. Accepts 'admin' or an empty * string (admin only), 'user', or 'both' (admin and user). Default empty. */ function wp_new_user_notification( $user_id, $deprecated = null, $notify = '' ) { if ( null !== $deprecated ) { _deprecated_argument( __FUNCTION__, '4.3.1' ); } // Accepts only 'user', 'admin' , 'both' or default '' as $notify. if ( ! in_array( $notify, array( 'user', 'admin', 'both', '' ), true ) ) { return; } $user = get_userdata( $user_id ); /* * The blogname option is escaped with esc_html() on the way into the database in sanitize_option(). * We want to reverse this for the plain text arena of emails. */ $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); /** * Filters whether the admin is notified of a new user registration. * * @since 6.1.0 * * @param bool $send Whether to send the email. Default true. * @param WP_User $user User object for new user. */ $send_notification_to_admin = apply_filters( 'wp_send_new_user_notification_to_admin', true, $user ); if ( 'user' !== $notify && true === $send_notification_to_admin ) { $admin_user = get_user_by( 'email', get_option( 'admin_email' ) ); if ( $admin_user ) { $switched_locale = switch_to_user_locale( $admin_user->ID ); } else { $switched_locale = switch_to_locale( get_locale() ); } /* translators: %s: Site title. */ $message = sprintf( __( 'New user registration on your site %s:' ), $blogname ) . "\r\n\r\n"; /* translators: %s: User login. */ $message .= sprintf( __( 'Username: %s' ), $user->user_login ) . "\r\n\r\n"; /* translators: %s: User email address. */ $message .= sprintf( __( 'Email: %s' ), $user->user_email ) . "\r\n"; $wp_new_user_notification_email_admin = array( 'to' => get_option( 'admin_email' ), /* translators: New user registration notification email subject. %s: Site title. */ 'subject' => __( '[%s] New User Registration' ), 'message' => $message, 'headers' => '', ); /** * Filters the contents of the new user notification email sent to the site admin. * * @since 4.9.0 * * @param array $wp_new_user_notification_email_admin { * Used to build wp_mail(). * * @type string $to The intended recipient - site admin email address. * @type string $subject The subject of the email. * @type string $message The body of the email. * @type string $headers The headers of the email. * } * @param WP_User $user User object for new user. * @param string $blogname The site title. */ $wp_new_user_notification_email_admin = apply_filters( 'wp_new_user_notification_email_admin', $wp_new_user_notification_email_admin, $user, $blogname ); wp_mail( $wp_new_user_notification_email_admin['to'], wp_specialchars_decode( sprintf( $wp_new_user_notification_email_admin['subject'], $blogname ) ), $wp_new_user_notification_email_admin['message'], $wp_new_user_notification_email_admin['headers'] ); if ( $switched_locale ) { restore_previous_locale(); } } /** * Filters whether the user is notified of their new user registration. * * @since 6.1.0 * * @param bool $send Whether to send the email. Default true. * @param WP_User $user User object for new user. */ $send_notification_to_user = apply_filters( 'wp_send_new_user_notification_to_user', true, $user ); // `$deprecated` was pre-4.3 `$plaintext_pass`. An empty `$plaintext_pass` didn't sent a user notification. if ( 'admin' === $notify || true !== $send_notification_to_user || ( empty( $deprecated ) && empty( $notify ) ) ) { return; } $key = get_password_reset_key( $user ); if ( is_wp_error( $key ) ) { return; } $switched_locale = switch_to_user_locale( $user_id ); /* translators: %s: User login. */ $message = sprintf( __( 'Username: %s' ), $user->user_login ) . "\r\n\r\n"; $message .= __( 'To set your password, visit the following address:' ) . "\r\n\r\n"; /* * Since some user login names end in a period, this could produce ambiguous URLs that * end in a period. To avoid the ambiguity, ensure that the login is not the last query * arg in the URL. If moving it to the end, a trailing period will need to be escaped. * * @see https://core.trac.wordpress.org/tickets/42957 */ $message .= network_site_url( 'wp-login.php?login=' . rawurlencode( $user->user_login ) . "&key=$key&action=rp", 'login' ) . "\r\n\r\n"; $message .= wp_login_url() . "\r\n"; $wp_new_user_notification_email = array( 'to' => $user->user_email, /* translators: Login details notification email subject. %s: Site title. */ 'subject' => __( '[%s] Login Details' ), 'message' => $message, 'headers' => '', ); /** * Filters the contents of the new user notification email sent to the new user. * * @since 4.9.0 * * @param array $wp_new_user_notification_email { * Used to build wp_mail(). * * @type string $to The intended recipient - New user email address. * @type string $subject The subject of the email. * @type string $message The body of the email. * @type string $headers The headers of the email. * } * @param WP_User $user User object for new user. * @param string $blogname The site title. */ $wp_new_user_notification_email = apply_filters( 'wp_new_user_notification_email', $wp_new_user_notification_email, $user, $blogname ); wp_mail( $wp_new_user_notification_email['to'], wp_specialchars_decode( sprintf( $wp_new_user_notification_email['subject'], $blogname ) ), $wp_new_user_notification_email['message'], $wp_new_user_notification_email['headers'] ); if ( $switched_locale ) { restore_previous_locale(); } } endif; if ( ! function_exists( 'wp_nonce_tick' ) ) : /** * Returns the time-dependent variable for nonce creation. * * A nonce has a lifespan of two ticks. Nonces in their second tick may be * updated, e.g. by autosave. * * @since 2.5.0 * @since 6.1.0 Added `$action` argument. * * @param string|int $action Optional. The nonce action. Default -1. * @return float Float value rounded up to the next highest integer. */ function wp_nonce_tick( $action = -1 ) { /** * Filters the lifespan of nonces in seconds. * * @since 2.5.0 * @since 6.1.0 Added `$action` argument to allow for more targeted filters. * * @param int $lifespan Lifespan of nonces in seconds. Default 86,400 seconds, or one day. * @param string|int $action The nonce action, or -1 if none was provided. */ $nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS, $action ); return ceil( time() / ( $nonce_life / 2 ) ); } endif; if ( ! function_exists( 'wp_verify_nonce' ) ) : /** * Verifies that a correct security nonce was used with time limit. * * A nonce is valid for between 12 and 24 hours (by default). * * @since 2.0.3 * * @param string $nonce Nonce value that was used for verification, usually via a form field. * @param string|int $action Should give context to what is taking place and be the same when nonce was created. * @return int|false 1 if the nonce is valid and generated between 0-12 hours ago, * 2 if the nonce is valid and generated between 12-24 hours ago. * False if the nonce is invalid. */ function wp_verify_nonce( $nonce, $action = -1 ) { $nonce = (string) $nonce; $user = wp_get_current_user(); $uid = (int) $user->ID; if ( ! $uid ) { /** * Filters whether the user who generated the nonce is logged out. * * @since 3.5.0 * * @param int $uid ID of the nonce-owning user. * @param string|int $action The nonce action, or -1 if none was provided. */ $uid = apply_filters( 'nonce_user_logged_out', $uid, $action ); } if ( empty( $nonce ) ) { return false; } $token = wp_get_session_token(); $i = wp_nonce_tick( $action ); // Nonce generated 0-12 hours ago. $expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 ); if ( hash_equals( $expected, $nonce ) ) { return 1; } // Nonce generated 12-24 hours ago. $expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 ); if ( hash_equals( $expected, $nonce ) ) { return 2; } /** * Fires when nonce verification fails. * * @since 4.4.0 * * @param string $nonce The invalid nonce. * @param string|int $action The nonce action. * @param WP_User $user The current user object. * @param string $token The user's session token. */ do_action( 'wp_verify_nonce_failed', $nonce, $action, $user, $token ); // Invalid nonce. return false; } endif; if ( ! function_exists( 'wp_create_nonce' ) ) : /** * Creates a cryptographic token tied to a specific action, user, user session, * and window of time. * * @since 2.0.3 * @since 4.0.0 Session tokens were integrated with nonce creation. * * @param string|int $action Scalar value to add context to the nonce. * @return string The token. */ function wp_create_nonce( $action = -1 ) { $user = wp_get_current_user(); $uid = (int) $user->ID; if ( ! $uid ) { /** This filter is documented in wp-includes/pluggable.php */ $uid = apply_filters( 'nonce_user_logged_out', $uid, $action ); } $token = wp_get_session_token(); $i = wp_nonce_tick( $action ); return substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 ); } endif; if ( ! function_exists( 'wp_salt' ) ) : /** * Returns a salt to add to hashes. * * Salts are created using secret keys. Secret keys are located in two places: * in the database and in the wp-config.php file. The secret key in the database * is randomly generated and will be appended to the secret keys in wp-config.php. * * The secret keys in wp-config.php should be updated to strong, random keys to maximize * security. Below is an example of how the secret key constants are defined. * Do not paste this example directly into wp-config.php. Instead, have a * {@link https://api.wordpress.org/secret-key/1.1/salt/ secret key created} just * for you. * * define('AUTH_KEY', ' Xakm<o xQy rw4EMsLKM-?!T+,PFF})H4lzcW57AF0U@N@< >M%G4Yt>f`z]MON'); * define('SECURE_AUTH_KEY', 'LzJ}op]mr|6+![P}Ak:uNdJCJZd>(Hx.-Mh#Tz)pCIU#uGEnfFz|f ;;eU%/U^O~'); * define('LOGGED_IN_KEY', '|i|Ux`9<p-h$aFf(qnT:sDO:D1P^wZ$$/Ra@miTJi9G;ddp_<q}6H1)o|a +&JCM'); * define('NONCE_KEY', '%:R{[P|,s.KuMltH5}cI;/k<Gx~j!f0I)m_sIyu+&NJZ)-iO>z7X>QYR0Z_XnZ@|'); * define('AUTH_SALT', 'eZyT)-Naw]F8CwA*VaW#q*|.)g@o}||wf~@C-YSt}(dh_r6EbI#A,y|nU2{B#JBW'); * define('SECURE_AUTH_SALT', '!=oLUTXh,QW=H `}`L|9/^4-3 STz},T(w}W<I`.JjPi)<Bmf1v,HpGe}T1:Xt7n'); * define('LOGGED_IN_SALT', '+XSqHc;@Q*K_b|Z?NC[3H!!EONbh.n<+=uKR:>*c(u`g~EJBf#8u#R{mUEZrozmm'); * define('NONCE_SALT', 'h`GXHhD>SLWVfg1(1(N{;.V!MoE(SfbA_ksP@&`+AycHcAV$+?@3q+rxV{%^VyKT'); * * Salting passwords helps against tools which has stored hashed values of * common dictionary strings. The added values makes it harder to crack. * * @since 2.5.0 * * @link https://api.wordpress.org/secret-key/1.1/salt/ Create secrets for wp-config.php * * @param string $scheme Authentication scheme (auth, secure_auth, logged_in, nonce). * @return string Salt value */ function wp_salt( $scheme = 'auth' ) { static $cached_salts = array(); if ( isset( $cached_salts[ $scheme ] ) ) { /** * Filters the WordPress salt. * * @since 2.5.0 * * @param string $cached_salt Cached salt for the given scheme. * @param string $scheme Authentication scheme. Values include 'auth', * 'secure_auth', 'logged_in', and 'nonce'. */ return apply_filters( 'salt', $cached_salts[ $scheme ], $scheme ); } static $duplicated_keys; if ( null === $duplicated_keys ) { $duplicated_keys = array(); foreach ( array( 'AUTH', 'SECURE_AUTH', 'LOGGED_IN', 'NONCE', 'SECRET' ) as $first ) { foreach ( array( 'KEY', 'SALT' ) as $second ) { if ( ! defined( "{$first}_{$second}" ) ) { continue; } $value = constant( "{$first}_{$second}" ); $duplicated_keys[ $value ] = isset( $duplicated_keys[ $value ] ); } } $duplicated_keys['put your unique phrase here'] = true; /* * translators: This string should only be translated if wp-config-sample.php is localized. * You can check the localized release package or * https://i18n.svn.wordpress.org/<locale code>/branches/<wp version>/dist/wp-config-sample.php */ $duplicated_keys[ __( 'put your unique phrase here' ) ] = true; } /* * Determine which options to prime. * * If the salt keys are undefined, use a duplicate value or the * default `put your unique phrase here` value the salt will be * generated via `wp_generate_password()` and stored as a site * option. These options will be primed to avoid repeated * database requests for undefined salts. */ $options_to_prime = array(); foreach ( array( 'auth', 'secure_auth', 'logged_in', 'nonce' ) as $key ) { foreach ( array( 'key', 'salt' ) as $second ) { $const = strtoupper( "{$key}_{$second}" ); if ( ! defined( $const ) || true === $duplicated_keys[ constant( $const ) ] ) { $options_to_prime[] = "{$key}_{$second}"; } } } if ( ! empty( $options_to_prime ) ) { /* * Also prime `secret_key` used for undefined salting schemes. * * If the scheme is unknown, the default value for `secret_key` will be * used too for the salt. This should rarely happen, so the option is only * primed if other salts are undefined. * * At this point of execution it is known that a database call will be made * to prime salts, so the `secret_key` option can be primed regardless of the * constants status. */ $options_to_prime[] = 'secret_key'; wp_prime_site_option_caches( $options_to_prime ); } $values = array( 'key' => '', 'salt' => '', ); if ( defined( 'SECRET_KEY' ) && SECRET_KEY && empty( $duplicated_keys[ SECRET_KEY ] ) ) { $values['key'] = SECRET_KEY; } if ( 'auth' === $scheme && defined( 'SECRET_SALT' ) && SECRET_SALT && empty( $duplicated_keys[ SECRET_SALT ] ) ) { $values['salt'] = SECRET_SALT; } if ( in_array( $scheme, array( 'auth', 'secure_auth', 'logged_in', 'nonce' ), true ) ) { foreach ( array( 'key', 'salt' ) as $type ) { $const = strtoupper( "{$scheme}_{$type}" ); if ( defined( $const ) && constant( $const ) && empty( $duplicated_keys[ constant( $const ) ] ) ) { $values[ $type ] = constant( $const ); } elseif ( ! $values[ $type ] ) { $values[ $type ] = get_site_option( "{$scheme}_{$type}" ); if ( ! $values[ $type ] ) { $values[ $type ] = wp_generate_password( 64, true, true ); update_site_option( "{$scheme}_{$type}", $values[ $type ] ); } } } } else { if ( ! $values['key'] ) { $values['key'] = get_site_option( 'secret_key' ); if ( ! $values['key'] ) { $values['key'] = wp_generate_password( 64, true, true ); update_site_option( 'secret_key', $values['key'] ); } } $values['salt'] = hash_hmac( 'md5', $scheme, $values['key'] ); } $cached_salts[ $scheme ] = $values['key'] . $values['salt']; /** This filter is documented in wp-includes/pluggable.php */ return apply_filters( 'salt', $cached_salts[ $scheme ], $scheme ); } endif; if ( ! function_exists( 'wp_hash' ) ) : /** * Gets the hash of the given string. * * The default algorithm is md5 but can be changed to any algorithm supported by * `hash_hmac()`. Use the `hash_hmac_algos()` function to check the supported * algorithms. * * @since 2.0.3 * @since 6.8.0 The `$algo` parameter was added. * * @throws InvalidArgumentException if the hashing algorithm is not supported. * * @param string $data Plain text to hash. * @param string $scheme Authentication scheme (auth, secure_auth, logged_in, nonce). * @param string $algo Hashing algorithm to use. Default: 'md5'. * @return string Hash of $data. */ function wp_hash( $data, $scheme = 'auth', $algo = 'md5' ) { $salt = wp_salt( $scheme ); // Ensure the algorithm is supported by the hash_hmac function. if ( ! in_array( $algo, hash_hmac_algos(), true ) ) { throw new InvalidArgumentException( sprintf( /* translators: 1: Name of a cryptographic hash algorithm. 2: List of supported algorithms. */ __( 'Unsupported hashing algorithm: %1$s. Supported algorithms are: %2$s' ), $algo, implode( ', ', hash_hmac_algos() ) ) ); } return hash_hmac( $algo, $data, $salt ); } endif; if ( ! function_exists( 'wp_hash_password' ) ) : /** * Creates a hash of a plain text password. * * For integration with other applications, this function can be overwritten to * instead use the other package password hashing algorithm. * * @since 2.5.0 * @since 6.8.0 The password is now hashed using bcrypt by default instead of phpass. * * @global PasswordHash $wp_hasher phpass object. * * @param string $password Plain text user password to hash. * @return string The hash string of the password. */ function wp_hash_password( #[\SensitiveParameter] $password ) { global $wp_hasher; if ( ! empty( $wp_hasher ) ) { return $wp_hasher->HashPassword( trim( $password ) ); } if ( strlen( $password ) > 4096 ) { return '*'; } /** * Filters the hashing algorithm to use in the password_hash() and password_needs_rehash() functions. * * The default is the value of the `PASSWORD_BCRYPT` constant which means bcrypt is used. * * **Important:** The only password hashing algorithm that is guaranteed to be available across PHP * installations is bcrypt. If you use any other algorithm you must make sure that it is available on * the server. The `password_algos()` function can be used to check which hashing algorithms are available. * * The hashing options can be controlled via the {@see 'wp_hash_password_options'} filter. * * Other available constants include: * * - `PASSWORD_ARGON2I` * - `PASSWORD_ARGON2ID` * - `PASSWORD_DEFAULT` * * The values of the algorithm constants are strings in PHP 7.4+ and integers in PHP 7.3 and earlier. * * @since 6.8.0 * * @param string|int $algorithm The hashing algorithm. Default is the value of the `PASSWORD_BCRYPT` constant. */ $algorithm = apply_filters( 'wp_hash_password_algorithm', PASSWORD_BCRYPT ); /** * Filters the options passed to the password_hash() and password_needs_rehash() functions. * * The default hashing algorithm is bcrypt, but this can be changed via the {@see 'wp_hash_password_algorithm'} * filter. You must ensure that the options are appropriate for the algorithm in use. * * The values of the algorithm constants are strings in PHP 7.4+ and integers in PHP 7.3 and earlier. * * @since 6.8.0 * * @param array $options Array of options to pass to the password hashing functions. * By default this is an empty array which means the default * options will be used. * @param string|int $algorithm The hashing algorithm in use. */ $options = apply_filters( 'wp_hash_password_options', array(), $algorithm ); // Algorithms other than bcrypt don't need to use pre-hashing. if ( PASSWORD_BCRYPT !== $algorithm ) { return password_hash( $password, $algorithm, $options ); } // Use SHA-384 to retain entropy from a password that's longer than 72 bytes, and a `wp-sha384` key for domain separation. $password_to_hash = base64_encode( hash_hmac( 'sha384', trim( $password ), 'wp-sha384', true ) ); // Add a prefix to facilitate distinguishing vanilla bcrypt hashes. return '$wp' . password_hash( $password_to_hash, $algorithm, $options ); } endif; if ( ! function_exists( 'wp_check_password' ) ) : /** * Checks a plaintext password against a hashed password. * * Note that this function may be used to check a value that is not a user password. * A plugin may use this function to check a password of a different type, and there * may not always be a user ID associated with the password. * * For integration with other applications, this function can be overwritten to * instead use the other package password hashing algorithm. * * @since 2.5.0 * @since 6.8.0 Passwords in WordPress are now hashed with bcrypt by default. A * password that wasn't hashed with bcrypt will be checked with phpass. * * @global PasswordHash $wp_hasher phpass object. Used as a fallback for verifying * passwords that were hashed with phpass. * * @param string $password Plaintext password. * @param string $hash Hash of the password to check against. * @param string|int $user_id Optional. ID of a user associated with the password. * @return bool False, if the $password does not match the hashed password. */ function wp_check_password( #[\SensitiveParameter] $password, $hash, $user_id = '' ) { global $wp_hasher; if ( strlen( $hash ) <= 32 ) { // Check the hash using md5 regardless of the current hashing mechanism. $check = hash_equals( $hash, md5( $password ) ); } elseif ( ! empty( $wp_hasher ) ) { // Check the password using the overridden hasher. $check = $wp_hasher->CheckPassword( $password, $hash ); } elseif ( strlen( $password ) > 4096 ) { // Passwords longer than 4096 characters are not supported. $check = false; } elseif ( str_starts_with( $hash, '$wp' ) ) { // Check the password using the current prefixed hash. $password_to_verify = base64_encode( hash_hmac( 'sha384', $password, 'wp-sha384', true ) ); $check = password_verify( $password_to_verify, substr( $hash, 3 ) ); } elseif ( str_starts_with( $hash, '$P$' ) ) { // Check the password using phpass. require_once ABSPATH . WPINC . '/class-phpass.php'; $check = ( new PasswordHash( 8, true ) )->CheckPassword( $password, $hash ); } else { // Check the password using compat support for any non-prefixed hash. $check = password_verify( $password, $hash ); } /** * Filters whether the plaintext password matches the hashed password. * * @since 2.5.0 * @since 6.8.0 Passwords are now hashed with bcrypt by default. * Old passwords may still be hashed with phpass or md5. * * @param bool $check Whether the passwords match. * @param string $password The plaintext password. * @param string $hash The hashed password. * @param string|int $user_id Optional ID of a user associated with the password. * Can be empty. */ return apply_filters( 'check_password', $check, $password, $hash, $user_id ); } endif; if ( ! function_exists( 'wp_password_needs_rehash' ) ) : /** * Checks whether a password hash needs to be rehashed. * * Passwords are hashed with bcrypt using the default cost. A password hashed in a prior version * of WordPress may still be hashed with phpass and will need to be rehashed. If the default cost * or algorithm is changed in PHP or WordPress then a password hashed in a previous version will * need to be rehashed. * * Note that, just like wp_check_password(), this function may be used to check a value that is * not a user password. A plugin may use this function to check a password of a different type, * and there may not always be a user ID associated with the password. * * @since 6.8.0 * * @global PasswordHash $wp_hasher phpass object. * * @param string $hash Hash of a password to check. * @param string|int $user_id Optional. ID of a user associated with the password. * @return bool Whether the hash needs to be rehashed. */ function wp_password_needs_rehash( $hash, $user_id = '' ) { global $wp_hasher; if ( ! empty( $wp_hasher ) ) { return false; } /** This filter is documented in wp-includes/pluggable.php */ $algorithm = apply_filters( 'wp_hash_password_algorithm', PASSWORD_BCRYPT ); /** This filter is documented in wp-includes/pluggable.php */ $options = apply_filters( 'wp_hash_password_options', array(), $algorithm ); $prefixed = str_starts_with( $hash, '$wp' ); if ( ( PASSWORD_BCRYPT === $algorithm ) && ! $prefixed ) { // If bcrypt is in use and the hash is not prefixed then it needs to be rehashed. $needs_rehash = true; } else { // Otherwise check the hash minus its prefix if necessary. $hash_to_check = $prefixed ? substr( $hash, 3 ) : $hash; $needs_rehash = password_needs_rehash( $hash_to_check, $algorithm, $options ); } /** * Filters whether the password hash needs to be rehashed. * * @since 6.8.0 * * @param bool $needs_rehash Whether the password hash needs to be rehashed. * @param string $hash The password hash. * @param string|int $user_id Optional. ID of a user associated with the password. */ return apply_filters( 'password_needs_rehash', $needs_rehash, $hash, $user_id ); } endif; if ( ! function_exists( 'wp_generate_password' ) ) : /** * Generates a random password drawn from the defined set of characters. * * Uses wp_rand() to create passwords with far less predictability * than similar native PHP functions like `rand()` or `mt_rand()`. * * @since 2.5.0 * * @param int $length Optional. The length of password to generate. Default 12. * @param bool $special_chars Optional. Whether to include standard special characters. * Default true. * @param bool $extra_special_chars Optional. Whether to include other special characters. * Used when generating secret keys and salts. Default false. * @return string The random password. */ function wp_generate_password( $length = 12, $special_chars = true, $extra_special_chars = false ) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; if ( $special_chars ) { $chars .= '!@#$%^&*()'; } if ( $extra_special_chars ) { $chars .= '-_ []{}<>~`+=,.;:/?|'; } $password = ''; for ( $i = 0; $i < $length; $i++ ) { $password .= substr( $chars, wp_rand( 0, strlen( $chars ) - 1 ), 1 ); } /** * Filters the randomly-generated password. * * @since 3.0.0 * @since 5.3.0 Added the `$length`, `$special_chars`, and `$extra_special_chars` parameters. * * @param string $password The generated password. * @param int $length The length of password to generate. * @param bool $special_chars Whether to include standard special characters. * @param bool $extra_special_chars Whether to include other special characters. */ return apply_filters( 'random_password', $password, $length, $special_chars, $extra_special_chars ); } endif; if ( ! function_exists( 'wp_rand' ) ) : /** * Generates a random non-negative number. * * @since 2.6.2 * @since 4.4.0 Uses PHP7 random_int() or the random_compat library if available. * @since 6.1.0 Returns zero instead of a random number if both `$min` and `$max` are zero. * * @global string $rnd_value * * @param int $min Optional. Lower limit for the generated number. * Accepts positive integers or zero. Defaults to 0. * @param int $max Optional. Upper limit for the generated number. * Accepts positive integers. Defaults to 4294967295. * @return int A random non-negative number between min and max. */ function wp_rand( $min = null, $max = null ) { global $rnd_value; /* * Some misconfigured 32-bit environments (Entropy PHP, for example) * truncate integers larger than PHP_INT_MAX to PHP_INT_MAX rather than overflowing them to floats. */ $max_random_number = 3000000000 === 2147483647 ? (float) '4294967295' : 4294967295; // 4294967295 = 0xffffffff if ( null === $min ) { $min = 0; } if ( null === $max ) { $max = $max_random_number; } // We only handle ints, floats are truncated to their integer value. $min = (int) $min; $max = (int) $max; // Use PHP's CSPRNG, or a compatible method. static $use_random_int_functionality = true; if ( $use_random_int_functionality ) { try { // wp_rand() can accept arguments in either order, PHP cannot. $_max = max( $min, $max ); $_min = min( $min, $max ); $val = random_int( $_min, $_max ); if ( false !== $val ) { return absint( $val ); } else { $use_random_int_functionality = false; } } catch ( Error $e ) { $use_random_int_functionality = false; } catch ( Exception $e ) { $use_random_int_functionality = false; } } /* * Reset $rnd_value after 14 uses. * 32 (md5) + 40 (sha1) + 40 (sha1) / 8 = 14 random numbers from $rnd_value. */ if ( strlen( $rnd_value ) < 8 ) { if ( defined( 'WP_SETUP_CONFIG' ) ) { static $seed = ''; } else { $seed = get_transient( 'random_seed' ); } $rnd_value = md5( uniqid( microtime() . mt_rand(), true ) . $seed ); $rnd_value .= sha1( $rnd_value ); $rnd_value .= sha1( $rnd_value . $seed ); $seed = md5( $seed . $rnd_value ); if ( ! defined( 'WP_SETUP_CONFIG' ) && ! defined( 'WP_INSTALLING' ) ) { set_transient( 'random_seed', $seed ); } } // Take the first 8 digits for our value. $value = substr( $rnd_value, 0, 8 ); // Strip the first eight, leaving the remainder for the next call to wp_rand(). $rnd_value = substr( $rnd_value, 8 ); $value = abs( hexdec( $value ) ); // Reduce the value to be within the min - max range. $value = $min + ( $max - $min + 1 ) * $value / ( $max_random_number + 1 ); return abs( (int) $value ); } endif; if ( ! function_exists( 'wp_set_password' ) ) : /** * Updates the user's password with a new hashed one. * * For integration with other applications, this function can be overwritten to * instead use the other package password checking algorithm. * * Please note: This function should be used sparingly and is really only meant for single-time * application. Leveraging this improperly in a plugin or theme could result in an endless loop * of password resets if precautions are not taken to ensure it does not execute on every page load. * * @since 2.5.0 * @since 6.8.0 The password is now hashed using bcrypt by default instead of phpass. * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $password The plaintext new user password. * @param int $user_id User ID. */ function wp_set_password( #[\SensitiveParameter] $password, $user_id ) { global $wpdb; $old_user_data = get_userdata( $user_id ); $hash = wp_hash_password( $password ); $wpdb->update( $wpdb->users, array( 'user_pass' => $hash, 'user_activation_key' => '', ), array( 'ID' => $user_id ) ); clean_user_cache( $user_id ); /** * Fires after the user password is set. * * @since 6.2.0 * @since 6.7.0 The `$old_user_data` parameter was added. * * @param string $password The plaintext password just set. * @param int $user_id The ID of the user whose password was just set. * @param WP_User $old_user_data Object containing user's data prior to update. */ do_action( 'wp_set_password', $password, $user_id, $old_user_data ); } endif; if ( ! function_exists( 'get_avatar' ) ) : /** * Retrieves the avatar `<img>` tag for a user, email address, MD5 hash, comment, or post. * * @since 2.5.0 * @since 4.2.0 Added the optional `$args` parameter. * @since 5.5.0 Added the `loading` argument. * @since 6.1.0 Added the `decoding` argument. * @since 6.3.0 Added the `fetchpriority` argument. * * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar MD5 hash, * user email, WP_User object, WP_Post object, or WP_Comment object. * @param int $size Optional. Height and width of the avatar in pixels. Default 96. * @param string $default_value URL for the default image or a default type. Accepts: * - '404' (return a 404 instead of a default image) * - 'retro' (a 8-bit arcade-style pixelated face) * - 'robohash' (a robot) * - 'monsterid' (a monster) * - 'wavatar' (a cartoon face) * - 'identicon' (the "quilt", a geometric pattern) * - 'initials' (initials based avatar with background color) * - 'color' (generated background color) * - 'mystery', 'mm', or 'mysteryman' (The Oyster Man) * - 'blank' (transparent GIF) * - 'gravatar_default' (the Gravatar logo) * Default is the value of the 'avatar_default' option, * with a fallback of 'mystery'. * @param string $alt Optional. Alternative text to use in the avatar image tag. * Default empty. * @param array $args { * Optional. Extra arguments to retrieve the avatar. * * @type int $height Display height of the avatar in pixels. Defaults to $size. * @type int $width Display width of the avatar in pixels. Defaults to $size. * @type bool $force_default Whether to always show the default image, never the Gravatar. * Default false. * @type string $rating What rating to display avatars up to. Accepts: * - 'G' (suitable for all audiences) * - 'PG' (possibly offensive, usually for audiences 13 and above) * - 'R' (intended for adult audiences above 17) * - 'X' (even more mature than above) * Default is the value of the 'avatar_rating' option. * @type string $scheme URL scheme to use. See set_url_scheme() for accepted values. * Default null. * @type array|string $class Array or string of additional classes to add to the img element. * Default null. * @type bool $force_display Whether to always show the avatar - ignores the show_avatars option. * Default false. * @type string $loading Value for the `loading` attribute. * Default null. * @type string $fetchpriority Value for the `fetchpriority` attribute. * Default null. * @type string $decoding Value for the `decoding` attribute. * Default null. * @type string $extra_attr HTML attributes to insert in the IMG element. Is not sanitized. * Default empty. * } * @return string|false `<img>` tag for the user's avatar. False on failure. */ function get_avatar( $id_or_email, $size = 96, $default_value = '', $alt = '', $args = null ) { $defaults = array( // get_avatar_data() args. 'size' => 96, 'height' => null, 'width' => null, 'default' => get_option( 'avatar_default', 'mystery' ), 'force_default' => false, 'rating' => get_option( 'avatar_rating' ), 'scheme' => null, 'alt' => '', 'class' => null, 'force_display' => false, 'loading' => null, 'fetchpriority' => null, 'decoding' => null, 'extra_attr' => '', ); if ( empty( $args ) ) { $args = array(); } $args['size'] = (int) $size; $args['default'] = $default_value; $args['alt'] = $alt; $args = wp_parse_args( $args, $defaults ); if ( empty( $args['height'] ) ) { $args['height'] = $args['size']; } if ( empty( $args['width'] ) ) { $args['width'] = $args['size']; } // Update args with loading optimized attributes. $loading_optimization_attr = wp_get_loading_optimization_attributes( 'img', $args, 'get_avatar' ); $args = array_merge( $args, $loading_optimization_attr ); if ( is_object( $id_or_email ) && isset( $id_or_email->comment_ID ) ) { $id_or_email = get_comment( $id_or_email ); } /** * Allows the HTML for a user's avatar to be returned early. * * Returning a non-null value will effectively short-circuit get_avatar(), passing * the value through the {@see 'get_avatar'} filter and returning early. * * @since 4.2.0 * * @param string|null $avatar HTML for the user's avatar. Default null. * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar MD5 hash, * user email, WP_User object, WP_Post object, or WP_Comment object. * @param array $args Arguments passed to get_avatar_url(), after processing. */ $avatar = apply_filters( 'pre_get_avatar', null, $id_or_email, $args ); if ( ! is_null( $avatar ) ) { /** This filter is documented in wp-includes/pluggable.php */ return apply_filters( 'get_avatar', $avatar, $id_or_email, $args['size'], $args['default'], $args['alt'], $args ); } if ( ! $args['force_display'] && ! get_option( 'show_avatars' ) ) { return false; } $url2x = get_avatar_url( $id_or_email, array_merge( $args, array( 'size' => $args['size'] * 2 ) ) ); $args = get_avatar_data( $id_or_email, $args ); $url = $args['url']; if ( ! $url || is_wp_error( $url ) ) { return false; } $class = array( 'avatar', 'avatar-' . (int) $args['size'], 'photo' ); if ( ! $args['found_avatar'] || $args['force_default'] ) { $class[] = 'avatar-default'; } if ( $args['class'] ) { if ( is_array( $args['class'] ) ) { $class = array_merge( $class, $args['class'] ); } else { $class[] = $args['class']; } } // Add `loading`, `fetchpriority`, and `decoding` attributes. $extra_attr = $args['extra_attr']; if ( in_array( $args['loading'], array( 'lazy', 'eager' ), true ) && ! preg_match( '/\bloading\s*=/', $extra_attr ) ) { if ( ! empty( $extra_attr ) ) { $extra_attr .= ' '; } $extra_attr .= "loading='{$args['loading']}'"; } if ( in_array( $args['fetchpriority'], array( 'high', 'low', 'auto' ), true ) && ! preg_match( '/\bfetchpriority\s*=/', $extra_attr ) ) { if ( ! empty( $extra_attr ) ) { $extra_attr .= ' '; } $extra_attr .= "fetchpriority='{$args['fetchpriority']}'"; } if ( in_array( $args['decoding'], array( 'async', 'sync', 'auto' ), true ) && ! preg_match( '/\bdecoding\s*=/', $extra_attr ) ) { if ( ! empty( $extra_attr ) ) { $extra_attr .= ' '; } $extra_attr .= "decoding='{$args['decoding']}'"; } $avatar = sprintf( "<img alt='%s' src='%s' srcset='%s' class='%s' height='%d' width='%d' %s/>", esc_attr( $args['alt'] ), esc_url( $url ), esc_url( $url2x ) . ' 2x', esc_attr( implode( ' ', $class ) ), (int) $args['height'], (int) $args['width'], $extra_attr ); /** * Filters the HTML for a user's avatar. * * @since 2.5.0 * @since 4.2.0 Added the `$args` parameter. * * @param string $avatar HTML for the user's avatar. * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar MD5 hash, * user email, WP_User object, WP_Post object, or WP_Comment object. * @param int $size Height and width of the avatar in pixels. * @param string $default_value URL for the default image or a default type. Accepts: * - '404' (return a 404 instead of a default image) * - 'retro' (a 8-bit arcade-style pixelated face) * - 'robohash' (a robot) * - 'monsterid' (a monster) * - 'wavatar' (a cartoon face) * - 'identicon' (the "quilt", a geometric pattern) * - 'mystery', 'mm', or 'mysteryman' (The Oyster Man) * - 'blank' (transparent GIF) * - 'gravatar_default' (the Gravatar logo) * @param string $alt Alternative text to use in the avatar image tag. * @param array $args Arguments passed to get_avatar_data(), after processing. */ return apply_filters( 'get_avatar', $avatar, $id_or_email, $args['size'], $args['default'], $args['alt'], $args ); } endif; if ( ! function_exists( 'wp_text_diff' ) ) : /** * Displays a human readable HTML representation of the difference between two strings. * * The Diff is available for getting the changes between versions. The output is * HTML, so the primary use is for displaying the changes. If the two strings * are equivalent, then an empty string will be returned. * * @since 2.6.0 * * @see wp_parse_args() Used to change defaults to user defined settings. * @uses Text_Diff * @uses WP_Text_Diff_Renderer_Table * * @param string $left_string "old" (left) version of string. * @param string $right_string "new" (right) version of string. * @param string|array $args { * Associative array of options to pass to WP_Text_Diff_Renderer_Table(). * * @type string $title Titles the diff in a manner compatible * with the output. Default empty. * @type string $title_left Change the HTML to the left of the title. * Default empty. * @type string $title_right Change the HTML to the right of the title. * Default empty. * @type bool $show_split_view True for split view (two columns), false for * un-split view (single column). Default true. * } * @return string Empty string if strings are equivalent or HTML with differences. */ function wp_text_diff( $left_string, $right_string, $args = null ) { $defaults = array( 'title' => '', 'title_left' => '', 'title_right' => '', 'show_split_view' => true, ); $args = wp_parse_args( $args, $defaults ); if ( ! class_exists( 'WP_Text_Diff_Renderer_Table', false ) ) { require ABSPATH . WPINC . '/wp-diff.php'; } $left_string = normalize_whitespace( $left_string ); $right_string = normalize_whitespace( $right_string ); $left_lines = explode( "\n", $left_string ); $right_lines = explode( "\n", $right_string ); $text_diff = new Text_Diff( $left_lines, $right_lines ); $renderer = new WP_Text_Diff_Renderer_Table( $args ); $diff = $renderer->render( $text_diff ); if ( ! $diff ) { return ''; } $is_split_view = ! empty( $args['show_split_view'] ); $is_split_view_class = $is_split_view ? ' is-split-view' : ''; $r = "<table class='diff$is_split_view_class'>\n"; if ( $args['title'] ) { $r .= "<caption class='diff-title'>$args[title]</caption>\n"; } if ( $args['title_left'] || $args['title_right'] ) { $r .= '<thead>'; } if ( $args['title_left'] || $args['title_right'] ) { $th_or_td_left = empty( $args['title_left'] ) ? 'td' : 'th'; $th_or_td_right = empty( $args['title_right'] ) ? 'td' : 'th'; $r .= "<tr class='diff-sub-title'>\n"; $r .= "\t<$th_or_td_left>$args[title_left]</$th_or_td_left>\n"; if ( $is_split_view ) { $r .= "\t<$th_or_td_right>$args[title_right]</$th_or_td_right>\n"; } $r .= "</tr>\n"; } if ( $args['title_left'] || $args['title_right'] ) { $r .= "</thead>\n"; } $r .= "<tbody>\n$diff\n</tbody>\n"; $r .= '</table>'; return $r; } endif; class-wp-customize-setting.php 0000666 00000072412 15226762357 0012532 0 ustar 00 <?php /** * WordPress Customize Setting classes * * @package WordPress * @subpackage Customize * @since 3.4.0 */ // Don't load directly. if ( ! defined( 'ABSPATH' ) ) { die( '-1' ); } /** * Customize Setting class. * * Handles saving and sanitizing of settings. * * @since 3.4.0 * * @see WP_Customize_Manager * @link https://developer.wordpress.org/themes/customize-api */ #[AllowDynamicProperties] class WP_Customize_Setting { /** * Customizer bootstrap instance. * * @since 3.4.0 * @var WP_Customize_Manager */ public $manager; /** * Unique string identifier for the setting. * * @since 3.4.0 * @var string */ public $id; /** * Type of customize settings. * * @since 3.4.0 * @var string */ public $type = 'theme_mod'; /** * Capability required to edit this setting. * * @since 3.4.0 * @var string|array */ public $capability = 'edit_theme_options'; /** * Theme features required to support the setting. * * @since 3.4.0 * @var string|string[] */ public $theme_supports = ''; /** * The default value for the setting. * * @since 3.4.0 * @var string */ public $default = ''; /** * Options for rendering the live preview of changes in Customizer. * * Set this value to 'postMessage' to enable a custom JavaScript handler to render changes to this setting * as opposed to reloading the whole page. * * @since 3.4.0 * @var string */ public $transport = 'refresh'; /** * Server-side validation callback for the setting's value. * * @since 4.6.0 * @var callable */ public $validate_callback = ''; /** * Callback to filter a Customize setting value in un-slashed form. * * @since 3.4.0 * @var callable */ public $sanitize_callback = ''; /** * Callback to convert a Customize PHP setting value to a value that is JSON serializable. * * @since 3.4.0 * @var callable */ public $sanitize_js_callback = ''; /** * Whether or not the setting is initially dirty when created. * * This is used to ensure that a setting will be sent from the pane to the * preview when loading the Customizer. Normally a setting only is synced to * the preview if it has been changed. This allows the setting to be sent * from the start. * * @since 4.2.0 * @var bool */ public $dirty = false; /** * ID Data. * * @since 3.4.0 * @var array */ protected $id_data = array(); /** * Whether or not preview() was called. * * @since 4.4.0 * @var bool */ protected $is_previewed = false; /** * Cache of multidimensional values to improve performance. * * @since 4.4.0 * @var array */ protected static $aggregated_multidimensionals = array(); /** * Whether the multidimensional setting is aggregated. * * @since 4.4.0 * @var bool */ protected $is_multidimensional_aggregated = false; /** * Constructor. * * Any supplied $args override class property defaults. * * @since 3.4.0 * * @param WP_Customize_Manager $manager Customizer bootstrap instance. * @param string $id A specific ID of the setting. * Can be a theme mod or option name. * @param array $args { * Optional. Array of properties for the new Setting object. Default empty array. * * @type string $type Type of the setting. Default 'theme_mod'. * @type string $capability Capability required for the setting. Default 'edit_theme_options' * @type string|string[] $theme_supports Theme features required to support the panel. Default is none. * @type string $default Default value for the setting. Default is empty string. * @type string $transport Options for rendering the live preview of changes in Customizer. * Using 'refresh' makes the change visible by reloading the whole preview. * Using 'postMessage' allows a custom JavaScript to handle live changes. * Default is 'refresh'. * @type callable $validate_callback Server-side validation callback for the setting's value. * @type callable $sanitize_callback Callback to filter a Customize setting value in un-slashed form. * @type callable $sanitize_js_callback Callback to convert a Customize PHP setting value to a value that is * JSON serializable. * @type bool $dirty Whether or not the setting is initially dirty when created. * } */ public function __construct( $manager, $id, $args = array() ) { $keys = array_keys( get_object_vars( $this ) ); foreach ( $keys as $key ) { if ( isset( $args[ $key ] ) ) { $this->$key = $args[ $key ]; } } $this->manager = $manager; $this->id = $id; // Parse the ID for array keys. $this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) ); $this->id_data['base'] = array_shift( $this->id_data['keys'] ); // Rebuild the ID. $this->id = $this->id_data['base']; if ( ! empty( $this->id_data['keys'] ) ) { $this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']'; } if ( $this->validate_callback ) { add_filter( "customize_validate_{$this->id}", $this->validate_callback, 10, 3 ); } if ( $this->sanitize_callback ) { add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 ); } if ( $this->sanitize_js_callback ) { add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 ); } if ( 'option' === $this->type || 'theme_mod' === $this->type ) { // Other setting types can opt-in to aggregate multidimensional explicitly. $this->aggregate_multidimensional(); // Allow option settings to indicate whether they should be autoloaded. if ( 'option' === $this->type && isset( $args['autoload'] ) ) { self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] = $args['autoload']; } } } /** * Get parsed ID data for multidimensional setting. * * @since 4.4.0 * * @return array { * ID data for multidimensional setting. * * @type string $base ID base * @type array $keys Keys for multidimensional array. * } */ final public function id_data() { return $this->id_data; } /** * Set up the setting for aggregated multidimensional values. * * When a multidimensional setting gets aggregated, all of its preview and update * calls get combined into one call, greatly improving performance. * * @since 4.4.0 */ protected function aggregate_multidimensional() { $id_base = $this->id_data['base']; if ( ! isset( self::$aggregated_multidimensionals[ $this->type ] ) ) { self::$aggregated_multidimensionals[ $this->type ] = array(); } if ( ! isset( self::$aggregated_multidimensionals[ $this->type ][ $id_base ] ) ) { self::$aggregated_multidimensionals[ $this->type ][ $id_base ] = array( 'previewed_instances' => array(), // Calling preview() will add the $setting to the array. 'preview_applied_instances' => array(), // Flags for which settings have had their values applied. 'root_value' => $this->get_root_value( array() ), // Root value for initial state, manipulated by preview and update calls. ); } if ( ! empty( $this->id_data['keys'] ) ) { // Note the preview-applied flag is cleared at priority 9 to ensure it is cleared before a deferred-preview runs. add_action( "customize_post_value_set_{$this->id}", array( $this, '_clear_aggregated_multidimensional_preview_applied_flag' ), 9 ); $this->is_multidimensional_aggregated = true; } } /** * Reset `$aggregated_multidimensionals` static variable. * * This is intended only for use by unit tests. * * @since 4.5.0 * @ignore */ public static function reset_aggregated_multidimensionals() { self::$aggregated_multidimensionals = array(); } /** * The ID for the current site when the preview() method was called. * * @since 4.2.0 * @var int */ protected $_previewed_blog_id; /** * Return true if the current site is not the same as the previewed site. * * @since 4.2.0 * * @return bool If preview() has been called. */ public function is_current_blog_previewed() { if ( ! isset( $this->_previewed_blog_id ) ) { return false; } return ( get_current_blog_id() === $this->_previewed_blog_id ); } /** * Original non-previewed value stored by the preview method. * * @see WP_Customize_Setting::preview() * @since 4.1.1 * @var mixed */ protected $_original_value; /** * Add filters to supply the setting's value when accessed. * * If the setting already has a pre-existing value and there is no incoming * post value for the setting, then this method will short-circuit since * there is no change to preview. * * @since 3.4.0 * @since 4.4.0 Added boolean return value. * * @return bool False when preview short-circuits due no change needing to be previewed. */ public function preview() { if ( ! isset( $this->_previewed_blog_id ) ) { $this->_previewed_blog_id = get_current_blog_id(); } // Prevent re-previewing an already-previewed setting. if ( $this->is_previewed ) { return true; } $id_base = $this->id_data['base']; $is_multidimensional = ! empty( $this->id_data['keys'] ); $multidimensional_filter = array( $this, '_multidimensional_preview_filter' ); /* * Check if the setting has a pre-existing value (an isset check), * and if doesn't have any incoming post value. If both checks are true, * then the preview short-circuits because there is nothing that needs * to be previewed. */ $undefined = new stdClass(); $needs_preview = ( $undefined !== $this->post_value( $undefined ) ); $value = null; // Since no post value was defined, check if we have an initial value set. if ( ! $needs_preview ) { if ( $this->is_multidimensional_aggregated ) { $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value']; $value = $this->multidimensional_get( $root, $this->id_data['keys'], $undefined ); } else { $default = $this->default; $this->default = $undefined; // Temporarily set default to undefined so we can detect if existing value is set. $value = $this->value(); $this->default = $default; } $needs_preview = ( $undefined === $value ); // Because the default needs to be supplied. } // If the setting does not need previewing now, defer to when it has a value to preview. if ( ! $needs_preview ) { if ( ! has_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ) ) { add_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ); } return false; } switch ( $this->type ) { case 'theme_mod': if ( ! $is_multidimensional ) { add_filter( "theme_mod_{$id_base}", array( $this, '_preview_filter' ) ); } else { if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) { // Only add this filter once for this ID base. add_filter( "theme_mod_{$id_base}", $multidimensional_filter ); } self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this; } break; case 'option': if ( ! $is_multidimensional ) { add_filter( "pre_option_{$id_base}", array( $this, '_preview_filter' ) ); } else { if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) { // Only add these filters once for this ID base. add_filter( "option_{$id_base}", $multidimensional_filter ); add_filter( "default_option_{$id_base}", $multidimensional_filter ); } self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this; } break; default: /** * Fires when the WP_Customize_Setting::preview() method is called for settings * not handled as theme_mods or options. * * The dynamic portion of the hook name, `$this->id`, refers to the setting ID. * * @since 3.4.0 * * @param WP_Customize_Setting $setting WP_Customize_Setting instance. */ do_action( "customize_preview_{$this->id}", $this ); /** * Fires when the WP_Customize_Setting::preview() method is called for settings * not handled as theme_mods or options. * * The dynamic portion of the hook name, `$this->type`, refers to the setting type. * * @since 4.1.0 * * @param WP_Customize_Setting $setting WP_Customize_Setting instance. */ do_action( "customize_preview_{$this->type}", $this ); } $this->is_previewed = true; return true; } /** * Clear out the previewed-applied flag for a multidimensional-aggregated value whenever its post value is updated. * * This ensures that the new value will get sanitized and used the next time * that `WP_Customize_Setting::_multidimensional_preview_filter()` * is called for this setting. * * @since 4.4.0 * * @see WP_Customize_Manager::set_post_value() * @see WP_Customize_Setting::_multidimensional_preview_filter() */ final public function _clear_aggregated_multidimensional_preview_applied_flag() { unset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['preview_applied_instances'][ $this->id ] ); } /** * Callback function to filter non-multidimensional theme mods and options. * * If switch_to_blog() was called after the preview() method, and the current * site is now not the same site, then this method does a no-op and returns * the original value. * * @since 3.4.0 * * @param mixed $original Old value. * @return mixed New or old value. */ public function _preview_filter( $original ) { if ( ! $this->is_current_blog_previewed() ) { return $original; } $undefined = new stdClass(); // Symbol hack. $post_value = $this->post_value( $undefined ); if ( $undefined !== $post_value ) { $value = $post_value; } else { /* * Note that we don't use $original here because preview() will * not add the filter in the first place if it has an initial value * and there is no post value. */ $value = $this->default; } return $value; } /** * Callback function to filter multidimensional theme mods and options. * * For all multidimensional settings of a given type, the preview filter for * the first setting previewed will be used to apply the values for the others. * * @since 4.4.0 * * @see WP_Customize_Setting::$aggregated_multidimensionals * @param mixed $original Original root value. * @return mixed New or old value. */ final public function _multidimensional_preview_filter( $original ) { if ( ! $this->is_current_blog_previewed() ) { return $original; } $id_base = $this->id_data['base']; // If no settings have been previewed yet (which should not be the case, since $this is), just pass through the original value. if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) { return $original; } foreach ( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] as $previewed_setting ) { // Skip applying previewed value for any settings that have already been applied. if ( ! empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] ) ) { continue; } // Do the replacements of the posted/default sub value into the root value. $value = $previewed_setting->post_value( $previewed_setting->default ); $root = self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value']; $root = $previewed_setting->multidimensional_replace( $root, $previewed_setting->id_data['keys'], $value ); self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'] = $root; // Mark this setting having been applied so that it will be skipped when the filter is called again. self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] = true; } return self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value']; } /** * Checks user capabilities and theme supports, and then saves * the value of the setting. * * @since 3.4.0 * * @return void|false Void on success, false if cap check fails * or value isn't set or is invalid. */ final public function save() { $value = $this->post_value(); if ( ! $this->check_capabilities() || ! isset( $value ) ) { return false; } $id_base = $this->id_data['base']; /** * Fires when the WP_Customize_Setting::save() method is called. * * The dynamic portion of the hook name, `$id_base` refers to * the base slug of the setting name. * * @since 3.4.0 * * @param WP_Customize_Setting $setting WP_Customize_Setting instance. */ do_action( "customize_save_{$id_base}", $this ); $this->update( $value ); } /** * Fetch and sanitize the $_POST value for the setting. * * During a save request prior to save, post_value() provides the new value while value() does not. * * @since 3.4.0 * * @param mixed $default_value A default value which is used as a fallback. Default null. * @return mixed The default value on failure, otherwise the sanitized and validated value. */ final public function post_value( $default_value = null ) { return $this->manager->post_value( $this, $default_value ); } /** * Sanitize an input. * * @since 3.4.0 * * @param string|array $value The value to sanitize. * @return string|array|null|WP_Error Sanitized value, or `null`/`WP_Error` if invalid. */ public function sanitize( $value ) { /** * Filters a Customize setting value in un-slashed form. * * @since 3.4.0 * * @param mixed $value Value of the setting. * @param WP_Customize_Setting $setting WP_Customize_Setting instance. */ return apply_filters( "customize_sanitize_{$this->id}", $value, $this ); } /** * Validates an input. * * @since 4.6.0 * * @see WP_REST_Request::has_valid_params() * * @param mixed $value Value to validate. * @return true|WP_Error True if the input was validated, otherwise WP_Error. */ public function validate( $value ) { if ( is_wp_error( $value ) ) { return $value; } if ( is_null( $value ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value.' ) ); } $validity = new WP_Error(); /** * Validates a Customize setting value. * * Plugins should amend the `$validity` object via its `WP_Error::add()` method. * * The dynamic portion of the hook name, `$this->ID`, refers to the setting ID. * * @since 4.6.0 * * @param WP_Error $validity Filtered from `true` to `WP_Error` when invalid. * @param mixed $value Value of the setting. * @param WP_Customize_Setting $setting WP_Customize_Setting instance. */ $validity = apply_filters( "customize_validate_{$this->id}", $validity, $value, $this ); if ( is_wp_error( $validity ) && ! $validity->has_errors() ) { $validity = true; } return $validity; } /** * Get the root value for a setting, especially for multidimensional ones. * * @since 4.4.0 * * @param mixed $default_value Value to return if root does not exist. * @return mixed */ protected function get_root_value( $default_value = null ) { $id_base = $this->id_data['base']; if ( 'option' === $this->type ) { return get_option( $id_base, $default_value ); } elseif ( 'theme_mod' === $this->type ) { return get_theme_mod( $id_base, $default_value ); } else { /* * Any WP_Customize_Setting subclass implementing aggregate multidimensional * will need to override this method to obtain the data from the appropriate * location. */ return $default_value; } } /** * Set the root value for a setting, especially for multidimensional ones. * * @since 4.4.0 * * @param mixed $value Value to set as root of multidimensional setting. * @return bool Whether the multidimensional root was updated successfully. */ protected function set_root_value( $value ) { $id_base = $this->id_data['base']; if ( 'option' === $this->type ) { $autoload = true; if ( isset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] ) ) { $autoload = self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload']; } return update_option( $id_base, $value, $autoload ); } elseif ( 'theme_mod' === $this->type ) { set_theme_mod( $id_base, $value ); return true; } else { /* * Any WP_Customize_Setting subclass implementing aggregate multidimensional * will need to override this method to obtain the data from the appropriate * location. */ return false; } } /** * Save the value of the setting, using the related API. * * @since 3.4.0 * * @param mixed $value The value to update. * @return bool The result of saving the value. */ protected function update( $value ) { $id_base = $this->id_data['base']; if ( 'option' === $this->type || 'theme_mod' === $this->type ) { if ( ! $this->is_multidimensional_aggregated ) { return $this->set_root_value( $value ); } else { $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value']; $root = $this->multidimensional_replace( $root, $this->id_data['keys'], $value ); self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'] = $root; return $this->set_root_value( $root ); } } else { /** * Fires when the WP_Customize_Setting::update() method is called for settings * not handled as theme_mods or options. * * The dynamic portion of the hook name, `$this->type`, refers to the type of setting. * * @since 3.4.0 * * @param mixed $value Value of the setting. * @param WP_Customize_Setting $setting WP_Customize_Setting instance. */ do_action( "customize_update_{$this->type}", $value, $this ); return has_action( "customize_update_{$this->type}" ); } } /** * Deprecated method. * * @since 3.4.0 * @deprecated 4.4.0 Deprecated in favor of update() method. */ protected function _update_theme_mod() { _deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' ); } /** * Deprecated method. * * @since 3.4.0 * @deprecated 4.4.0 Deprecated in favor of update() method. */ protected function _update_option() { _deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' ); } /** * Fetch the value of the setting. * * @since 3.4.0 * * @return mixed The value. */ public function value() { $id_base = $this->id_data['base']; $is_core_type = ( 'option' === $this->type || 'theme_mod' === $this->type ); if ( ! $is_core_type && ! $this->is_multidimensional_aggregated ) { // Use post value if previewed and a post value is present. if ( $this->is_previewed ) { $value = $this->post_value( null ); if ( null !== $value ) { return $value; } } $value = $this->get_root_value( $this->default ); /** * Filters a Customize setting value not handled as a theme_mod or option. * * The dynamic portion of the hook name, `$id_base`, refers to * the base slug of the setting name, initialized from `$this->id_data['base']`. * * For settings handled as theme_mods or options, see those corresponding * functions for available hooks. * * @since 3.4.0 * @since 4.6.0 Added the `$this` setting instance as the second parameter. * * @param mixed $default_value The setting default value. Default empty. * @param WP_Customize_Setting $setting The setting instance. */ $value = apply_filters( "customize_value_{$id_base}", $value, $this ); } elseif ( $this->is_multidimensional_aggregated ) { $root_value = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value']; $value = $this->multidimensional_get( $root_value, $this->id_data['keys'], $this->default ); // Ensure that the post value is used if the setting is previewed, since preview filters aren't applying on cached $root_value. if ( $this->is_previewed ) { $value = $this->post_value( $value ); } } else { $value = $this->get_root_value( $this->default ); } return $value; } /** * Sanitize the setting's value for use in JavaScript. * * @since 3.4.0 * * @return mixed The requested escaped value. */ public function js_value() { /** * Filters a Customize setting value for use in JavaScript. * * The dynamic portion of the hook name, `$this->id`, refers to the setting ID. * * @since 3.4.0 * * @param mixed $value The setting value. * @param WP_Customize_Setting $setting WP_Customize_Setting instance. */ $value = apply_filters( "customize_sanitize_js_{$this->id}", $this->value(), $this ); if ( is_string( $value ) ) { return html_entity_decode( $value, ENT_QUOTES, 'UTF-8' ); } return $value; } /** * Retrieves the data to export to the client via JSON. * * @since 4.6.0 * * @return array Array of parameters passed to JavaScript. */ public function json() { return array( 'value' => $this->js_value(), 'transport' => $this->transport, 'dirty' => $this->dirty, 'type' => $this->type, ); } /** * Validate user capabilities whether the theme supports the setting. * * @since 3.4.0 * * @return bool False if theme doesn't support the setting or user can't change setting, otherwise true. */ final public function check_capabilities() { if ( $this->capability && ! current_user_can( $this->capability ) ) { return false; } if ( $this->theme_supports && ! current_theme_supports( ...(array) $this->theme_supports ) ) { return false; } return true; } /** * Multidimensional helper function. * * @since 3.4.0 * * @param array $root * @param array $keys * @param bool $create Default false. * @return array|void Keys are 'root', 'node', and 'key'. */ final protected function multidimensional( &$root, $keys, $create = false ) { if ( $create && empty( $root ) ) { $root = array(); } if ( ! isset( $root ) || empty( $keys ) ) { return; } $last = array_pop( $keys ); $node = &$root; foreach ( $keys as $key ) { if ( $create && ! isset( $node[ $key ] ) ) { $node[ $key ] = array(); } if ( ! is_array( $node ) || ! isset( $node[ $key ] ) ) { return; } $node = &$node[ $key ]; } if ( $create ) { if ( ! is_array( $node ) ) { // Account for an array overriding a string or object value. $node = array(); } if ( ! isset( $node[ $last ] ) ) { $node[ $last ] = array(); } } if ( ! isset( $node[ $last ] ) ) { return; } return array( 'root' => &$root, 'node' => &$node, 'key' => $last, ); } /** * Will attempt to replace a specific value in a multidimensional array. * * @since 3.4.0 * * @param array $root * @param array $keys * @param mixed $value The value to update. * @return mixed */ final protected function multidimensional_replace( $root, $keys, $value ) { if ( ! isset( $value ) ) { return $root; } elseif ( empty( $keys ) ) { // If there are no keys, we're replacing the root. return $value; } $result = $this->multidimensional( $root, $keys, true ); if ( isset( $result ) ) { $result['node'][ $result['key'] ] = $value; } return $root; } /** * Will attempt to fetch a specific value from a multidimensional array. * * @since 3.4.0 * * @param array $root * @param array $keys * @param mixed $default_value A default value which is used as a fallback. Default null. * @return mixed The requested value or the default value. */ final protected function multidimensional_get( $root, $keys, $default_value = null ) { if ( empty( $keys ) ) { // If there are no keys, test the root. return isset( $root ) ? $root : $default_value; } $result = $this->multidimensional( $root, $keys ); return isset( $result ) ? $result['node'][ $result['key'] ] : $default_value; } /** * Will attempt to check if a specific value in a multidimensional array is set. * * @since 3.4.0 * * @param array $root * @param array $keys * @return bool True if value is set, false if not. */ final protected function multidimensional_isset( $root, $keys ) { $result = $this->multidimensional_get( $root, $keys ); return isset( $result ); } } /** * WP_Customize_Filter_Setting class. */ require_once ABSPATH . WPINC . '/customize/class-wp-customize-filter-setting.php'; /** * WP_Customize_Header_Image_Setting class. */ require_once ABSPATH . WPINC . '/customize/class-wp-customize-header-image-setting.php'; /** * WP_Customize_Background_Image_Setting class. */ require_once ABSPATH . WPINC . '/customize/class-wp-customize-background-image-setting.php'; /** * WP_Customize_Nav_Menu_Item_Setting class. */ require_once ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-setting.php'; /** * WP_Customize_Nav_Menu_Setting class. */ require_once ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-setting.php'; https-detection-20260605124018-20260621214653.php 0000666 00000013341 15226762357 0013367 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-customize-custom-css-setting.php 0000666 00000012244 15226762357 0014625 0 ustar 00 <?php /** * Customize API: WP_Customize_Custom_CSS_Setting class * * This handles validation, sanitization and saving of the value. * * @package WordPress * @subpackage Customize * @since 4.7.0 */ /** * Custom Setting to handle WP Custom CSS. * * @since 4.7.0 * * @see WP_Customize_Setting */ final class WP_Customize_Custom_CSS_Setting extends WP_Customize_Setting { /** * The setting type. * * @since 4.7.0 * @var string */ public $type = 'custom_css'; /** * Setting Transport * * @since 4.7.0 * @var string */ public $transport = 'postMessage'; /** * Capability required to edit this setting. * * @since 4.7.0 * @var string */ public $capability = 'edit_css'; /** * Stylesheet * * @since 4.7.0 * @var string */ public $stylesheet = ''; /** * WP_Customize_Custom_CSS_Setting constructor. * * @since 4.7.0 * * @throws Exception If the setting ID does not match the pattern `custom_css[$stylesheet]`. * * @param WP_Customize_Manager $manager Customizer bootstrap instance. * @param string $id A specific ID of the setting. * Can be a theme mod or option name. * @param array $args Setting arguments. */ public function __construct( $manager, $id, $args = array() ) { parent::__construct( $manager, $id, $args ); if ( 'custom_css' !== $this->id_data['base'] ) { throw new Exception( 'Expected custom_css id_base.' ); } if ( 1 !== count( $this->id_data['keys'] ) || empty( $this->id_data['keys'][0] ) ) { throw new Exception( 'Expected single stylesheet key.' ); } $this->stylesheet = $this->id_data['keys'][0]; } /** * Add filter to preview post value. * * @since 4.7.9 * * @return bool False when preview short-circuits due no change needing to be previewed. */ public function preview() { if ( $this->is_previewed ) { return false; } $this->is_previewed = true; add_filter( 'wp_get_custom_css', array( $this, 'filter_previewed_wp_get_custom_css' ), 9, 2 ); return true; } /** * Filters `wp_get_custom_css` for applying the customized value. * * This is used in the preview when `wp_get_custom_css()` is called for rendering the styles. * * @since 4.7.0 * * @see wp_get_custom_css() * * @param string $css Original CSS. * @param string $stylesheet Current stylesheet. * @return string CSS. */ public function filter_previewed_wp_get_custom_css( $css, $stylesheet ) { if ( $stylesheet === $this->stylesheet ) { $customized_value = $this->post_value( null ); if ( ! is_null( $customized_value ) ) { $css = $customized_value; } } return $css; } /** * Fetch the value of the setting. Will return the previewed value when `preview()` is called. * * @since 4.7.0 * * @see WP_Customize_Setting::value() * * @return string */ public function value() { if ( $this->is_previewed ) { $post_value = $this->post_value( null ); if ( null !== $post_value ) { return $post_value; } } $id_base = $this->id_data['base']; $value = ''; $post = wp_get_custom_css_post( $this->stylesheet ); if ( $post ) { $value = $post->post_content; } if ( empty( $value ) ) { $value = $this->default; } /** This filter is documented in wp-includes/class-wp-customize-setting.php */ $value = apply_filters( "customize_value_{$id_base}", $value, $this ); return $value; } /** * Validate a received value for being valid CSS. * * Checks for imbalanced braces, brackets, and comments. * Notifications are rendered when the customizer state is saved. * * @since 4.7.0 * @since 4.9.0 Checking for balanced characters has been moved client-side via linting in code editor. * @since 5.9.0 Renamed `$css` to `$value` for PHP 8 named parameter support. * * @param string $value CSS to validate. * @return true|WP_Error True if the input was validated, otherwise WP_Error. */ public function validate( $value ) { // Restores the more descriptive, specific name for use within this method. $css = $value; $validity = new WP_Error(); if ( preg_match( '#</?\w+#', $css ) ) { $validity->add( 'illegal_markup', __( 'Markup is not allowed in CSS.' ) ); } if ( ! $validity->has_errors() ) { $validity = parent::validate( $css ); } return $validity; } /** * Store the CSS setting value in the custom_css custom post type for the stylesheet. * * @since 4.7.0 * @since 5.9.0 Renamed `$css` to `$value` for PHP 8 named parameter support. * * @param string $value CSS to update. * @return int|false The post ID or false if the value could not be saved. */ public function update( $value ) { // Restores the more descriptive, specific name for use within this method. $css = $value; if ( empty( $css ) ) { $css = ''; } $r = wp_update_custom_css_post( $css, array( 'stylesheet' => $this->stylesheet, ) ); if ( is_wp_error( $r ) ) { return false; } $post_id = $r->ID; // Cache post ID in theme mod for performance to avoid additional DB query. if ( $this->manager->get_stylesheet() === $this->stylesheet ) { set_theme_mod( 'custom_css_post_id', $post_id ); } return $post_id; } } https-detection-20260605100735.php 0000666 00000013341 15226762357 0012002 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260606135930.php 0000666 00000011205 15226762357 0012020 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } rest-api.php 0000666 00000304456 15226762357 0007040 0 ustar 00 <?php /** * REST API functions. * * @package WordPress * @subpackage REST_API * @since 4.4.0 */ /** * Version number for our API. * * @var string */ define( 'REST_API_VERSION', '2.0' ); /** * Registers a REST API route. * * Note: Do not use before the {@see 'rest_api_init'} hook. * * @since 4.4.0 * @since 5.1.0 Added a `_doing_it_wrong()` notice when not called on or after the `rest_api_init` hook. * @since 5.5.0 Added a `_doing_it_wrong()` notice when the required `permission_callback` argument is not set. * * @param string $route_namespace The first URL segment after core prefix. Should be unique to your package/plugin. * @param string $route The base URL for route you are adding. * @param array $args Optional. Either an array of options for the endpoint, or an array of arrays for * multiple methods. Default empty array. * @param bool $override Optional. If the route already exists, should we override it? True overrides, * false merges (with newer overriding if duplicate keys exist). Default false. * @return bool True on success, false on error. */ function register_rest_route( $route_namespace, $route, $args = array(), $override = false ) { if ( empty( $route_namespace ) ) { /* * Non-namespaced routes are not allowed, with the exception of the main * and namespace indexes. If you really need to register a * non-namespaced route, call `WP_REST_Server::register_route` directly. */ _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: string value of the namespace, 2: string value of the route. */ __( 'Routes must be namespaced with plugin or theme name and version. Instead there seems to be an empty namespace \'%1$s\' for route \'%2$s\'.' ), '<code>' . $route_namespace . '</code>', '<code>' . $route . '</code>' ), '4.4.0' ); return false; } elseif ( empty( $route ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: string value of the namespace, 2: string value of the route. */ __( 'Route must be specified. Instead within the namespace \'%1$s\', there seems to be an empty route \'%2$s\'.' ), '<code>' . $route_namespace . '</code>', '<code>' . $route . '</code>' ), '4.4.0' ); return false; } $clean_namespace = trim( $route_namespace, '/' ); if ( $clean_namespace !== $route_namespace ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: string value of the namespace, 2: string value of the route. */ __( 'Namespace must not start or end with a slash. Instead namespace \'%1$s\' for route \'%2$s\' seems to contain a slash.' ), '<code>' . $route_namespace . '</code>', '<code>' . $route . '</code>' ), '5.4.2' ); } if ( ! did_action( 'rest_api_init' ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: rest_api_init, 2: string value of the route, 3: string value of the namespace. */ __( 'REST API routes must be registered on the %1$s action. Instead route \'%2$s\' with namespace \'%3$s\' was not registered on this action.' ), '<code>rest_api_init</code>', '<code>' . $route . '</code>', '<code>' . $route_namespace . '</code>' ), '5.1.0' ); } if ( isset( $args['args'] ) ) { $common_args = $args['args']; unset( $args['args'] ); } else { $common_args = array(); } if ( isset( $args['callback'] ) ) { // Upgrade a single set to multiple. $args = array( $args ); } $defaults = array( 'methods' => 'GET', 'callback' => null, 'args' => array(), ); foreach ( $args as $key => &$arg_group ) { if ( ! is_numeric( $key ) ) { // Route option, skip here. continue; } $arg_group = array_merge( $defaults, $arg_group ); $arg_group['args'] = array_merge( $common_args, $arg_group['args'] ); if ( ! isset( $arg_group['permission_callback'] ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: The REST API route being registered, 2: The argument name, 3: The suggested function name. */ __( 'The REST API route definition for %1$s is missing the required %2$s argument. For REST API routes that are intended to be public, use %3$s as the permission callback.' ), '<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>', '<code>permission_callback</code>', '<code>__return_true</code>' ), '5.5.0' ); } foreach ( $arg_group['args'] as $arg ) { if ( ! is_array( $arg ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: $args, 2: The REST API route being registered. */ __( 'REST API %1$s should be an array of arrays. Non-array value detected for %2$s.' ), '<code>$args</code>', '<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>' ), '6.1.0' ); break; // Leave the foreach loop once a non-array argument was found. } } } $full_route = '/' . $clean_namespace . '/' . trim( $route, '/' ); rest_get_server()->register_route( $clean_namespace, $full_route, $args, $override ); return true; } /** * Registers a new field on an existing WordPress object type. * * @since 4.7.0 * * @global array $wp_rest_additional_fields Holds registered fields, organized * by object type. * * @param string|array $object_type Object(s) the field is being registered to, * "post"|"term"|"comment" etc. * @param string $attribute The attribute name. * @param array $args { * Optional. An array of arguments used to handle the registered field. * * @type callable|null $get_callback Optional. The callback function used to retrieve the field value. Default is * 'null', the field will not be returned in the response. The function will * be passed the prepared object data. * @type callable|null $update_callback Optional. The callback function used to set and update the field value. Default * is 'null', the value cannot be set or updated. The function will be passed * the model object, like WP_Post. * @type array|null $schema Optional. The schema for this field. * Default is 'null', no schema entry will be returned. * } */ function register_rest_field( $object_type, $attribute, $args = array() ) { global $wp_rest_additional_fields; $defaults = array( 'get_callback' => null, 'update_callback' => null, 'schema' => null, ); $args = wp_parse_args( $args, $defaults ); $object_types = (array) $object_type; foreach ( $object_types as $object_type ) { $wp_rest_additional_fields[ $object_type ][ $attribute ] = $args; } } /** * Registers rewrite rules for the REST API. * * @since 4.4.0 * * @see rest_api_register_rewrites() * @global WP $wp Current WordPress environment instance. */ function rest_api_init() { rest_api_register_rewrites(); global $wp; $wp->add_query_var( 'rest_route' ); } /** * Adds REST rewrite rules. * * @since 4.4.0 * * @see add_rewrite_rule() * @global WP_Rewrite $wp_rewrite WordPress rewrite component. */ function rest_api_register_rewrites() { global $wp_rewrite; add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' ); add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' ); add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' ); add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' ); } /** * Registers the default REST API filters. * * Attached to the {@see 'rest_api_init'} action * to make testing and disabling these filters easier. * * @since 4.4.0 */ function rest_api_default_filters() { if ( wp_is_serving_rest_request() ) { // Deprecated reporting. add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 ); add_filter( 'deprecated_function_trigger_error', '__return_false' ); add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 ); add_filter( 'deprecated_argument_trigger_error', '__return_false' ); add_action( 'doing_it_wrong_run', 'rest_handle_doing_it_wrong', 10, 3 ); add_filter( 'doing_it_wrong_trigger_error', '__return_false' ); } // Default serving. add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' ); add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 ); add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 ); add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 ); add_filter( 'rest_index', 'rest_add_application_passwords_to_index' ); } /** * Registers default REST API routes. * * @since 4.7.0 */ function create_initial_rest_routes() { foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { $controller = $post_type->get_rest_controller(); if ( ! $controller ) { continue; } if ( ! $post_type->late_route_registration ) { $controller->register_routes(); } $revisions_controller = $post_type->get_revisions_rest_controller(); if ( $revisions_controller ) { $revisions_controller->register_routes(); } $autosaves_controller = $post_type->get_autosave_rest_controller(); if ( $autosaves_controller ) { $autosaves_controller->register_routes(); } if ( $post_type->late_route_registration ) { $controller->register_routes(); } } // Post types. $controller = new WP_REST_Post_Types_Controller(); $controller->register_routes(); // Post statuses. $controller = new WP_REST_Post_Statuses_Controller(); $controller->register_routes(); // Taxonomies. $controller = new WP_REST_Taxonomies_Controller(); $controller->register_routes(); // Terms. foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) { $controller = $taxonomy->get_rest_controller(); if ( ! $controller ) { continue; } $controller->register_routes(); } // Users. $controller = new WP_REST_Users_Controller(); $controller->register_routes(); // Application Passwords $controller = new WP_REST_Application_Passwords_Controller(); $controller->register_routes(); // Comments. $controller = new WP_REST_Comments_Controller(); $controller->register_routes(); $search_handlers = array( new WP_REST_Post_Search_Handler(), new WP_REST_Term_Search_Handler(), new WP_REST_Post_Format_Search_Handler(), ); /** * Filters the search handlers to use in the REST search controller. * * @since 5.0.0 * * @param array $search_handlers List of search handlers to use in the controller. Each search * handler instance must extend the `WP_REST_Search_Handler` class. * Default is only a handler for posts. */ $search_handlers = apply_filters( 'wp_rest_search_handlers', $search_handlers ); $controller = new WP_REST_Search_Controller( $search_handlers ); $controller->register_routes(); // Block Renderer. $controller = new WP_REST_Block_Renderer_Controller(); $controller->register_routes(); // Block Types. $controller = new WP_REST_Block_Types_Controller(); $controller->register_routes(); // Settings. $controller = new WP_REST_Settings_Controller(); $controller->register_routes(); // Themes. $controller = new WP_REST_Themes_Controller(); $controller->register_routes(); // Plugins. $controller = new WP_REST_Plugins_Controller(); $controller->register_routes(); // Sidebars. $controller = new WP_REST_Sidebars_Controller(); $controller->register_routes(); // Widget Types. $controller = new WP_REST_Widget_Types_Controller(); $controller->register_routes(); // Widgets. $controller = new WP_REST_Widgets_Controller(); $controller->register_routes(); // Block Directory. $controller = new WP_REST_Block_Directory_Controller(); $controller->register_routes(); // Pattern Directory. $controller = new WP_REST_Pattern_Directory_Controller(); $controller->register_routes(); // Block Patterns. $controller = new WP_REST_Block_Patterns_Controller(); $controller->register_routes(); // Block Pattern Categories. $controller = new WP_REST_Block_Pattern_Categories_Controller(); $controller->register_routes(); // Site Health. $site_health = WP_Site_Health::get_instance(); $controller = new WP_REST_Site_Health_Controller( $site_health ); $controller->register_routes(); // URL Details. $controller = new WP_REST_URL_Details_Controller(); $controller->register_routes(); // Menu Locations. $controller = new WP_REST_Menu_Locations_Controller(); $controller->register_routes(); // Site Editor Export. $controller = new WP_REST_Edit_Site_Export_Controller(); $controller->register_routes(); // Navigation Fallback. $controller = new WP_REST_Navigation_Fallback_Controller(); $controller->register_routes(); // Font Collections. $font_collections_controller = new WP_REST_Font_Collections_Controller(); $font_collections_controller->register_routes(); // Abilities. $abilities_categories_controller = new WP_REST_Abilities_V1_Categories_Controller(); $abilities_categories_controller->register_routes(); $abilities_run_controller = new WP_REST_Abilities_V1_Run_Controller(); $abilities_run_controller->register_routes(); $abilities_list_controller = new WP_REST_Abilities_V1_List_Controller(); $abilities_list_controller->register_routes(); } /** * Loads the REST API. * * @since 4.4.0 * * @global WP $wp Current WordPress environment instance. */ function rest_api_loaded() { if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) { return; } // Return an error message if query_var is not a string. if ( ! is_string( $GLOBALS['wp']->query_vars['rest_route'] ) ) { $rest_type_error = new WP_Error( 'rest_path_invalid_type', __( 'The REST route parameter must be a string.' ), array( 'status' => 400 ) ); wp_die( $rest_type_error ); } /** * Whether this is a REST Request. * * @since 4.4.0 * @var bool */ define( 'REST_REQUEST', true ); // Initialize the server. $server = rest_get_server(); // Fire off the request. $route = untrailingslashit( $GLOBALS['wp']->query_vars['rest_route'] ); if ( empty( $route ) ) { $route = '/'; } $server->serve_request( $route ); // We're done. die(); } /** * Retrieves the URL prefix for any API resource. * * @since 4.4.0 * * @return string Prefix. */ function rest_get_url_prefix() { /** * Filters the REST URL prefix. * * @since 4.4.0 * * @param string $prefix URL prefix. Default 'wp-json'. */ return apply_filters( 'rest_url_prefix', 'wp-json' ); } /** * Retrieves the URL to a REST endpoint on a site. * * Note: The returned URL is NOT escaped. * * @since 4.4.0 * * @todo Check if this is even necessary * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * * @param int|null $blog_id Optional. Blog ID. Default of null returns URL for current blog. * @param string $path Optional. REST route. Default '/'. * @param string $scheme Optional. Sanitization scheme. Default 'rest'. * @return string Full URL to the endpoint. */ function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) { if ( empty( $path ) ) { $path = '/'; } $path = '/' . ltrim( $path, '/' ); if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) { global $wp_rewrite; if ( $wp_rewrite->using_index_permalinks() ) { $url = get_home_url( $blog_id, $wp_rewrite->index . '/' . rest_get_url_prefix(), $scheme ); } else { $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme ); } $url .= $path; } else { $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) ); /* * nginx only allows HTTP/1.0 methods when redirecting from / to /index.php. * To work around this, we manually add index.php to the URL, avoiding the redirect. */ if ( ! str_ends_with( $url, 'index.php' ) ) { $url .= 'index.php'; } $url = add_query_arg( 'rest_route', $path, $url ); } if ( is_ssl() && isset( $_SERVER['SERVER_NAME'] ) ) { // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS. if ( parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) === $_SERVER['SERVER_NAME'] ) { $url = set_url_scheme( $url, 'https' ); } } if ( is_admin() && force_ssl_admin() ) { /* * In this situation the home URL may be http:, and `is_ssl()` may be false, * but the admin is served over https: (one way or another), so REST API usage * will be blocked by browsers unless it is also served over HTTPS. */ $url = set_url_scheme( $url, 'https' ); } /** * Filters the REST URL. * * Use this filter to adjust the url returned by the get_rest_url() function. * * @since 4.4.0 * * @param string $url REST URL. * @param string $path REST route. * @param int|null $blog_id Blog ID. * @param string $scheme Sanitization scheme. */ return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme ); } /** * Retrieves the URL to a REST endpoint. * * Note: The returned URL is NOT escaped. * * @since 4.4.0 * * @param string $path Optional. REST route. Default empty. * @param string $scheme Optional. Sanitization scheme. Default 'rest'. * @return string Full URL to the endpoint. */ function rest_url( $path = '', $scheme = 'rest' ) { return get_rest_url( null, $path, $scheme ); } /** * Do a REST request. * * Used primarily to route internal requests through WP_REST_Server. * * @since 4.4.0 * * @param WP_REST_Request|string $request Request. * @return WP_REST_Response REST response. */ function rest_do_request( $request ) { $request = rest_ensure_request( $request ); return rest_get_server()->dispatch( $request ); } /** * Retrieves the current REST server instance. * * Instantiates a new instance if none exists already. * * @since 4.5.0 * * @global WP_REST_Server $wp_rest_server REST server instance. * * @return WP_REST_Server REST server instance. */ function rest_get_server() { /* @var WP_REST_Server $wp_rest_server */ global $wp_rest_server; if ( empty( $wp_rest_server ) ) { /** * Filters the REST Server Class. * * This filter allows you to adjust the server class used by the REST API, using a * different class to handle requests. * * @since 4.4.0 * * @param string $class_name The name of the server class. Default 'WP_REST_Server'. */ $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' ); $wp_rest_server = new $wp_rest_server_class(); /** * Fires when preparing to serve a REST API request. * * Endpoint objects should be created and register their hooks on this action rather * than another action to ensure they're only loaded when needed. * * @since 4.4.0 * * @param WP_REST_Server $wp_rest_server Server object. */ do_action( 'rest_api_init', $wp_rest_server ); } return $wp_rest_server; } /** * Ensures request arguments are a request object (for consistency). * * @since 4.4.0 * @since 5.3.0 Accept string argument for the request path. * * @param array|string|WP_REST_Request $request Request to check. * @return WP_REST_Request REST request instance. */ function rest_ensure_request( $request ) { if ( $request instanceof WP_REST_Request ) { return $request; } if ( is_string( $request ) ) { return new WP_REST_Request( 'GET', $request ); } return new WP_REST_Request( 'GET', '', $request ); } /** * Ensures a REST response is a response object (for consistency). * * This implements WP_REST_Response, allowing usage of `set_status`/`header`/etc * without needing to double-check the object. Will also allow WP_Error to indicate error * responses, so users should immediately check for this value. * * @since 4.4.0 * * @param WP_REST_Response|WP_Error|WP_HTTP_Response|mixed $response Response to check. * @return WP_REST_Response|WP_Error If response generated an error, WP_Error, if response * is already an instance, WP_REST_Response, otherwise * returns a new WP_REST_Response instance. */ function rest_ensure_response( $response ) { if ( is_wp_error( $response ) ) { return $response; } if ( $response instanceof WP_REST_Response ) { return $response; } /* * While WP_HTTP_Response is the base class of WP_REST_Response, it doesn't provide * all the required methods used in WP_REST_Server::dispatch(). */ if ( $response instanceof WP_HTTP_Response ) { return new WP_REST_Response( $response->get_data(), $response->get_status(), $response->get_headers() ); } return new WP_REST_Response( $response ); } /** * Handles _deprecated_function() errors. * * @since 4.4.0 * * @param string $function_name The function that was called. * @param string $replacement The function that should have been called. * @param string $version Version. */ function rest_handle_deprecated_function( $function_name, $replacement, $version ) { if ( ! WP_DEBUG || headers_sent() ) { return; } if ( ! empty( $replacement ) ) { /* translators: 1: Function name, 2: WordPress version number, 3: New function name. */ $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function_name, $version, $replacement ); } else { /* translators: 1: Function name, 2: WordPress version number. */ $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function_name, $version ); } header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) ); } /** * Handles _deprecated_argument() errors. * * @since 4.4.0 * * @param string $function_name The function that was called. * @param string $message A message regarding the change. * @param string $version Version. */ function rest_handle_deprecated_argument( $function_name, $message, $version ) { if ( ! WP_DEBUG || headers_sent() ) { return; } if ( $message ) { /* translators: 1: Function name, 2: WordPress version number, 3: Error message. */ $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function_name, $version, $message ); } else { /* translators: 1: Function name, 2: WordPress version number. */ $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function_name, $version ); } header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) ); } /** * Handles _doing_it_wrong errors. * * @since 5.5.0 * * @param string $function_name The function that was called. * @param string $message A message explaining what has been done incorrectly. * @param string|null $version The version of WordPress where the message was added. */ function rest_handle_doing_it_wrong( $function_name, $message, $version ) { if ( ! WP_DEBUG || headers_sent() ) { return; } if ( $version ) { /* translators: Developer debugging message. 1: PHP function name, 2: WordPress version number, 3: Explanatory message. */ $string = __( '%1$s (since %2$s; %3$s)' ); $string = sprintf( $string, $function_name, $version, $message ); } else { /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message. */ $string = __( '%1$s (%2$s)' ); $string = sprintf( $string, $function_name, $message ); } header( sprintf( 'X-WP-DoingItWrong: %s', $string ) ); } /** * Sends Cross-Origin Resource Sharing headers with API requests. * * @since 4.4.0 * * @param mixed $value Response data. * @return mixed Response data. */ function rest_send_cors_headers( $value ) { $origin = get_http_origin(); if ( $origin ) { // Requests from file:// and data: URLs send "Origin: null". if ( 'null' !== $origin ) { $origin = sanitize_url( $origin ); } header( 'Access-Control-Allow-Origin: ' . $origin ); header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' ); header( 'Access-Control-Allow-Credentials: true' ); header( 'Vary: Origin', false ); } elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) { header( 'Vary: Origin', false ); } return $value; } /** * Handles OPTIONS requests for the server. * * This is handled outside of the server code, as it doesn't obey normal route * mapping. * * @since 4.4.0 * * @param mixed $response Current response, either response or `null` to indicate pass-through. * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server). * @param WP_REST_Request $request The request that was used to make current response. * @return WP_REST_Response Modified response, either response or `null` to indicate pass-through. */ function rest_handle_options_request( $response, $handler, $request ) { if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) { return $response; } $response = new WP_REST_Response(); $data = array(); foreach ( $handler->get_routes() as $route => $endpoints ) { $match = preg_match( '@^' . $route . '$@i', $request->get_route(), $matches ); if ( ! $match ) { continue; } $args = array(); foreach ( $matches as $param => $value ) { if ( ! is_int( $param ) ) { $args[ $param ] = $value; } } foreach ( $endpoints as $endpoint ) { $request->set_url_params( $args ); $request->set_attributes( $endpoint ); } $data = $handler->get_data_for_route( $route, $endpoints, 'help' ); $response->set_matched_route( $route ); break; } $response->set_data( $data ); return $response; } /** * Sends the "Allow" header to state all methods that can be sent to the current route. * * @since 4.4.0 * * @param WP_REST_Response $response Current response being served. * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server). * @param WP_REST_Request $request The request that was used to make current response. * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods. */ function rest_send_allow_header( $response, $server, $request ) { $matched_route = $response->get_matched_route(); if ( ! $matched_route ) { return $response; } $routes = $server->get_routes(); $allowed_methods = array(); // Get the allowed methods across the routes. foreach ( $routes[ $matched_route ] as $_handler ) { foreach ( $_handler['methods'] as $handler_method => $value ) { if ( ! empty( $_handler['permission_callback'] ) ) { $permission = call_user_func( $_handler['permission_callback'], $request ); $allowed_methods[ $handler_method ] = true === $permission; } else { $allowed_methods[ $handler_method ] = true; } } } // Strip out all the methods that are not allowed (false values). $allowed_methods = array_filter( $allowed_methods ); if ( $allowed_methods ) { $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) ); } return $response; } /** * Recursively computes the intersection of arrays using keys for comparison. * * @since 5.3.0 * * @param array $array1 The array with master keys to check. * @param array $array2 An array to compare keys against. * @return array An associative array containing all the entries of array1 which have keys * that are present in all arguments. */ function _rest_array_intersect_key_recursive( $array1, $array2 ) { $array1 = array_intersect_key( $array1, $array2 ); foreach ( $array1 as $key => $value ) { if ( is_array( $value ) && is_array( $array2[ $key ] ) ) { $array1[ $key ] = _rest_array_intersect_key_recursive( $value, $array2[ $key ] ); } } return $array1; } /** * Filters the REST API response to include only an allow-listed set of response object fields. * * @since 4.8.0 * * @param WP_REST_Response $response Current response being served. * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server). * @param WP_REST_Request $request The request that was used to make current response. * @return WP_REST_Response Response to be served, trimmed down to contain a subset of fields. */ function rest_filter_response_fields( $response, $server, $request ) { if ( ! isset( $request['_fields'] ) || $response->is_error() ) { return $response; } $data = $response->get_data(); $fields = wp_parse_list( $request['_fields'] ); if ( 0 === count( $fields ) ) { return $response; } // Trim off outside whitespace from the comma delimited list. $fields = array_map( 'trim', $fields ); // Create nested array of accepted field hierarchy. $fields_as_keyed = array(); foreach ( $fields as $field ) { $parts = explode( '.', $field ); $ref = &$fields_as_keyed; while ( count( $parts ) > 1 ) { $next = array_shift( $parts ); if ( isset( $ref[ $next ] ) && true === $ref[ $next ] ) { // Skip any sub-properties if their parent prop is already marked for inclusion. break 2; } $ref[ $next ] = isset( $ref[ $next ] ) ? $ref[ $next ] : array(); $ref = &$ref[ $next ]; } $last = array_shift( $parts ); $ref[ $last ] = true; } if ( wp_is_numeric_array( $data ) ) { $new_data = array(); foreach ( $data as $item ) { $new_data[] = _rest_array_intersect_key_recursive( $item, $fields_as_keyed ); } } else { $new_data = _rest_array_intersect_key_recursive( $data, $fields_as_keyed ); } $response->set_data( $new_data ); return $response; } /** * Given an array of fields to include in a response, some of which may be * `nested.fields`, determine whether the provided field should be included * in the response body. * * If a parent field is passed in, the presence of any nested field within * that parent will cause the method to return `true`. For example "title" * will return true if any of `title`, `title.raw` or `title.rendered` is * provided. * * @since 5.3.0 * * @param string $field A field to test for inclusion in the response body. * @param array $fields An array of string fields supported by the endpoint. * @return bool Whether to include the field or not. */ function rest_is_field_included( $field, $fields ) { if ( in_array( $field, $fields, true ) ) { return true; } foreach ( $fields as $accepted_field ) { /* * Check to see if $field is the parent of any item in $fields. * A field "parent" should be accepted if "parent.child" is accepted. */ if ( str_starts_with( $accepted_field, "$field." ) ) { return true; } /* * Conversely, if "parent" is accepted, all "parent.child" fields * should also be accepted. */ if ( str_starts_with( $field, "$accepted_field." ) ) { return true; } } return false; } /** * Adds the REST API URL to the WP RSD endpoint. * * @since 4.4.0 * * @see get_rest_url() */ function rest_output_rsd() { $api_root = get_rest_url(); if ( empty( $api_root ) ) { return; } ?> <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" /> <?php } /** * Outputs the REST API link tag into page header. * * @since 4.4.0 * * @see get_rest_url() */ function rest_output_link_wp_head() { $api_root = get_rest_url(); if ( empty( $api_root ) ) { return; } printf( '<link rel="https://api.w.org/" href="%s" />', esc_url( $api_root ) ); $resource = rest_get_queried_resource_route(); if ( $resource ) { printf( '<link rel="alternate" title="%1$s" type="application/json" href="%2$s" />', _x( 'JSON', 'REST API resource link name' ), esc_url( rest_url( $resource ) ) ); } } /** * Sends a Link header for the REST API. * * @since 4.4.0 */ function rest_output_link_header() { if ( headers_sent() ) { return; } $api_root = get_rest_url(); if ( empty( $api_root ) ) { return; } header( sprintf( 'Link: <%s>; rel="https://api.w.org/"', sanitize_url( $api_root ) ), false ); $resource = rest_get_queried_resource_route(); if ( $resource ) { header( sprintf( 'Link: <%1$s>; rel="alternate"; title="%2$s"; type="application/json"', sanitize_url( rest_url( $resource ) ), _x( 'JSON', 'REST API resource link name' ) ), false ); } } /** * Checks for errors when using cookie-based authentication. * * WordPress' built-in cookie authentication is always active * for logged in users. However, the API has to check nonces * for each request to ensure users are not vulnerable to CSRF. * * @since 4.4.0 * * @global mixed $wp_rest_auth_cookie * * @param WP_Error|mixed $result Error from another authentication handler, * null if we should handle it, or another value if not. * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true. */ function rest_cookie_check_errors( $result ) { if ( ! empty( $result ) ) { return $result; } global $wp_rest_auth_cookie; /* * Is cookie authentication being used? (If we get an auth * error, but we're still logged in, another authentication * must have been used). */ if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) { return $result; } // Determine if there is a nonce. $nonce = null; if ( isset( $_REQUEST['_wpnonce'] ) ) { $nonce = $_REQUEST['_wpnonce']; } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) { $nonce = $_SERVER['HTTP_X_WP_NONCE']; } if ( null === $nonce ) { // No nonce at all, so act as if it's an unauthenticated request. wp_set_current_user( 0 ); return true; } // Check the nonce. $result = wp_verify_nonce( $nonce, 'wp_rest' ); if ( ! $result ) { add_filter( 'rest_send_nocache_headers', '__return_true', 20 ); return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie check failed' ), array( 'status' => 403 ) ); } // Send a refreshed nonce in header. rest_get_server()->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) ); return true; } /** * Collects cookie authentication status. * * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors. * * @since 4.4.0 * * @see current_action() * @global mixed $wp_rest_auth_cookie */ function rest_cookie_collect_status() { global $wp_rest_auth_cookie; $status_type = current_action(); if ( 'auth_cookie_valid' !== $status_type ) { $wp_rest_auth_cookie = substr( $status_type, 12 ); return; } $wp_rest_auth_cookie = true; } /** * Collects the status of authenticating with an application password. * * @since 5.6.0 * @since 5.7.0 Added the `$app_password` parameter. * * @global WP_User|WP_Error|null $wp_rest_application_password_status * @global string|null $wp_rest_application_password_uuid * * @param WP_Error $user_or_error The authenticated user or error instance. * @param array $app_password The Application Password used to authenticate. */ function rest_application_password_collect_status( $user_or_error, $app_password = array() ) { global $wp_rest_application_password_status, $wp_rest_application_password_uuid; $wp_rest_application_password_status = $user_or_error; if ( empty( $app_password['uuid'] ) ) { $wp_rest_application_password_uuid = null; } else { $wp_rest_application_password_uuid = $app_password['uuid']; } } /** * Gets the Application Password used for authenticating the request. * * @since 5.7.0 * * @global string|null $wp_rest_application_password_uuid * * @return string|null The Application Password UUID, or null if Application Passwords was not used. */ function rest_get_authenticated_app_password() { global $wp_rest_application_password_uuid; return $wp_rest_application_password_uuid; } /** * Checks for errors when using application password-based authentication. * * @since 5.6.0 * * @global WP_User|WP_Error|null $wp_rest_application_password_status * * @param WP_Error|null|true $result Error from another authentication handler, * null if we should handle it, or another value if not. * @return WP_Error|null|true WP_Error if the application password is invalid, the $result, otherwise true. */ function rest_application_password_check_errors( $result ) { global $wp_rest_application_password_status; if ( ! empty( $result ) ) { return $result; } if ( is_wp_error( $wp_rest_application_password_status ) ) { $data = $wp_rest_application_password_status->get_error_data(); if ( ! isset( $data['status'] ) ) { $data['status'] = 401; } $wp_rest_application_password_status->add_data( $data ); return $wp_rest_application_password_status; } if ( $wp_rest_application_password_status instanceof WP_User ) { return true; } return $result; } /** * Adds Application Passwords info to the REST API index. * * @since 5.6.0 * * @param WP_REST_Response $response The index response object. * @return WP_REST_Response */ function rest_add_application_passwords_to_index( $response ) { if ( ! wp_is_application_passwords_available() ) { return $response; } $response->data['authentication']['application-passwords'] = array( 'endpoints' => array( 'authorization' => admin_url( 'authorize-application.php' ), ), ); return $response; } /** * Retrieves the avatar URLs in various sizes. * * @since 4.7.0 * * @see get_avatar_url() * * @param mixed $id_or_email The avatar to retrieve a URL for. Accepts a user ID, Gravatar MD5 hash, * user email, WP_User object, WP_Post object, or WP_Comment object. * @return (string|false)[] Avatar URLs keyed by size. Each value can be a URL string or boolean false. */ function rest_get_avatar_urls( $id_or_email ) { $avatar_sizes = rest_get_avatar_sizes(); $urls = array(); foreach ( $avatar_sizes as $size ) { $urls[ $size ] = get_avatar_url( $id_or_email, array( 'size' => $size ) ); } return $urls; } /** * Retrieves the pixel sizes for avatars. * * @since 4.7.0 * * @return int[] List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`. */ function rest_get_avatar_sizes() { /** * Filters the REST avatar sizes. * * Use this filter to adjust the array of sizes returned by the * `rest_get_avatar_sizes` function. * * @since 4.4.0 * * @param int[] $sizes An array of int values that are the pixel sizes for avatars. * Default `[ 24, 48, 96 ]`. */ return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) ); } /** * Parses an RFC3339 time into a Unix timestamp. * * Explicitly check for `false` to detect failure, as zero is a valid return * value on success. * * @since 4.4.0 * * @param string $date RFC3339 timestamp. * @param bool $force_utc Optional. Whether to force UTC timezone instead of using * the timestamp's timezone. Default false. * @return int|false Unix timestamp on success, false on failure. */ function rest_parse_date( $date, $force_utc = false ) { if ( $force_utc ) { $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date ); } $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#'; if ( ! preg_match( $regex, $date, $matches ) ) { return false; } return strtotime( $date ); } /** * Parses a 3 or 6 digit hex color (with #). * * @since 5.4.0 * * @param string $color 3 or 6 digit hex color (with #). * @return string|false Color value on success, false on failure. */ function rest_parse_hex_color( $color ) { $regex = '|^#([A-Fa-f0-9]{3}){1,2}$|'; if ( ! preg_match( $regex, $color, $matches ) ) { return false; } return $color; } /** * Parses a date into both its local and UTC equivalent, in MySQL datetime format. * * @since 4.4.0 * * @see rest_parse_date() * * @param string $date RFC3339 timestamp. * @param bool $is_utc Whether the provided date should be interpreted as UTC. Default false. * @return array|null { * Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s), * null on failure. * * @type string $0 Local datetime string. * @type string $1 UTC datetime string. * } */ function rest_get_date_with_gmt( $date, $is_utc = false ) { /* * Whether or not the original date actually has a timezone string * changes the way we need to do timezone conversion. * Store this info before parsing the date, and use it later. */ $has_timezone = preg_match( '#(Z|[+-]\d{2}(:\d{2})?)$#', $date ); $date = rest_parse_date( $date ); if ( false === $date ) { return null; } /* * At this point $date could either be a local date (if we were passed * a *local* date without a timezone offset) or a UTC date (otherwise). * Timezone conversion needs to be handled differently between these two cases. */ if ( ! $is_utc && ! $has_timezone ) { $local = gmdate( 'Y-m-d H:i:s', $date ); $utc = get_gmt_from_date( $local ); } else { $utc = gmdate( 'Y-m-d H:i:s', $date ); $local = get_date_from_gmt( $utc ); } return array( $local, $utc ); } /** * Returns a contextual HTTP error code for authorization failure. * * @since 4.7.0 * * @return int 401 if the user is not logged in, 403 if the user is logged in. */ function rest_authorization_required_code() { return is_user_logged_in() ? 403 : 401; } /** * Validate a request argument based on details registered to the route. * * @since 4.7.0 * * @param mixed $value * @param WP_REST_Request $request * @param string $param * @return true|WP_Error */ function rest_validate_request_arg( $value, $request, $param ) { $attributes = $request->get_attributes(); if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { return true; } $args = $attributes['args'][ $param ]; return rest_validate_value_from_schema( $value, $args, $param ); } /** * Sanitize a request argument based on details registered to the route. * * @since 4.7.0 * * @param mixed $value * @param WP_REST_Request $request * @param string $param * @return mixed */ function rest_sanitize_request_arg( $value, $request, $param ) { $attributes = $request->get_attributes(); if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { return $value; } $args = $attributes['args'][ $param ]; return rest_sanitize_value_from_schema( $value, $args, $param ); } /** * Parse a request argument based on details registered to the route. * * Runs a validation check and sanitizes the value, primarily to be used via * the `sanitize_callback` arguments in the endpoint args registration. * * @since 4.7.0 * * @param mixed $value * @param WP_REST_Request $request * @param string $param * @return mixed */ function rest_parse_request_arg( $value, $request, $param ) { $is_valid = rest_validate_request_arg( $value, $request, $param ); if ( is_wp_error( $is_valid ) ) { return $is_valid; } $value = rest_sanitize_request_arg( $value, $request, $param ); return $value; } /** * Determines if an IP address is valid. * * Handles both IPv4 and IPv6 addresses. * * @since 4.7.0 * * @param string $ip IP address. * @return string|false The valid IP address, otherwise false. */ function rest_is_ip_address( $ip ) { $ipv4_pattern = '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/'; if ( ! preg_match( $ipv4_pattern, $ip ) && ! WpOrg\Requests\Ipv6::check_ipv6( $ip ) ) { return false; } return $ip; } /** * Changes a boolean-like value into the proper boolean value. * * @since 4.7.0 * * @param bool|string|int $value The value being evaluated. * @return bool Returns the proper associated boolean value. */ function rest_sanitize_boolean( $value ) { // String values are translated to `true`; make sure 'false' is false. if ( is_string( $value ) ) { $value = strtolower( $value ); if ( in_array( $value, array( 'false', '0' ), true ) ) { $value = false; } } // Everything else will map nicely to boolean. return (bool) $value; } /** * Determines if a given value is boolean-like. * * @since 4.7.0 * * @param bool|string $maybe_bool The value being evaluated. * @return bool True if a boolean, otherwise false. */ function rest_is_boolean( $maybe_bool ) { if ( is_bool( $maybe_bool ) ) { return true; } if ( is_string( $maybe_bool ) ) { $maybe_bool = strtolower( $maybe_bool ); $valid_boolean_values = array( 'false', 'true', '0', '1', ); return in_array( $maybe_bool, $valid_boolean_values, true ); } if ( is_int( $maybe_bool ) ) { return in_array( $maybe_bool, array( 0, 1 ), true ); } return false; } /** * Determines if a given value is integer-like. * * @since 5.5.0 * * @param mixed $maybe_integer The value being evaluated. * @return bool True if an integer, otherwise false. */ function rest_is_integer( $maybe_integer ) { return is_numeric( $maybe_integer ) && round( (float) $maybe_integer ) === (float) $maybe_integer; } /** * Determines if a given value is array-like. * * @since 5.5.0 * * @param mixed $maybe_array The value being evaluated. * @return bool */ function rest_is_array( $maybe_array ) { if ( is_scalar( $maybe_array ) ) { $maybe_array = wp_parse_list( $maybe_array ); } return wp_is_numeric_array( $maybe_array ); } /** * Converts an array-like value to an array. * * @since 5.5.0 * * @param mixed $maybe_array The value being evaluated. * @return array Returns the array extracted from the value. */ function rest_sanitize_array( $maybe_array ) { if ( is_scalar( $maybe_array ) ) { return wp_parse_list( $maybe_array ); } if ( ! is_array( $maybe_array ) ) { return array(); } // Normalize to numeric array so nothing unexpected is in the keys. return array_values( $maybe_array ); } /** * Determines if a given value is object-like. * * @since 5.5.0 * * @param mixed $maybe_object The value being evaluated. * @return bool True if object like, otherwise false. */ function rest_is_object( $maybe_object ) { if ( '' === $maybe_object ) { return true; } if ( $maybe_object instanceof stdClass ) { return true; } if ( $maybe_object instanceof JsonSerializable ) { $maybe_object = $maybe_object->jsonSerialize(); } return is_array( $maybe_object ); } /** * Converts an object-like value to an array. * * @since 5.5.0 * * @param mixed $maybe_object The value being evaluated. * @return array Returns the object extracted from the value as an associative array. */ function rest_sanitize_object( $maybe_object ) { if ( '' === $maybe_object ) { return array(); } if ( $maybe_object instanceof stdClass ) { return (array) $maybe_object; } if ( $maybe_object instanceof JsonSerializable ) { $maybe_object = $maybe_object->jsonSerialize(); } if ( ! is_array( $maybe_object ) ) { return array(); } return $maybe_object; } /** * Gets the best type for a value. * * @since 5.5.0 * * @param mixed $value The value to check. * @param string[] $types The list of possible types. * @return string The best matching type, an empty string if no types match. */ function rest_get_best_type_for_value( $value, $types ) { static $checks = array( 'array' => 'rest_is_array', 'object' => 'rest_is_object', 'integer' => 'rest_is_integer', 'number' => 'is_numeric', 'boolean' => 'rest_is_boolean', 'string' => 'is_string', 'null' => 'is_null', ); /* * Both arrays and objects allow empty strings to be converted to their types. * But the best answer for this type is a string. */ if ( '' === $value && in_array( 'string', $types, true ) ) { return 'string'; } foreach ( $types as $type ) { if ( isset( $checks[ $type ] ) && $checks[ $type ]( $value ) ) { return $type; } } return ''; } /** * Handles getting the best type for a multi-type schema. * * This is a wrapper for {@see rest_get_best_type_for_value()} that handles * backward compatibility for schemas that use invalid types. * * @since 5.5.0 * * @param mixed $value The value to check. * @param array $args The schema array to use. * @param string $param The parameter name, used in error messages. * @return string */ function rest_handle_multi_type_schema( $value, $args, $param = '' ) { $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' ); $invalid_types = array_diff( $args['type'], $allowed_types ); if ( $invalid_types ) { _doing_it_wrong( __FUNCTION__, /* translators: 1: Parameter, 2: List of allowed types. */ wp_sprintf( __( 'The "type" schema keyword for %1$s can only contain the built-in types: %2$l.' ), $param, $allowed_types ), '5.5.0' ); } $best_type = rest_get_best_type_for_value( $value, $args['type'] ); if ( ! $best_type ) { if ( ! $invalid_types ) { return ''; } // Backward compatibility for previous behavior which allowed the value if there was an invalid type used. $best_type = reset( $invalid_types ); } return $best_type; } /** * Checks if an array is made up of unique items. * * @since 5.5.0 * * @param array $input_array The array to check. * @return bool True if the array contains unique items, false otherwise. */ function rest_validate_array_contains_unique_items( $input_array ) { $seen = array(); foreach ( $input_array as $item ) { $stabilized = rest_stabilize_value( $item ); $key = serialize( $stabilized ); if ( ! isset( $seen[ $key ] ) ) { $seen[ $key ] = true; continue; } return false; } return true; } /** * Stabilizes a value following JSON Schema semantics. * * For lists, order is preserved. For objects, properties are reordered alphabetically. * * @since 5.5.0 * * @param mixed $value The value to stabilize. Must already be sanitized. Objects should have been converted to arrays. * @return mixed The stabilized value. */ function rest_stabilize_value( $value ) { if ( is_scalar( $value ) || is_null( $value ) ) { return $value; } if ( is_object( $value ) ) { _doing_it_wrong( __FUNCTION__, __( 'Cannot stabilize objects. Convert the object to an array first.' ), '5.5.0' ); return $value; } ksort( $value ); foreach ( $value as $k => $v ) { $value[ $k ] = rest_stabilize_value( $v ); } return $value; } /** * Validates if the JSON Schema pattern matches a value. * * @since 5.6.0 * * @param string $pattern The pattern to match against. * @param string $value The value to check. * @return bool True if the pattern matches the given value, false otherwise. */ function rest_validate_json_schema_pattern( $pattern, $value ) { $escaped_pattern = str_replace( '#', '\\#', $pattern ); return 1 === preg_match( '#' . $escaped_pattern . '#u', $value ); } /** * Finds the schema for a property using the patternProperties keyword. * * @since 5.6.0 * * @param string $property The property name to check. * @param array $args The schema array to use. * @return array|null The schema of matching pattern property, or null if no patterns match. */ function rest_find_matching_pattern_property_schema( $property, $args ) { if ( isset( $args['patternProperties'] ) ) { foreach ( $args['patternProperties'] as $pattern => $child_schema ) { if ( rest_validate_json_schema_pattern( $pattern, $property ) ) { return $child_schema; } } } return null; } /** * Formats a combining operation error into a WP_Error object. * * @since 5.6.0 * * @param string $param The parameter name. * @param array $error The error details. * @return WP_Error */ function rest_format_combining_operation_error( $param, $error ) { $position = $error['index']; $reason = $error['error_object']->get_error_message(); if ( isset( $error['schema']['title'] ) ) { $title = $error['schema']['title']; return new WP_Error( 'rest_no_matching_schema', /* translators: 1: Parameter, 2: Schema title, 3: Reason. */ sprintf( __( '%1$s is not a valid %2$s. Reason: %3$s' ), $param, $title, $reason ), array( 'position' => $position ) ); } return new WP_Error( 'rest_no_matching_schema', /* translators: 1: Parameter, 2: Reason. */ sprintf( __( '%1$s does not match the expected format. Reason: %2$s' ), $param, $reason ), array( 'position' => $position ) ); } /** * Gets the error of combining operation. * * @since 5.6.0 * * @param array $value The value to validate. * @param string $param The parameter name, used in error messages. * @param array $errors The errors array, to search for possible error. * @return WP_Error The combining operation error. */ function rest_get_combining_operation_error( $value, $param, $errors ) { // If there is only one error, simply return it. if ( 1 === count( $errors ) ) { return rest_format_combining_operation_error( $param, $errors[0] ); } // Filter out all errors related to type validation. $filtered_errors = array(); foreach ( $errors as $error ) { $error_code = $error['error_object']->get_error_code(); $error_data = $error['error_object']->get_error_data(); if ( 'rest_invalid_type' !== $error_code || ( isset( $error_data['param'] ) && $param !== $error_data['param'] ) ) { $filtered_errors[] = $error; } } // If there is only one error left, simply return it. if ( 1 === count( $filtered_errors ) ) { return rest_format_combining_operation_error( $param, $filtered_errors[0] ); } // If there are only errors related to object validation, try choosing the most appropriate one. if ( count( $filtered_errors ) > 1 && 'object' === $filtered_errors[0]['schema']['type'] ) { $result = null; $number = 0; foreach ( $filtered_errors as $error ) { if ( isset( $error['schema']['properties'] ) ) { $n = count( array_intersect_key( $error['schema']['properties'], $value ) ); if ( $n > $number ) { $result = $error; $number = $n; } } } if ( null !== $result ) { return rest_format_combining_operation_error( $param, $result ); } } // If each schema has a title, include those titles in the error message. $schema_titles = array(); foreach ( $errors as $error ) { if ( isset( $error['schema']['title'] ) ) { $schema_titles[] = $error['schema']['title']; } } if ( count( $schema_titles ) === count( $errors ) ) { /* translators: 1: Parameter, 2: Schema titles. */ return new WP_Error( 'rest_no_matching_schema', wp_sprintf( __( '%1$s is not a valid %2$l.' ), $param, $schema_titles ) ); } /* translators: %s: Parameter. */ return new WP_Error( 'rest_no_matching_schema', sprintf( __( '%s does not match any of the expected formats.' ), $param ) ); } /** * Finds the matching schema among the "anyOf" schemas. * * @since 5.6.0 * * @param mixed $value The value to validate. * @param array $args The schema array to use. * @param string $param The parameter name, used in error messages. * @return array|WP_Error The matching schema or WP_Error instance if all schemas do not match. */ function rest_find_any_matching_schema( $value, $args, $param ) { $errors = array(); foreach ( $args['anyOf'] as $index => $schema ) { if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) { $schema['type'] = $args['type']; } $is_valid = rest_validate_value_from_schema( $value, $schema, $param ); if ( ! is_wp_error( $is_valid ) ) { return $schema; } $errors[] = array( 'error_object' => $is_valid, 'schema' => $schema, 'index' => $index, ); } return rest_get_combining_operation_error( $value, $param, $errors ); } /** * Finds the matching schema among the "oneOf" schemas. * * @since 5.6.0 * * @param mixed $value The value to validate. * @param array $args The schema array to use. * @param string $param The parameter name, used in error messages. * @param bool $stop_after_first_match Optional. Whether the process should stop after the first successful match. * @return array|WP_Error The matching schema or WP_Error instance if the number of matching schemas is not equal to one. */ function rest_find_one_matching_schema( $value, $args, $param, $stop_after_first_match = false ) { $matching_schemas = array(); $errors = array(); foreach ( $args['oneOf'] as $index => $schema ) { if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) { $schema['type'] = $args['type']; } $is_valid = rest_validate_value_from_schema( $value, $schema, $param ); if ( ! is_wp_error( $is_valid ) ) { if ( $stop_after_first_match ) { return $schema; } $matching_schemas[] = array( 'schema_object' => $schema, 'index' => $index, ); } else { $errors[] = array( 'error_object' => $is_valid, 'schema' => $schema, 'index' => $index, ); } } if ( ! $matching_schemas ) { return rest_get_combining_operation_error( $value, $param, $errors ); } if ( count( $matching_schemas ) > 1 ) { $schema_positions = array(); $schema_titles = array(); foreach ( $matching_schemas as $schema ) { $schema_positions[] = $schema['index']; if ( isset( $schema['schema_object']['title'] ) ) { $schema_titles[] = $schema['schema_object']['title']; } } // If each schema has a title, include those titles in the error message. if ( count( $schema_titles ) === count( $matching_schemas ) ) { return new WP_Error( 'rest_one_of_multiple_matches', /* translators: 1: Parameter, 2: Schema titles. */ wp_sprintf( __( '%1$s matches %2$l, but should match only one.' ), $param, $schema_titles ), array( 'positions' => $schema_positions ) ); } return new WP_Error( 'rest_one_of_multiple_matches', /* translators: %s: Parameter. */ sprintf( __( '%s matches more than one of the expected formats.' ), $param ), array( 'positions' => $schema_positions ) ); } return $matching_schemas[0]['schema_object']; } /** * Checks the equality of two values, following JSON Schema semantics. * * Property order is ignored for objects. * * Values must have been previously sanitized/coerced to their native types. * * @since 5.7.0 * * @param mixed $value1 The first value to check. * @param mixed $value2 The second value to check. * @return bool True if the values are equal or false otherwise. */ function rest_are_values_equal( $value1, $value2 ) { if ( is_array( $value1 ) && is_array( $value2 ) ) { if ( count( $value1 ) !== count( $value2 ) ) { return false; } foreach ( $value1 as $index => $value ) { if ( ! array_key_exists( $index, $value2 ) || ! rest_are_values_equal( $value, $value2[ $index ] ) ) { return false; } } return true; } if ( is_int( $value1 ) && is_float( $value2 ) || is_float( $value1 ) && is_int( $value2 ) ) { return (float) $value1 === (float) $value2; } return $value1 === $value2; } /** * Validates that the given value is a member of the JSON Schema "enum". * * @since 5.7.0 * * @param mixed $value The value to validate. * @param array $args The schema array to use. * @param string $param The parameter name, used in error messages. * @return true|WP_Error True if the "enum" contains the value or a WP_Error instance otherwise. */ function rest_validate_enum( $value, $args, $param ) { $sanitized_value = rest_sanitize_value_from_schema( $value, $args, $param ); if ( is_wp_error( $sanitized_value ) ) { return $sanitized_value; } foreach ( $args['enum'] as $enum_value ) { if ( rest_are_values_equal( $sanitized_value, $enum_value ) ) { return true; } } $encoded_enum_values = array(); foreach ( $args['enum'] as $enum_value ) { $encoded_enum_values[] = is_scalar( $enum_value ) ? $enum_value : wp_json_encode( $enum_value ); } if ( count( $encoded_enum_values ) === 1 ) { /* translators: 1: Parameter, 2: Valid values. */ return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not %2$s.' ), $param, $encoded_enum_values[0] ) ); } /* translators: 1: Parameter, 2: List of valid values. */ return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not one of %2$l.' ), $param, $encoded_enum_values ) ); } /** * Get all valid JSON schema properties. * * @since 5.6.0 * * @return string[] All valid JSON schema properties. */ function rest_get_allowed_schema_keywords() { return array( 'title', 'description', 'default', 'type', 'format', 'enum', 'items', 'properties', 'additionalProperties', 'patternProperties', 'minProperties', 'maxProperties', 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf', 'minLength', 'maxLength', 'pattern', 'minItems', 'maxItems', 'uniqueItems', 'anyOf', 'oneOf', ); } /** * Validate a value based on a schema. * * @since 4.7.0 * @since 4.9.0 Support the "object" type. * @since 5.2.0 Support validating "additionalProperties" against a schema. * @since 5.3.0 Support multiple types. * @since 5.4.0 Convert an empty string to an empty object. * @since 5.5.0 Add the "uuid" and "hex-color" formats. * Support the "minLength", "maxLength" and "pattern" keywords for strings. * Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays. * Validate required properties. * @since 5.6.0 Support the "minProperties" and "maxProperties" keywords for objects. * Support the "multipleOf" keyword for numbers and integers. * Support the "patternProperties" keyword for objects. * Support the "anyOf" and "oneOf" keywords. * * @param mixed $value The value to validate. * @param array $args Schema array to use for validation. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_value_from_schema( $value, $args, $param = '' ) { if ( isset( $args['anyOf'] ) ) { $matching_schema = rest_find_any_matching_schema( $value, $args, $param ); if ( is_wp_error( $matching_schema ) ) { return $matching_schema; } if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) { $args['type'] = $matching_schema['type']; } } if ( isset( $args['oneOf'] ) ) { $matching_schema = rest_find_one_matching_schema( $value, $args, $param ); if ( is_wp_error( $matching_schema ) ) { return $matching_schema; } if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) { $args['type'] = $matching_schema['type']; } } $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' ); if ( ! isset( $args['type'] ) ) { /* translators: %s: Parameter. */ _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' ); } if ( is_array( $args['type'] ) ) { $best_type = rest_handle_multi_type_schema( $value, $args, $param ); if ( ! $best_type ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: List of types. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ), array( 'param' => $param ) ); } $args['type'] = $best_type; } if ( ! in_array( $args['type'], $allowed_types, true ) ) { _doing_it_wrong( __FUNCTION__, /* translators: 1: Parameter, 2: The list of allowed types. */ wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ), '5.5.0' ); } switch ( $args['type'] ) { case 'null': $is_valid = rest_validate_null_value_from_schema( $value, $param ); break; case 'boolean': $is_valid = rest_validate_boolean_value_from_schema( $value, $param ); break; case 'object': $is_valid = rest_validate_object_value_from_schema( $value, $args, $param ); break; case 'array': $is_valid = rest_validate_array_value_from_schema( $value, $args, $param ); break; case 'number': $is_valid = rest_validate_number_value_from_schema( $value, $args, $param ); break; case 'string': $is_valid = rest_validate_string_value_from_schema( $value, $args, $param ); break; case 'integer': $is_valid = rest_validate_integer_value_from_schema( $value, $args, $param ); break; default: $is_valid = true; break; } if ( is_wp_error( $is_valid ) ) { return $is_valid; } if ( ! empty( $args['enum'] ) ) { $enum_contains_value = rest_validate_enum( $value, $args, $param ); if ( is_wp_error( $enum_contains_value ) ) { return $enum_contains_value; } } /* * The "format" keyword should only be applied to strings. However, for backward compatibility, * we allow the "format" keyword if the type keyword was not specified, or was set to an invalid value. */ if ( isset( $args['format'] ) && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) ) ) { switch ( $args['format'] ) { case 'hex-color': if ( ! rest_parse_hex_color( $value ) ) { return new WP_Error( 'rest_invalid_hex_color', __( 'Invalid hex color.' ) ); } break; case 'date-time': if ( false === rest_parse_date( $value ) ) { return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) ); } break; case 'email': if ( ! is_email( $value ) ) { return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) ); } break; case 'ip': if ( ! rest_is_ip_address( $value ) ) { /* translators: %s: IP address. */ return new WP_Error( 'rest_invalid_ip', sprintf( __( '%s is not a valid IP address.' ), $param ) ); } break; case 'uuid': if ( ! wp_is_uuid( $value ) ) { /* translators: %s: The name of a JSON field expecting a valid UUID. */ return new WP_Error( 'rest_invalid_uuid', sprintf( __( '%s is not a valid UUID.' ), $param ) ); } break; } } return true; } /** * Validates a null value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_null_value_from_schema( $value, $param ) { if ( null !== $value ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ), array( 'param' => $param ) ); } return true; } /** * Validates a boolean value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_boolean_value_from_schema( $value, $param ) { if ( ! rest_is_boolean( $value ) ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ), array( 'param' => $param ) ); } return true; } /** * Validates an object value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param array $args Schema array to use for validation. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_object_value_from_schema( $value, $args, $param ) { if ( ! rest_is_object( $value ) ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ), array( 'param' => $param ) ); } $value = rest_sanitize_object( $value ); if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4 foreach ( $args['required'] as $name ) { if ( ! array_key_exists( $name, $value ) ) { return new WP_Error( 'rest_property_required', /* translators: 1: Property of an object, 2: Parameter. */ sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) ); } } } elseif ( isset( $args['properties'] ) ) { // schema version 3 foreach ( $args['properties'] as $name => $property ) { if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) { return new WP_Error( 'rest_property_required', /* translators: 1: Property of an object, 2: Parameter. */ sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) ); } } } foreach ( $value as $property => $v ) { if ( isset( $args['properties'][ $property ] ) ) { $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' ); if ( is_wp_error( $is_valid ) ) { return $is_valid; } continue; } $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args ); if ( null !== $pattern_property_schema ) { $is_valid = rest_validate_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' ); if ( is_wp_error( $is_valid ) ) { return $is_valid; } continue; } if ( isset( $args['additionalProperties'] ) ) { if ( false === $args['additionalProperties'] ) { return new WP_Error( 'rest_additional_properties_forbidden', /* translators: %s: Property of an object. */ sprintf( __( '%1$s is not a valid property of Object.' ), $property ) ); } if ( is_array( $args['additionalProperties'] ) ) { $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' ); if ( is_wp_error( $is_valid ) ) { return $is_valid; } } } } if ( isset( $args['minProperties'] ) && count( $value ) < $args['minProperties'] ) { return new WP_Error( 'rest_too_few_properties', sprintf( /* translators: 1: Parameter, 2: Number. */ _n( '%1$s must contain at least %2$s property.', '%1$s must contain at least %2$s properties.', $args['minProperties'] ), $param, number_format_i18n( $args['minProperties'] ) ) ); } if ( isset( $args['maxProperties'] ) && count( $value ) > $args['maxProperties'] ) { return new WP_Error( 'rest_too_many_properties', sprintf( /* translators: 1: Parameter, 2: Number. */ _n( '%1$s must contain at most %2$s property.', '%1$s must contain at most %2$s properties.', $args['maxProperties'] ), $param, number_format_i18n( $args['maxProperties'] ) ) ); } return true; } /** * Validates an array value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param array $args Schema array to use for validation. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_array_value_from_schema( $value, $args, $param ) { if ( ! rest_is_array( $value ) ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ), array( 'param' => $param ) ); } $value = rest_sanitize_array( $value ); if ( isset( $args['items'] ) ) { foreach ( $value as $index => $v ) { $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' ); if ( is_wp_error( $is_valid ) ) { return $is_valid; } } } if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) { return new WP_Error( 'rest_too_few_items', sprintf( /* translators: 1: Parameter, 2: Number. */ _n( '%1$s must contain at least %2$s item.', '%1$s must contain at least %2$s items.', $args['minItems'] ), $param, number_format_i18n( $args['minItems'] ) ) ); } if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) { return new WP_Error( 'rest_too_many_items', sprintf( /* translators: 1: Parameter, 2: Number. */ _n( '%1$s must contain at most %2$s item.', '%1$s must contain at most %2$s items.', $args['maxItems'] ), $param, number_format_i18n( $args['maxItems'] ) ) ); } if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) { /* translators: %s: Parameter. */ return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) ); } return true; } /** * Validates a number value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param array $args Schema array to use for validation. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_number_value_from_schema( $value, $args, $param ) { if ( ! is_numeric( $value ) ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ), array( 'param' => $param ) ); } if ( isset( $args['multipleOf'] ) && fmod( $value, $args['multipleOf'] ) !== 0.0 ) { return new WP_Error( 'rest_invalid_multiple', /* translators: 1: Parameter, 2: Multiplier. */ sprintf( __( '%1$s must be a multiple of %2$s.' ), $param, $args['multipleOf'] ) ); } if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) { if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) { return new WP_Error( 'rest_out_of_bounds', /* translators: 1: Parameter, 2: Minimum number. */ sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) ); } if ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) { return new WP_Error( 'rest_out_of_bounds', /* translators: 1: Parameter, 2: Minimum number. */ sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) ); } } if ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) { if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) { return new WP_Error( 'rest_out_of_bounds', /* translators: 1: Parameter, 2: Maximum number. */ sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) ); } if ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) { return new WP_Error( 'rest_out_of_bounds', /* translators: 1: Parameter, 2: Maximum number. */ sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) ); } } if ( isset( $args['minimum'], $args['maximum'] ) ) { if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) { return new WP_Error( 'rest_out_of_bounds', sprintf( /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */ __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); } } if ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { if ( $value > $args['maximum'] || $value <= $args['minimum'] ) { return new WP_Error( 'rest_out_of_bounds', sprintf( /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */ __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); } } if ( ! empty( $args['exclusiveMaximum'] ) && empty( $args['exclusiveMinimum'] ) ) { if ( $value >= $args['maximum'] || $value < $args['minimum'] ) { return new WP_Error( 'rest_out_of_bounds', sprintf( /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */ __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); } } if ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { if ( $value > $args['maximum'] || $value < $args['minimum'] ) { return new WP_Error( 'rest_out_of_bounds', sprintf( /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */ __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); } } } return true; } /** * Validates a string value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param array $args Schema array to use for validation. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_string_value_from_schema( $value, $args, $param ) { if ( ! is_string( $value ) ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ), array( 'param' => $param ) ); } if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) { return new WP_Error( 'rest_too_short', sprintf( /* translators: 1: Parameter, 2: Number of characters. */ _n( '%1$s must be at least %2$s character long.', '%1$s must be at least %2$s characters long.', $args['minLength'] ), $param, number_format_i18n( $args['minLength'] ) ) ); } if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) { return new WP_Error( 'rest_too_long', sprintf( /* translators: 1: Parameter, 2: Number of characters. */ _n( '%1$s must be at most %2$s character long.', '%1$s must be at most %2$s characters long.', $args['maxLength'] ), $param, number_format_i18n( $args['maxLength'] ) ) ); } if ( isset( $args['pattern'] ) && ! rest_validate_json_schema_pattern( $args['pattern'], $value ) ) { return new WP_Error( 'rest_invalid_pattern', /* translators: 1: Parameter, 2: Pattern. */ sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] ) ); } return true; } /** * Validates an integer value based on a schema. * * @since 5.7.0 * * @param mixed $value The value to validate. * @param array $args Schema array to use for validation. * @param string $param The parameter name, used in error messages. * @return true|WP_Error */ function rest_validate_integer_value_from_schema( $value, $args, $param ) { $is_valid_number = rest_validate_number_value_from_schema( $value, $args, $param ); if ( is_wp_error( $is_valid_number ) ) { return $is_valid_number; } if ( ! rest_is_integer( $value ) ) { return new WP_Error( 'rest_invalid_type', /* translators: 1: Parameter, 2: Type name. */ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ), array( 'param' => $param ) ); } return true; } /** * Sanitize a value based on a schema. * * @since 4.7.0 * @since 5.5.0 Added the `$param` parameter. * @since 5.6.0 Support the "anyOf" and "oneOf" keywords. * @since 5.9.0 Added `text-field` and `textarea-field` formats. * * @param mixed $value The value to sanitize. * @param array $args Schema array to use for sanitization. * @param string $param The parameter name, used in error messages. * @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized. */ function rest_sanitize_value_from_schema( $value, $args, $param = '' ) { if ( isset( $args['anyOf'] ) ) { $matching_schema = rest_find_any_matching_schema( $value, $args, $param ); if ( is_wp_error( $matching_schema ) ) { return $matching_schema; } if ( ! isset( $args['type'] ) ) { $args['type'] = $matching_schema['type']; } $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param ); } if ( isset( $args['oneOf'] ) ) { $matching_schema = rest_find_one_matching_schema( $value, $args, $param ); if ( is_wp_error( $matching_schema ) ) { return $matching_schema; } if ( ! isset( $args['type'] ) ) { $args['type'] = $matching_schema['type']; } $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param ); } $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' ); if ( ! isset( $args['type'] ) ) { /* translators: %s: Parameter. */ _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' ); } if ( is_array( $args['type'] ) ) { $best_type = rest_handle_multi_type_schema( $value, $args, $param ); if ( ! $best_type ) { return null; } $args['type'] = $best_type; } if ( ! in_array( $args['type'], $allowed_types, true ) ) { _doing_it_wrong( __FUNCTION__, /* translators: 1: Parameter, 2: The list of allowed types. */ wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ), '5.5.0' ); } if ( 'array' === $args['type'] ) { $value = rest_sanitize_array( $value ); if ( ! empty( $args['items'] ) ) { foreach ( $value as $index => $v ) { $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' ); } } if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) { /* translators: %s: Parameter. */ return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) ); } return $value; } if ( 'object' === $args['type'] ) { $value = rest_sanitize_object( $value ); foreach ( $value as $property => $v ) { if ( isset( $args['properties'][ $property ] ) ) { $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' ); continue; } $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args ); if ( null !== $pattern_property_schema ) { $value[ $property ] = rest_sanitize_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' ); continue; } if ( isset( $args['additionalProperties'] ) ) { if ( false === $args['additionalProperties'] ) { unset( $value[ $property ] ); } elseif ( is_array( $args['additionalProperties'] ) ) { $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' ); } } } return $value; } if ( 'null' === $args['type'] ) { return null; } if ( 'integer' === $args['type'] ) { return (int) $value; } if ( 'number' === $args['type'] ) { return (float) $value; } if ( 'boolean' === $args['type'] ) { return rest_sanitize_boolean( $value ); } // This behavior matches rest_validate_value_from_schema(). if ( isset( $args['format'] ) && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) ) ) { switch ( $args['format'] ) { case 'hex-color': return (string) sanitize_hex_color( $value ); case 'date-time': return sanitize_text_field( $value ); case 'email': // sanitize_email() validates, which would be unexpected. return sanitize_text_field( $value ); case 'uri': return sanitize_url( $value ); case 'ip': return sanitize_text_field( $value ); case 'uuid': return sanitize_text_field( $value ); case 'text-field': return sanitize_text_field( $value ); case 'textarea-field': return sanitize_textarea_field( $value ); } } if ( 'string' === $args['type'] ) { return (string) $value; } return $value; } /** * Append result of internal request to REST API for purpose of preloading data to be attached to a page. * Expected to be called in the context of `array_reduce`. * * @since 5.0.0 * * @param array $memo Reduce accumulator. * @param string $path REST API path to preload. * @return array Modified reduce accumulator. */ function rest_preload_api_request( $memo, $path ) { /* * array_reduce() doesn't support passing an array in PHP 5.2, * so we need to make sure we start with one. */ if ( ! is_array( $memo ) ) { $memo = array(); } if ( empty( $path ) ) { return $memo; } $method = 'GET'; if ( is_array( $path ) && 2 === count( $path ) ) { $method = end( $path ); $path = reset( $path ); if ( ! in_array( $method, array( 'GET', 'OPTIONS' ), true ) ) { $method = 'GET'; } } // Remove trailing slashes at the end of the REST API path (query part). $path = untrailingslashit( $path ); if ( empty( $path ) ) { $path = '/'; } $path_parts = parse_url( $path ); if ( false === $path_parts ) { return $memo; } if ( isset( $path_parts['path'] ) && '/' !== $path_parts['path'] ) { // Remove trailing slashes from the "path" part of the REST API path. $path_parts['path'] = untrailingslashit( $path_parts['path'] ); $path = str_contains( $path, '?' ) ? $path_parts['path'] . '?' . ( $path_parts['query'] ?? '' ) : $path_parts['path']; } $request = new WP_REST_Request( $method, $path_parts['path'] ); if ( ! empty( $path_parts['query'] ) ) { parse_str( $path_parts['query'], $query_params ); $request->set_query_params( $query_params ); } $response = rest_do_request( $request ); if ( 200 === $response->status ) { $server = rest_get_server(); /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $server, $request ); $embed = $request->has_param( '_embed' ) ? rest_parse_embed_param( $request['_embed'] ) : false; $data = (array) $server->response_to_data( $response, $embed ); if ( 'OPTIONS' === $method ) { $memo[ $method ][ $path ] = array( 'body' => $data, 'headers' => $response->headers, ); } else { $memo[ $path ] = array( 'body' => $data, 'headers' => $response->headers, ); } } return $memo; } /** * Parses the "_embed" parameter into the list of resources to embed. * * @since 5.4.0 * * @param string|array $embed Raw "_embed" parameter value. * @return true|string[] Either true to embed all embeds, or a list of relations to embed. */ function rest_parse_embed_param( $embed ) { if ( ! $embed || 'true' === $embed || '1' === $embed ) { return true; } $rels = wp_parse_list( $embed ); if ( ! $rels ) { return true; } return $rels; } /** * Filters the response to remove any fields not available in the given context. * * @since 5.5.0 * @since 5.6.0 Support the "patternProperties" keyword for objects. * Support the "anyOf" and "oneOf" keywords. * * @param array|object $response_data The response data to modify. * @param array $schema The schema for the endpoint used to filter the response. * @param string $context The requested context. * @return array|object The filtered response data. */ function rest_filter_response_by_context( $response_data, $schema, $context ) { if ( isset( $schema['anyOf'] ) ) { $matching_schema = rest_find_any_matching_schema( $response_data, $schema, '' ); if ( ! is_wp_error( $matching_schema ) ) { if ( ! isset( $schema['type'] ) ) { $schema['type'] = $matching_schema['type']; } $response_data = rest_filter_response_by_context( $response_data, $matching_schema, $context ); } } if ( isset( $schema['oneOf'] ) ) { $matching_schema = rest_find_one_matching_schema( $response_data, $schema, '', true ); if ( ! is_wp_error( $matching_schema ) ) { if ( ! isset( $schema['type'] ) ) { $schema['type'] = $matching_schema['type']; } $response_data = rest_filter_response_by_context( $response_data, $matching_schema, $context ); } } if ( ! is_array( $response_data ) && ! is_object( $response_data ) ) { return $response_data; } if ( isset( $schema['type'] ) ) { $type = $schema['type']; } elseif ( isset( $schema['properties'] ) ) { $type = 'object'; // Back compat if a developer accidentally omitted the type. } else { return $response_data; } $is_array_type = 'array' === $type || ( is_array( $type ) && in_array( 'array', $type, true ) ); $is_object_type = 'object' === $type || ( is_array( $type ) && in_array( 'object', $type, true ) ); if ( $is_array_type && $is_object_type ) { if ( rest_is_array( $response_data ) ) { $is_object_type = false; } else { $is_array_type = false; } } $has_additional_properties = $is_object_type && isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] ); foreach ( $response_data as $key => $value ) { $check = array(); if ( $is_array_type ) { $check = isset( $schema['items'] ) ? $schema['items'] : array(); } elseif ( $is_object_type ) { if ( isset( $schema['properties'][ $key ] ) ) { $check = $schema['properties'][ $key ]; } else { $pattern_property_schema = rest_find_matching_pattern_property_schema( $key, $schema ); if ( null !== $pattern_property_schema ) { $check = $pattern_property_schema; } elseif ( $has_additional_properties ) { $check = $schema['additionalProperties']; } } } if ( ! isset( $check['context'] ) ) { continue; } if ( ! in_array( $context, $check['context'], true ) ) { if ( $is_array_type ) { // All array items share schema, so there's no need to check each one. $response_data = array(); break; } if ( is_object( $response_data ) ) { unset( $response_data->$key ); } else { unset( $response_data[ $key ] ); } } elseif ( is_array( $value ) || is_object( $value ) ) { $new_value = rest_filter_response_by_context( $value, $check, $context ); if ( is_object( $response_data ) ) { $response_data->$key = $new_value; } else { $response_data[ $key ] = $new_value; } } } return $response_data; } /** * Sets the "additionalProperties" to false by default for all object definitions in the schema. * * @since 5.5.0 * @since 5.6.0 Support the "patternProperties" keyword. * * @param array $schema The schema to modify. * @return array The modified schema. */ function rest_default_additional_properties_to_false( $schema ) { $type = (array) $schema['type']; if ( in_array( 'object', $type, true ) ) { if ( isset( $schema['properties'] ) ) { foreach ( $schema['properties'] as $key => $child_schema ) { $schema['properties'][ $key ] = rest_default_additional_properties_to_false( $child_schema ); } } if ( isset( $schema['patternProperties'] ) ) { foreach ( $schema['patternProperties'] as $key => $child_schema ) { $schema['patternProperties'][ $key ] = rest_default_additional_properties_to_false( $child_schema ); } } if ( ! isset( $schema['additionalProperties'] ) ) { $schema['additionalProperties'] = false; } } if ( in_array( 'array', $type, true ) ) { if ( isset( $schema['items'] ) ) { $schema['items'] = rest_default_additional_properties_to_false( $schema['items'] ); } } return $schema; } /** * Gets the REST API route for a post. * * @since 5.5.0 * * @param int|WP_Post $post Post ID or post object. * @return string The route path with a leading slash for the given post, * or an empty string if there is not a route. */ function rest_get_route_for_post( $post ) { $post = get_post( $post ); if ( ! $post instanceof WP_Post ) { return ''; } $post_type_route = rest_get_route_for_post_type_items( $post->post_type ); if ( ! $post_type_route ) { return ''; } $route = sprintf( '%s/%d', $post_type_route, $post->ID ); /** * Filters the REST API route for a post. * * @since 5.5.0 * * @param string $route The route path. * @param WP_Post $post The post object. */ return apply_filters( 'rest_route_for_post', $route, $post ); } /** * Gets the REST API route for a post type. * * @since 5.9.0 * * @param string $post_type The name of a registered post type. * @return string The route path with a leading slash for the given post type, * or an empty string if there is not a route. */ function rest_get_route_for_post_type_items( $post_type ) { $post_type = get_post_type_object( $post_type ); if ( ! $post_type ) { return ''; } if ( ! $post_type->show_in_rest ) { return ''; } $namespace = ! empty( $post_type->rest_namespace ) ? $post_type->rest_namespace : 'wp/v2'; $rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name; $route = sprintf( '/%s/%s', $namespace, $rest_base ); /** * Filters the REST API route for a post type. * * @since 5.9.0 * * @param string $route The route path. * @param WP_Post_Type $post_type The post type object. */ return apply_filters( 'rest_route_for_post_type_items', $route, $post_type ); } /** * Gets the REST API route for a term. * * @since 5.5.0 * * @param int|WP_Term $term Term ID or term object. * @return string The route path with a leading slash for the given term, * or an empty string if there is not a route. */ function rest_get_route_for_term( $term ) { $term = get_term( $term ); if ( ! $term instanceof WP_Term ) { return ''; } $taxonomy_route = rest_get_route_for_taxonomy_items( $term->taxonomy ); if ( ! $taxonomy_route ) { return ''; } $route = sprintf( '%s/%d', $taxonomy_route, $term->term_id ); /** * Filters the REST API route for a term. * * @since 5.5.0 * * @param string $route The route path. * @param WP_Term $term The term object. */ return apply_filters( 'rest_route_for_term', $route, $term ); } /** * Gets the REST API route for a taxonomy. * * @since 5.9.0 * * @param string $taxonomy Name of taxonomy. * @return string The route path with a leading slash for the given taxonomy. */ function rest_get_route_for_taxonomy_items( $taxonomy ) { $taxonomy = get_taxonomy( $taxonomy ); if ( ! $taxonomy ) { return ''; } if ( ! $taxonomy->show_in_rest ) { return ''; } $namespace = ! empty( $taxonomy->rest_namespace ) ? $taxonomy->rest_namespace : 'wp/v2'; $rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; $route = sprintf( '/%s/%s', $namespace, $rest_base ); /** * Filters the REST API route for a taxonomy. * * @since 5.9.0 * * @param string $route The route path. * @param WP_Taxonomy $taxonomy The taxonomy object. */ return apply_filters( 'rest_route_for_taxonomy_items', $route, $taxonomy ); } /** * Gets the REST route for the currently queried object. * * @since 5.5.0 * * @return string The REST route of the resource, or an empty string if no resource identified. */ function rest_get_queried_resource_route() { if ( is_singular() ) { $route = rest_get_route_for_post( get_queried_object() ); } elseif ( is_category() || is_tag() || is_tax() ) { $route = rest_get_route_for_term( get_queried_object() ); } elseif ( is_author() ) { $route = '/wp/v2/users/' . get_queried_object_id(); } else { $route = ''; } /** * Filters the REST route for the currently queried object. * * @since 5.5.0 * * @param string $link The route with a leading slash, or an empty string. */ return apply_filters( 'rest_queried_resource_route', $route ); } /** * Retrieves an array of endpoint arguments from the item schema and endpoint method. * * @since 5.6.0 * * @param array $schema The full JSON schema for the endpoint. * @param string $method Optional. HTTP method of the endpoint. The arguments for `CREATABLE` endpoints are * checked for required values and may fall-back to a given default, this is not done * on `EDITABLE` endpoints. Default WP_REST_Server::CREATABLE. * @return array The endpoint arguments. */ function rest_get_endpoint_args_for_schema( $schema, $method = WP_REST_Server::CREATABLE ) { $schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array(); $endpoint_args = array(); $valid_schema_properties = rest_get_allowed_schema_keywords(); $valid_schema_properties = array_diff( $valid_schema_properties, array( 'default', 'required' ) ); foreach ( $schema_properties as $field_id => $params ) { // Arguments specified as `readonly` are not allowed to be set. if ( ! empty( $params['readonly'] ) ) { continue; } $endpoint_args[ $field_id ] = array( 'validate_callback' => 'rest_validate_request_arg', 'sanitize_callback' => 'rest_sanitize_request_arg', ); if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) { $endpoint_args[ $field_id ]['default'] = $params['default']; } if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) { $endpoint_args[ $field_id ]['required'] = true; } foreach ( $valid_schema_properties as $schema_prop ) { if ( isset( $params[ $schema_prop ] ) ) { $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ]; } } // Merge in any options provided by the schema property. if ( isset( $params['arg_options'] ) ) { // Only use required / default from arg_options on CREATABLE endpoints. if ( WP_REST_Server::CREATABLE !== $method ) { $params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '', ) ); } $endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] ); } } return $endpoint_args; } /** * Converts an error to a response object. * * This iterates over all error codes and messages to change it into a flat * array. This enables simpler client behavior, as it is represented as a * list in JSON rather than an object/map. * * @since 5.7.0 * * @param WP_Error $error WP_Error instance. * * @return WP_REST_Response List of associative arrays with code and message keys. */ function rest_convert_error_to_response( $error ) { $status = array_reduce( $error->get_all_error_data(), static function ( $status, $error_data ) { return is_array( $error_data ) && isset( $error_data['status'] ) ? $error_data['status'] : $status; }, 500 ); $errors = array(); foreach ( (array) $error->errors as $code => $messages ) { $all_data = $error->get_all_error_data( $code ); $last_data = array_pop( $all_data ); foreach ( (array) $messages as $message ) { $formatted = array( 'code' => $code, 'message' => $message, 'data' => $last_data, ); if ( $all_data ) { $formatted['additional_data'] = $all_data; } $errors[] = $formatted; } } $data = $errors[0]; if ( count( $errors ) > 1 ) { // Remove the primary error. array_shift( $errors ); $data['additional_errors'] = $errors; } return new WP_REST_Response( $data, $status ); } /** * Checks whether a REST API endpoint request is currently being handled. * * This may be a standalone REST API request, or an internal request dispatched from within a regular page load. * * @since 6.5.0 * * @global WP_REST_Server $wp_rest_server REST server instance. * * @return bool True if a REST endpoint request is currently being handled, false otherwise. */ function wp_is_rest_endpoint() { /* @var WP_REST_Server $wp_rest_server */ global $wp_rest_server; // Check whether this is a standalone REST request. $is_rest_endpoint = wp_is_serving_rest_request(); if ( ! $is_rest_endpoint ) { // Otherwise, check whether an internal REST request is currently being handled. $is_rest_endpoint = isset( $wp_rest_server ) && $wp_rest_server->is_dispatching(); } /** * Filters whether a REST endpoint request is currently being handled. * * This may be a standalone REST API request, or an internal request dispatched from within a regular page load. * * @since 6.5.0 * * @param bool $is_request_endpoint Whether a REST endpoint request is currently being handled. */ return (bool) apply_filters( 'wp_is_rest_endpoint', $is_rest_endpoint ); } https-migration-20260622071915.php 0000666 00000011205 15226762357 0012020 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260605081204.php 0000666 00000011205 15226762357 0012011 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-detection-20260605081605.php 0000666 00000013341 15226762357 0012006 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260606052425.php 0000666 00000013341 15226762357 0012005 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260615083002.php 0000666 00000013341 15226762357 0012000 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-detection-20260605034026-20260605074213.php 0000666 00000013341 15226762357 0013364 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } class-wp-plugin-dependencies.php 0000666 00000061343 15226762357 0012760 0 ustar 00 <?php /** * WordPress Plugin Administration API: WP_Plugin_Dependencies class * * @package WordPress * @subpackage Administration * @since 6.5.0 */ /** * Core class for installing plugin dependencies. * * It is designed to add plugin dependencies as designated in the * `Requires Plugins` header to a new view in the plugins install page. */ class WP_Plugin_Dependencies { /** * Holds 'get_plugins()'. * * @since 6.5.0 * * @var array */ protected static $plugins; /** * Holds plugin directory names to compare with cache. * * @since 6.5.0 * * @var array */ protected static $plugin_dirnames; /** * Holds sanitized plugin dependency slugs. * * Keyed on the dependent plugin's filepath, * relative to the plugins directory. * * @since 6.5.0 * * @var array */ protected static $dependencies; /** * Holds an array of sanitized plugin dependency slugs. * * @since 6.5.0 * * @var array */ protected static $dependency_slugs; /** * Holds an array of dependent plugin slugs. * * Keyed on the dependent plugin's filepath, * relative to the plugins directory. * * @since 6.5.0 * * @var array */ protected static $dependent_slugs; /** * Holds 'plugins_api()' data for plugin dependencies. * * @since 6.5.0 * * @var array */ protected static $dependency_api_data; /** * Holds plugin dependency filepaths, relative to the plugins directory. * * Keyed on the dependency's slug. * * @since 6.5.0 * * @var string[] */ protected static $dependency_filepaths; /** * An array of circular dependency pairings. * * @since 6.5.0 * * @var array[] */ protected static $circular_dependencies_pairs; /** * An array of circular dependency slugs. * * @since 6.5.0 * * @var string[] */ protected static $circular_dependencies_slugs; /** * Whether Plugin Dependencies have been initialized. * * @since 6.5.0 * * @var bool */ protected static $initialized = false; /** * Initializes by fetching plugin header and plugin API data. * * @since 6.5.0 */ public static function initialize() { if ( false === self::$initialized ) { self::read_dependencies_from_plugin_headers(); self::get_dependency_api_data(); self::$initialized = true; } } /** * Determines whether the plugin has plugins that depend on it. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return bool Whether the plugin has plugins that depend on it. */ public static function has_dependents( $plugin_file ) { return in_array( self::convert_to_slug( $plugin_file ), (array) self::$dependency_slugs, true ); } /** * Determines whether the plugin has plugin dependencies. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return bool Whether a plugin has plugin dependencies. */ public static function has_dependencies( $plugin_file ) { return isset( self::$dependencies[ $plugin_file ] ); } /** * Determines whether the plugin has active dependents. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return bool Whether the plugin has active dependents. */ public static function has_active_dependents( $plugin_file ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; $dependents = self::get_dependents( self::convert_to_slug( $plugin_file ) ); foreach ( $dependents as $dependent ) { if ( is_plugin_active( $dependent ) ) { return true; } } return false; } /** * Gets filepaths of plugins that require the dependency. * * @since 6.5.0 * * @param string $slug The dependency's slug. * @return array An array of dependent plugin filepaths, relative to the plugins directory. */ public static function get_dependents( $slug ) { $dependents = array(); foreach ( (array) self::$dependencies as $dependent => $dependencies ) { if ( in_array( $slug, $dependencies, true ) ) { $dependents[] = $dependent; } } return $dependents; } /** * Gets the slugs of plugins that the dependent requires. * * @since 6.5.0 * * @param string $plugin_file The dependent plugin's filepath, relative to the plugins directory. * @return array An array of dependency plugin slugs. */ public static function get_dependencies( $plugin_file ) { if ( isset( self::$dependencies[ $plugin_file ] ) ) { return self::$dependencies[ $plugin_file ]; } return array(); } /** * Gets a dependent plugin's filepath. * * @since 6.5.0 * * @param string $slug The dependent plugin's slug. * @return string|false The dependent plugin's filepath, relative to the plugins directory, * or false if the plugin has no dependencies. */ public static function get_dependent_filepath( $slug ) { $filepath = array_search( $slug, self::$dependent_slugs, true ); return $filepath ? $filepath : false; } /** * Determines whether the plugin has unmet dependencies. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return bool Whether the plugin has unmet dependencies. */ public static function has_unmet_dependencies( $plugin_file ) { if ( ! isset( self::$dependencies[ $plugin_file ] ) ) { return false; } require_once ABSPATH . 'wp-admin/includes/plugin.php'; foreach ( self::$dependencies[ $plugin_file ] as $dependency ) { $dependency_filepath = self::get_dependency_filepath( $dependency ); if ( false === $dependency_filepath || is_plugin_inactive( $dependency_filepath ) ) { return true; } } return false; } /** * Determines whether the plugin has a circular dependency. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return bool Whether the plugin has a circular dependency. */ public static function has_circular_dependency( $plugin_file ) { if ( ! is_array( self::$circular_dependencies_slugs ) ) { self::get_circular_dependencies(); } if ( ! empty( self::$circular_dependencies_slugs ) ) { $slug = self::convert_to_slug( $plugin_file ); if ( in_array( $slug, self::$circular_dependencies_slugs, true ) ) { return true; } } return false; } /** * Gets the names of plugins that require the plugin. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return array An array of dependent names. */ public static function get_dependent_names( $plugin_file ) { $dependent_names = array(); $plugins = self::get_plugins(); $slug = self::convert_to_slug( $plugin_file ); foreach ( self::get_dependents( $slug ) as $dependent ) { $dependent_names[ $dependent ] = $plugins[ $dependent ]['Name']; } sort( $dependent_names ); return $dependent_names; } /** * Gets the names of plugins required by the plugin. * * @since 6.5.0 * * @param string $plugin_file The dependent plugin's filepath, relative to the plugins directory. * @return array An array of dependency names. */ public static function get_dependency_names( $plugin_file ) { $dependency_api_data = self::get_dependency_api_data(); $dependencies = self::get_dependencies( $plugin_file ); $plugins = self::get_plugins(); $dependency_names = array(); foreach ( $dependencies as $dependency ) { // Use the name if it's available, otherwise fall back to the slug. if ( isset( $dependency_api_data[ $dependency ]['name'] ) ) { $name = $dependency_api_data[ $dependency ]['name']; } else { $dependency_filepath = self::get_dependency_filepath( $dependency ); if ( false !== $dependency_filepath ) { $name = $plugins[ $dependency_filepath ]['Name']; } else { $name = $dependency; } } $dependency_names[ $dependency ] = $name; } return $dependency_names; } /** * Gets the filepath for a dependency, relative to the plugin's directory. * * @since 6.5.0 * * @param string $slug The dependency's slug. * @return string|false If installed, the dependency's filepath relative to the plugins directory, otherwise false. */ public static function get_dependency_filepath( $slug ) { $dependency_filepaths = self::get_dependency_filepaths(); if ( ! isset( $dependency_filepaths[ $slug ] ) ) { return false; } return $dependency_filepaths[ $slug ]; } /** * Returns API data for the dependency. * * @since 6.5.0 * * @param string $slug The dependency's slug. * @return array|false The dependency's API data on success, otherwise false. */ public static function get_dependency_data( $slug ) { $dependency_api_data = self::get_dependency_api_data(); if ( isset( $dependency_api_data[ $slug ] ) ) { return $dependency_api_data[ $slug ]; } return false; } /** * Displays an admin notice if dependencies are not installed. * * @since 6.5.0 */ public static function display_admin_notice_for_unmet_dependencies() { if ( in_array( false, self::get_dependency_filepaths(), true ) ) { $error_message = __( 'Some required plugins are missing or inactive.' ); if ( is_multisite() ) { if ( current_user_can( 'manage_network_plugins' ) ) { $error_message .= ' ' . sprintf( /* translators: %s: Link to the network plugins page. */ __( '<a href="%s">Manage plugins</a>.' ), esc_url( network_admin_url( 'plugins.php' ) ) ); } else { $error_message .= ' ' . __( 'Please contact your network administrator.' ); } } elseif ( 'plugins' !== get_current_screen()->base ) { $error_message .= ' ' . sprintf( /* translators: %s: Link to the plugins page. */ __( '<a href="%s">Manage plugins</a>.' ), esc_url( admin_url( 'plugins.php' ) ) ); } wp_admin_notice( $error_message, array( 'type' => 'warning', ) ); } } /** * Displays an admin notice if circular dependencies are installed. * * @since 6.5.0 */ public static function display_admin_notice_for_circular_dependencies() { $circular_dependencies = self::get_circular_dependencies(); if ( ! empty( $circular_dependencies ) && count( $circular_dependencies ) > 1 ) { $circular_dependencies = array_unique( $circular_dependencies, SORT_REGULAR ); $plugins = self::get_plugins(); $plugin_dirnames = self::get_plugin_dirnames(); // Build output lines. $circular_dependency_lines = ''; foreach ( $circular_dependencies as $circular_dependency ) { $first_filepath = $plugin_dirnames[ $circular_dependency[0] ]; $second_filepath = $plugin_dirnames[ $circular_dependency[1] ]; $circular_dependency_lines .= sprintf( /* translators: 1: First plugin name, 2: Second plugin name. */ '<li>' . _x( '%1$s requires %2$s', 'The first plugin requires the second plugin.' ) . '</li>', '<strong>' . esc_html( $plugins[ $first_filepath ]['Name'] ) . '</strong>', '<strong>' . esc_html( $plugins[ $second_filepath ]['Name'] ) . '</strong>' ); } wp_admin_notice( sprintf( '<p>%1$s</p><ul>%2$s</ul><p>%3$s</p>', __( 'These plugins cannot be activated because their requirements are invalid.' ), $circular_dependency_lines, __( 'Please contact the plugin authors for more information.' ) ), array( 'type' => 'warning', 'paragraph_wrap' => false, ) ); } } /** * Checks plugin dependencies after a plugin is installed via AJAX. * * @since 6.5.0 */ public static function check_plugin_dependencies_during_ajax() { check_ajax_referer( 'updates' ); if ( empty( $_POST['slug'] ) ) { wp_send_json_error( array( 'slug' => '', 'pluginName' => '', 'errorCode' => 'no_plugin_specified', 'errorMessage' => __( 'No plugin specified.' ), ) ); } $slug = sanitize_key( wp_unslash( $_POST['slug'] ) ); $status = array( 'slug' => $slug ); self::get_plugins(); self::get_plugin_dirnames(); if ( ! isset( self::$plugin_dirnames[ $slug ] ) ) { $status['errorCode'] = 'plugin_not_installed'; $status['errorMessage'] = __( 'The plugin is not installed.' ); wp_send_json_error( $status ); } $plugin_file = self::$plugin_dirnames[ $slug ]; $status['pluginName'] = self::$plugins[ $plugin_file ]['Name']; $status['plugin'] = $plugin_file; if ( current_user_can( 'activate_plugin', $plugin_file ) && is_plugin_inactive( $plugin_file ) ) { $status['activateUrl'] = add_query_arg( array( '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $plugin_file ), 'action' => 'activate', 'plugin' => $plugin_file, ), is_multisite() ? network_admin_url( 'plugins.php' ) : admin_url( 'plugins.php' ) ); } if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) { $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] ); } self::initialize(); $dependencies = self::get_dependencies( $plugin_file ); if ( empty( $dependencies ) ) { $status['message'] = __( 'The plugin has no required plugins.' ); wp_send_json_success( $status ); } require_once ABSPATH . 'wp-admin/includes/plugin.php'; $inactive_dependencies = array(); foreach ( $dependencies as $dependency ) { if ( false === self::$plugin_dirnames[ $dependency ] || is_plugin_inactive( self::$plugin_dirnames[ $dependency ] ) ) { $inactive_dependencies[] = $dependency; } } if ( ! empty( $inactive_dependencies ) ) { $inactive_dependency_names = array_map( function ( $dependency ) { if ( isset( self::$dependency_api_data[ $dependency ]['Name'] ) ) { $inactive_dependency_name = self::$dependency_api_data[ $dependency ]['Name']; } else { $inactive_dependency_name = $dependency; } return $inactive_dependency_name; }, $inactive_dependencies ); $status['errorCode'] = 'inactive_dependencies'; $status['errorMessage'] = sprintf( /* translators: %s: A list of inactive dependency plugin names. */ __( 'The following plugins must be activated first: %s.' ), implode( ', ', $inactive_dependency_names ) ); $status['errorData'] = array_combine( $inactive_dependencies, $inactive_dependency_names ); wp_send_json_error( $status ); } $status['message'] = __( 'All required plugins are installed and activated.' ); wp_send_json_success( $status ); } /** * Gets data for installed plugins. * * @since 6.5.0 * * @return array An array of plugin data. */ protected static function get_plugins() { if ( is_array( self::$plugins ) ) { return self::$plugins; } require_once ABSPATH . 'wp-admin/includes/plugin.php'; self::$plugins = get_plugins(); return self::$plugins; } /** * Reads and stores dependency slugs from a plugin's 'Requires Plugins' header. * * @since 6.5.0 */ protected static function read_dependencies_from_plugin_headers() { self::$dependencies = array(); self::$dependency_slugs = array(); self::$dependent_slugs = array(); $plugins = self::get_plugins(); foreach ( $plugins as $plugin => $header ) { if ( '' === $header['RequiresPlugins'] ) { continue; } $dependency_slugs = self::sanitize_dependency_slugs( $header['RequiresPlugins'] ); self::$dependencies[ $plugin ] = $dependency_slugs; self::$dependency_slugs = array_merge( self::$dependency_slugs, $dependency_slugs ); $dependent_slug = self::convert_to_slug( $plugin ); self::$dependent_slugs[ $plugin ] = $dependent_slug; } self::$dependency_slugs = array_unique( self::$dependency_slugs ); } /** * Sanitizes slugs. * * @since 6.5.0 * * @param string $slugs A comma-separated string of plugin dependency slugs. * @return array An array of sanitized plugin dependency slugs. */ protected static function sanitize_dependency_slugs( $slugs ) { $sanitized_slugs = array(); $slugs = explode( ',', $slugs ); foreach ( $slugs as $slug ) { $slug = trim( $slug ); /** * Filters a plugin dependency's slug before matching to * the WordPress.org slug format. * * Can be used to switch between free and premium plugin slugs, for example. * * @since 6.5.0 * * @param string $slug The slug. */ $slug = apply_filters( 'wp_plugin_dependencies_slug', $slug ); // Match to WordPress.org slug format. if ( preg_match( '/^[a-z0-9]+(-[a-z0-9]+)*$/mu', $slug ) ) { $sanitized_slugs[] = $slug; } } $sanitized_slugs = array_unique( $sanitized_slugs ); sort( $sanitized_slugs ); return $sanitized_slugs; } /** * Gets the filepath of installed dependencies. * If a dependency is not installed, the filepath defaults to false. * * @since 6.5.0 * * @return array An array of install dependencies filepaths, relative to the plugins directory. */ protected static function get_dependency_filepaths() { if ( is_array( self::$dependency_filepaths ) ) { return self::$dependency_filepaths; } if ( null === self::$dependency_slugs ) { return array(); } self::$dependency_filepaths = array(); $plugin_dirnames = self::get_plugin_dirnames(); foreach ( self::$dependency_slugs as $slug ) { if ( isset( $plugin_dirnames[ $slug ] ) ) { self::$dependency_filepaths[ $slug ] = $plugin_dirnames[ $slug ]; continue; } self::$dependency_filepaths[ $slug ] = false; } return self::$dependency_filepaths; } /** * Retrieves and stores dependency plugin data from the WordPress.org Plugin API. * * @since 6.5.0 * * @global string $pagenow The filename of the current screen. * * @return array|void An array of dependency API data, or void on early exit. */ protected static function get_dependency_api_data() { global $pagenow; if ( ! is_admin() || ( 'plugins.php' !== $pagenow && 'plugin-install.php' !== $pagenow ) ) { return; } if ( is_array( self::$dependency_api_data ) ) { return self::$dependency_api_data; } $plugins = self::get_plugins(); self::$dependency_api_data = (array) get_site_transient( 'wp_plugin_dependencies_plugin_data' ); foreach ( self::$dependency_slugs as $slug ) { // Set transient for individual data, remove from self::$dependency_api_data if transient expired. if ( ! get_site_transient( "wp_plugin_dependencies_plugin_timeout_{$slug}" ) ) { unset( self::$dependency_api_data[ $slug ] ); set_site_transient( "wp_plugin_dependencies_plugin_timeout_{$slug}", true, 12 * HOUR_IN_SECONDS ); } if ( isset( self::$dependency_api_data[ $slug ] ) ) { if ( false === self::$dependency_api_data[ $slug ] ) { $dependency_file = self::get_dependency_filepath( $slug ); if ( false === $dependency_file ) { self::$dependency_api_data[ $slug ] = array( 'Name' => $slug ); } else { self::$dependency_api_data[ $slug ] = array( 'Name' => $plugins[ $dependency_file ]['Name'] ); } continue; } // Don't hit the Plugin API if data exists. if ( ! empty( self::$dependency_api_data[ $slug ]['last_updated'] ) ) { continue; } } if ( ! function_exists( 'plugins_api' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; } $information = plugins_api( 'plugin_information', array( 'slug' => $slug, 'fields' => array( 'short_description' => true, 'icons' => true, ), ) ); if ( is_wp_error( $information ) ) { continue; } self::$dependency_api_data[ $slug ] = (array) $information; // plugins_api() returns 'name' not 'Name'. self::$dependency_api_data[ $slug ]['Name'] = self::$dependency_api_data[ $slug ]['name']; set_site_transient( 'wp_plugin_dependencies_plugin_data', self::$dependency_api_data, 0 ); } // Remove from self::$dependency_api_data if slug no longer a dependency. $differences = array_diff( array_keys( self::$dependency_api_data ), self::$dependency_slugs ); foreach ( $differences as $difference ) { unset( self::$dependency_api_data[ $difference ] ); } ksort( self::$dependency_api_data ); // Remove empty elements. self::$dependency_api_data = array_filter( self::$dependency_api_data ); set_site_transient( 'wp_plugin_dependencies_plugin_data', self::$dependency_api_data, 0 ); return self::$dependency_api_data; } /** * Gets plugin directory names. * * @since 6.5.0 * * @return array An array of plugin directory names. */ protected static function get_plugin_dirnames() { if ( is_array( self::$plugin_dirnames ) ) { return self::$plugin_dirnames; } self::$plugin_dirnames = array(); $plugin_files = array_keys( self::get_plugins() ); foreach ( $plugin_files as $plugin_file ) { $slug = self::convert_to_slug( $plugin_file ); self::$plugin_dirnames[ $slug ] = $plugin_file; } return self::$plugin_dirnames; } /** * Gets circular dependency data. * * @since 6.5.0 * * @return array[] An array of circular dependency pairings. */ protected static function get_circular_dependencies() { if ( is_array( self::$circular_dependencies_pairs ) ) { return self::$circular_dependencies_pairs; } if ( null === self::$dependencies ) { return array(); } self::$circular_dependencies_slugs = array(); self::$circular_dependencies_pairs = array(); foreach ( self::$dependencies as $dependent => $dependencies ) { /* * $dependent is in 'a/a.php' format. Dependencies are stored as slugs, i.e. 'a'. * * Convert $dependent to slug format for checking. */ $dependent_slug = self::convert_to_slug( $dependent ); self::$circular_dependencies_pairs = array_merge( self::$circular_dependencies_pairs, self::check_for_circular_dependencies( array( $dependent_slug ), $dependencies ) ); } return self::$circular_dependencies_pairs; } /** * Checks for circular dependencies. * * @since 6.5.0 * * @param array $dependents Array of dependent plugins. * @param array $dependencies Array of plugins dependencies. * @return array A circular dependency pairing, or an empty array if none exists. */ protected static function check_for_circular_dependencies( $dependents, $dependencies ) { $circular_dependencies_pairs = array(); // Check for a self-dependency. $dependents_location_in_its_own_dependencies = array_intersect( $dependents, $dependencies ); if ( ! empty( $dependents_location_in_its_own_dependencies ) ) { foreach ( $dependents_location_in_its_own_dependencies as $self_dependency ) { self::$circular_dependencies_slugs[] = $self_dependency; $circular_dependencies_pairs[] = array( $self_dependency, $self_dependency ); // No need to check for itself again. unset( $dependencies[ array_search( $self_dependency, $dependencies, true ) ] ); } } /* * Check each dependency to see: * 1. If it has dependencies. * 2. If its list of dependencies includes one of its own dependents. */ foreach ( $dependencies as $dependency ) { // Check if the dependency is also a dependent. $dependency_location_in_dependents = array_search( $dependency, self::$dependent_slugs, true ); if ( false !== $dependency_location_in_dependents ) { $dependencies_of_the_dependency = self::$dependencies[ $dependency_location_in_dependents ]; foreach ( $dependents as $dependent ) { // Check if its dependencies includes one of its own dependents. $dependent_location_in_dependency_dependencies = array_search( $dependent, $dependencies_of_the_dependency, true ); if ( false !== $dependent_location_in_dependency_dependencies ) { self::$circular_dependencies_slugs[] = $dependent; self::$circular_dependencies_slugs[] = $dependency; $circular_dependencies_pairs[] = array( $dependent, $dependency ); // Remove the dependent from its dependency's dependencies. unset( $dependencies_of_the_dependency[ $dependent_location_in_dependency_dependencies ] ); } } $dependents[] = $dependency; /* * Now check the dependencies of the dependency's dependencies for the dependent. * * Yes, that does make sense. */ $circular_dependencies_pairs = array_merge( $circular_dependencies_pairs, self::check_for_circular_dependencies( $dependents, array_unique( $dependencies_of_the_dependency ) ) ); } } return $circular_dependencies_pairs; } /** * Converts a plugin filepath to a slug. * * @since 6.5.0 * * @param string $plugin_file The plugin's filepath, relative to the plugins directory. * @return string The plugin's slug. */ protected static function convert_to_slug( $plugin_file ) { if ( 'hello.php' === $plugin_file ) { return 'hello-dolly'; } return str_contains( $plugin_file, '/' ) ? dirname( $plugin_file ) : str_replace( '.php', '', $plugin_file ); } } class-wpdb.php 0000666 00000347543 15226762357 0007357 0 ustar 00 <?php /** * WordPress database access abstraction class. * * Original code from {@link http://php.justinvincent.com Justin Vincent (justin@visunet.ie)} * * @package WordPress * @subpackage Database * @since 0.71 */ /** * @since 0.71 */ define( 'EZSQL_VERSION', 'WP1.25' ); /** * @since 0.71 */ define( 'OBJECT', 'OBJECT' ); // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ConstantNotUpperCase define( 'object', 'OBJECT' ); // Back compat. /** * @since 2.5.0 */ define( 'OBJECT_K', 'OBJECT_K' ); /** * @since 0.71 */ define( 'ARRAY_A', 'ARRAY_A' ); /** * @since 0.71 */ define( 'ARRAY_N', 'ARRAY_N' ); /** * WordPress database access abstraction class. * * This class is used to interact with a database without needing to use raw SQL statements. * By default, WordPress uses this class to instantiate the global $wpdb object, providing * access to the WordPress database. * * It is possible to replace the global instance with your own by setting the $wpdb global variable * in wp-content/db.php file to your class. The wpdb class will still be included, so you can * extend it or simply use your own. * * @link https://developer.wordpress.org/reference/classes/wpdb/ * * @since 0.71 */ #[AllowDynamicProperties] class wpdb { /** * Whether to show SQL/DB errors. * * Default is to show errors if both WP_DEBUG and WP_DEBUG_DISPLAY evaluate to true. * * @since 0.71 * * @var bool */ public $show_errors = false; /** * Whether to suppress errors during the DB bootstrapping. Default false. * * @since 2.5.0 * * @var bool */ public $suppress_errors = false; /** * The error encountered during the last query. * * @since 2.5.0 * * @var string */ public $last_error = ''; /** * The number of queries made. * * @since 1.2.0 * * @var int */ public $num_queries = 0; /** * Count of rows returned by the last query. * * @since 0.71 * * @var int */ public $num_rows = 0; /** * Count of rows affected by the last query. * * @since 0.71 * * @var int */ public $rows_affected = 0; /** * The ID generated for an AUTO_INCREMENT column by the last query (usually INSERT). * * @since 0.71 * * @var int */ public $insert_id = 0; /** * The last query made. * * @since 0.71 * * @var string */ public $last_query; /** * Results of the last query. * * @since 0.71 * * @var stdClass[]|null */ public $last_result; /** * Database query result. * * Possible values: * * - `mysqli_result` instance for successful SELECT, SHOW, DESCRIBE, or EXPLAIN queries * - `true` for other query types that were successful * - `null` if a query is yet to be made or if the result has since been flushed * - `false` if the query returned an error * * @since 0.71 * * @var mysqli_result|bool|null */ protected $result; /** * Cached column info, for confidence checking data before inserting. * * @since 4.2.0 * * @var array */ protected $col_meta = array(); /** * Calculated character sets keyed by table name. * * @since 4.2.0 * * @var string[] */ protected $table_charset = array(); /** * Whether text fields in the current query need to be confidence checked. * * @since 4.2.0 * * @var bool */ protected $check_current_query = true; /** * Flag to ensure we don't run into recursion problems when checking the collation. * * @since 4.2.0 * * @see wpdb::check_safe_collation() * @var bool */ private $checking_collation = false; /** * Saved info on the table column. * * @since 0.71 * * @var array */ protected $col_info; /** * Log of queries that were executed, for debugging purposes. * * @since 1.5.0 * @since 2.5.0 The third element in each query log was added to record the calling functions. * @since 5.1.0 The fourth element in each query log was added to record the start time. * @since 5.3.0 The fifth element in each query log was added to record custom data. * * @var array[] { * Array of arrays containing information about queries that were executed. * * @type array ...$0 { * Data for each query. * * @type string $0 The query's SQL. * @type float $1 Total time spent on the query, in seconds. * @type string $2 Comma-separated list of the calling functions. * @type float $3 Unix timestamp of the time at the start of the query. * @type array $4 Custom query data. * } * } */ public $queries; /** * The number of times to retry reconnecting before dying. Default 5. * * @since 3.9.0 * * @see wpdb::check_connection() * @var int */ protected $reconnect_retries = 5; /** * WordPress table prefix. * * You can set this to have multiple WordPress installations in a single database. * * @since 2.5.0 * * @var string */ public $prefix = ''; /** * WordPress base table prefix. * * @since 3.0.0 * * @var string */ public $base_prefix; /** * Whether the database queries are ready to start executing. * * @since 2.3.2 * * @var bool */ public $ready = false; /** * Blog ID. * * @since 3.0.0 * * @var int */ public $blogid = 0; /** * Site ID. * * @since 3.0.0 * * @var int */ public $siteid = 0; /** * List of WordPress per-site tables. * * @since 2.5.0 * * @see wpdb::tables() * @var string[] */ public $tables = array( 'posts', 'comments', 'links', 'options', 'postmeta', 'terms', 'term_taxonomy', 'term_relationships', 'termmeta', 'commentmeta', ); /** * List of deprecated WordPress tables. * * 'categories', 'post2cat', and 'link2cat' were deprecated in 2.3.0, db version 5539. * * @since 2.9.0 * * @see wpdb::tables() * @var string[] */ public $old_tables = array( 'categories', 'post2cat', 'link2cat' ); /** * List of WordPress global tables. * * @since 3.0.0 * * @see wpdb::tables() * @var string[] */ public $global_tables = array( 'users', 'usermeta' ); /** * List of Multisite global tables. * * @since 3.0.0 * * @see wpdb::tables() * @var string[] */ public $ms_global_tables = array( 'blogs', 'blogmeta', 'signups', 'site', 'sitemeta', 'registration_log', ); /** * List of deprecated WordPress Multisite global tables. * * @since 6.1.0 * * @see wpdb::tables() * @var string[] */ public $old_ms_global_tables = array( 'sitecategories' ); /** * WordPress Comments table. * * @since 1.5.0 * * @var string */ public $comments; /** * WordPress Comment Metadata table. * * @since 2.9.0 * * @var string */ public $commentmeta; /** * WordPress Links table. * * @since 1.5.0 * * @var string */ public $links; /** * WordPress Options table. * * @since 1.5.0 * * @var string */ public $options; /** * WordPress Post Metadata table. * * @since 1.5.0 * * @var string */ public $postmeta; /** * WordPress Posts table. * * @since 1.5.0 * * @var string */ public $posts; /** * WordPress Terms table. * * @since 2.3.0 * * @var string */ public $terms; /** * WordPress Term Relationships table. * * @since 2.3.0 * * @var string */ public $term_relationships; /** * WordPress Term Taxonomy table. * * @since 2.3.0 * * @var string */ public $term_taxonomy; /** * WordPress Term Meta table. * * @since 4.4.0 * * @var string */ public $termmeta; // // Global and Multisite tables // /** * WordPress User Metadata table. * * @since 2.3.0 * * @var string */ public $usermeta; /** * WordPress Users table. * * @since 1.5.0 * * @var string */ public $users; /** * Multisite Blogs table. * * @since 3.0.0 * * @var string|null */ public $blogs; /** * Multisite Blog Metadata table. * * @since 5.1.0 * * @var string|null */ public $blogmeta; /** * Multisite Registration Log table. * * @since 3.0.0 * * @var string|null */ public $registration_log; /** * Multisite Signups table. * * @since 3.0.0 * * @var string|null */ public $signups; /** * Multisite Sites table. * * @since 3.0.0 * * @var string|null */ public $site; /** * Multisite Sitewide Terms table. * * @since 3.0.0 * * @var string|null */ public $sitecategories; /** * Multisite Site Metadata table. * * @since 3.0.0 * * @var string|null */ public $sitemeta; /** * Format specifiers for DB columns. * * Columns not listed here default to %s. Initialized during WP load. * Keys are column names, values are format types: 'ID' => '%d'. * * @since 2.8.0 * * @see wpdb::prepare() * @see wpdb::insert() * @see wpdb::update() * @see wpdb::delete() * @see wp_set_wpdb_vars() * @var array */ public $field_types = array(); /** * Database table columns charset. * * @since 2.2.0 * * @var string */ public $charset; /** * Database table columns collate. * * @since 2.2.0 * * @var string */ public $collate; /** * Database Username. * * @since 2.9.0 * * @var string */ protected $dbuser; /** * Database Password. * * @since 3.1.0 * * @var string */ protected $dbpassword; /** * Database Name. * * @since 3.1.0 * * @var string */ protected $dbname; /** * Database Host. * * @since 3.1.0 * * @var string */ protected $dbhost; /** * Database handle. * * Possible values: * * - `mysqli` instance during normal operation * - `null` if the connection is yet to be made or has been closed * - `false` if the connection has failed * * @since 0.71 * * @var mysqli|false|null */ protected $dbh; /** * A textual description of the last query/get_row/get_var call. * * @since 3.0.0 * * @var string */ public $func_call; /** * Whether MySQL is used as the database engine. * * Set in wpdb::db_connect() to true, by default. This is used when checking * against the required MySQL version for WordPress. Normally, a replacement * database drop-in (db.php) will skip these checks, but setting this to true * will force the checks to occur. * * @since 3.3.0 * * @var bool */ public $is_mysql = null; /** * A list of incompatible SQL modes. * * @since 3.9.0 * * @var string[] */ protected $incompatible_modes = array( 'NO_ZERO_DATE', 'ONLY_FULL_GROUP_BY', 'STRICT_TRANS_TABLES', 'STRICT_ALL_TABLES', 'TRADITIONAL', 'ANSI', ); /** * Backward compatibility, where wpdb::prepare() has not quoted formatted/argnum placeholders. * * This is often used for table/field names (before %i was supported), and sometimes string formatting, e.g. * * $wpdb->prepare( 'WHERE `%1$s` = "%2$s something %3$s" OR %1$s = "%4$-10s"', 'field_1', 'a', 'b', 'c' ); * * But it's risky, e.g. forgetting to add quotes, resulting in SQL Injection vulnerabilities: * * $wpdb->prepare( 'WHERE (id = %1s) OR (id = %2$s)', $_GET['id'], $_GET['id'] ); // ?id=id * * This feature is preserved while plugin authors update their code to use safer approaches: * * $_GET['key'] = 'a`b'; * * $wpdb->prepare( 'WHERE %1s = %s', $_GET['key'], $_GET['value'] ); // WHERE a`b = 'value' * $wpdb->prepare( 'WHERE `%1$s` = "%2$s"', $_GET['key'], $_GET['value'] ); // WHERE `a`b` = "value" * * $wpdb->prepare( 'WHERE %i = %s', $_GET['key'], $_GET['value'] ); // WHERE `a``b` = 'value' * * While changing to false will be fine for queries not using formatted/argnum placeholders, * any remaining cases are most likely going to result in SQL errors (good, in a way): * * $wpdb->prepare( 'WHERE %1$s = "%2$-10s"', 'my_field', 'my_value' ); * true = WHERE my_field = "my_value " * false = WHERE 'my_field' = "'my_value '" * * But there may be some queries that result in an SQL Injection vulnerability: * * $wpdb->prepare( 'WHERE id = %1$s', $_GET['id'] ); // ?id=id * * So there may need to be a `_doing_it_wrong()` phase, after we know everyone can use * identifier placeholders (%i), but before this feature is disabled or removed. * * @since 6.2.0 * @var bool */ private $allow_unsafe_unquoted_parameters = true; /** * Whether to use the mysqli extension over mysql. This is no longer used as the mysql * extension is no longer supported. * * Default true. * * @since 3.9.0 * @since 6.4.0 This property was removed. * @since 6.4.1 This property was reinstated and its default value was changed to true. * The property is no longer used in core but may be accessed externally. * * @var bool */ private $use_mysqli = true; /** * Whether we've managed to successfully connect at some point. * * @since 3.9.0 * * @var bool */ private $has_connected = false; /** * Time when the last query was performed. * * Only set when `SAVEQUERIES` is defined and truthy. * * @since 1.5.0 * * @var float */ public $time_start = null; /** * The last SQL error that was encountered. * * @since 2.5.0 * * @var WP_Error|string */ public $error = null; /** * Connects to the database server and selects a database. * * Does the actual setting up * of the class properties and connection to the database. * * @since 2.0.8 * * @link https://core.trac.wordpress.org/ticket/3354 * * @param string $dbuser Database user. * @param string $dbpassword Database password. * @param string $dbname Database name. * @param string $dbhost Database host. */ public function __construct( $dbuser, #[\SensitiveParameter] $dbpassword, $dbname, $dbhost ) { if ( WP_DEBUG && WP_DEBUG_DISPLAY ) { $this->show_errors(); } $this->dbuser = $dbuser; $this->dbpassword = $dbpassword; $this->dbname = $dbname; $this->dbhost = $dbhost; // wp-config.php creation will manually connect when ready. if ( defined( 'WP_SETUP_CONFIG' ) ) { return; } $this->db_connect(); } /** * Makes private properties readable for backward compatibility. * * @since 3.5.0 * * @param string $name The private member to get, and optionally process. * @return mixed The private member. */ public function __get( $name ) { if ( 'col_info' === $name ) { $this->load_col_info(); } return $this->$name; } /** * Makes private properties settable for backward compatibility. * * @since 3.5.0 * * @param string $name The private member to set. * @param mixed $value The value to set. */ public function __set( $name, $value ) { $protected_members = array( 'col_meta', 'table_charset', 'check_current_query', 'allow_unsafe_unquoted_parameters', ); if ( in_array( $name, $protected_members, true ) ) { return; } $this->$name = $value; } /** * Makes private properties check-able for backward compatibility. * * @since 3.5.0 * * @param string $name The private member to check. * @return bool If the member is set or not. */ public function __isset( $name ) { return isset( $this->$name ); } /** * Makes private properties un-settable for backward compatibility. * * @since 3.5.0 * * @param string $name The private member to unset. */ public function __unset( $name ) { unset( $this->$name ); } /** * Sets $this->charset and $this->collate. * * @since 3.1.0 */ public function init_charset() { $charset = ''; $collate = ''; if ( function_exists( 'is_multisite' ) && is_multisite() ) { $charset = 'utf8'; if ( defined( 'DB_COLLATE' ) && DB_COLLATE ) { $collate = DB_COLLATE; } else { $collate = 'utf8_general_ci'; } } elseif ( defined( 'DB_COLLATE' ) ) { $collate = DB_COLLATE; } if ( defined( 'DB_CHARSET' ) ) { $charset = DB_CHARSET; } $charset_collate = $this->determine_charset( $charset, $collate ); $this->charset = $charset_collate['charset']; $this->collate = $charset_collate['collate']; } /** * Determines the best charset and collation to use given a charset and collation. * * For example, when able, utf8mb4 should be used instead of utf8. * * @since 4.6.0 * * @param string $charset The character set to check. * @param string $collate The collation to check. * @return array { * The most appropriate character set and collation to use. * * @type string $charset Character set. * @type string $collate Collation. * } */ public function determine_charset( $charset, $collate ) { if ( ( ! ( $this->dbh instanceof mysqli ) ) || empty( $this->dbh ) ) { return compact( 'charset', 'collate' ); } if ( 'utf8' === $charset ) { $charset = 'utf8mb4'; } if ( 'utf8mb4' === $charset ) { // _general_ is outdated, so we can upgrade it to _unicode_, instead. if ( ! $collate || 'utf8_general_ci' === $collate ) { $collate = 'utf8mb4_unicode_ci'; } else { $collate = str_replace( 'utf8_', 'utf8mb4_', $collate ); } } // _unicode_520_ is a better collation, we should use that when it's available. if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) { $collate = 'utf8mb4_unicode_520_ci'; } return compact( 'charset', 'collate' ); } /** * Sets the connection's character set. * * @since 3.1.0 * * @param mysqli $dbh The connection returned by `mysqli_connect()`. * @param string $charset Optional. The character set. Default null. * @param string $collate Optional. The collation. Default null. */ public function set_charset( $dbh, $charset = null, $collate = null ) { if ( ! isset( $charset ) ) { $charset = $this->charset; } if ( ! isset( $collate ) ) { $collate = $this->collate; } if ( $this->has_cap( 'collation' ) && ! empty( $charset ) ) { $set_charset_succeeded = true; if ( function_exists( 'mysqli_set_charset' ) && $this->has_cap( 'set_charset' ) ) { $set_charset_succeeded = mysqli_set_charset( $dbh, $charset ); } if ( $set_charset_succeeded ) { $query = $this->prepare( 'SET NAMES %s', $charset ); if ( ! empty( $collate ) ) { $query .= $this->prepare( ' COLLATE %s', $collate ); } mysqli_query( $dbh, $query ); } } } /** * Changes the current SQL mode, and ensures its WordPress compatibility. * * If no modes are passed, it will ensure the current SQL server modes are compatible. * * @since 3.9.0 * * @param array $modes Optional. A list of SQL modes to set. Default empty array. */ public function set_sql_mode( $modes = array() ) { if ( empty( $modes ) ) { $res = mysqli_query( $this->dbh, 'SELECT @@SESSION.sql_mode' ); if ( empty( $res ) ) { return; } $modes_array = mysqli_fetch_array( $res ); if ( empty( $modes_array[0] ) ) { return; } $modes_str = $modes_array[0]; if ( empty( $modes_str ) ) { return; } $modes = explode( ',', $modes_str ); } $modes = array_change_key_case( $modes, CASE_UPPER ); /** * Filters the list of incompatible SQL modes to exclude. * * @since 3.9.0 * * @param array $incompatible_modes An array of incompatible modes. */ $incompatible_modes = (array) apply_filters( 'incompatible_sql_modes', $this->incompatible_modes ); foreach ( $modes as $i => $mode ) { if ( in_array( $mode, $incompatible_modes, true ) ) { unset( $modes[ $i ] ); } } $modes_str = implode( ',', $modes ); mysqli_query( $this->dbh, "SET SESSION sql_mode='$modes_str'" ); } /** * Sets the table prefix for the WordPress tables. * * @since 2.5.0 * * @param string $prefix Alphanumeric name for the new prefix. * @param bool $set_table_names Optional. Whether the table names, e.g. wpdb::$posts, * should be updated or not. Default true. * @return string|WP_Error Old prefix or WP_Error on error. */ public function set_prefix( $prefix, $set_table_names = true ) { if ( preg_match( '|[^a-z0-9_]|i', $prefix ) ) { return new WP_Error( 'invalid_db_prefix', 'Invalid database prefix' ); } $old_prefix = is_multisite() ? '' : $prefix; if ( isset( $this->base_prefix ) ) { $old_prefix = $this->base_prefix; } $this->base_prefix = $prefix; if ( $set_table_names ) { foreach ( $this->tables( 'global' ) as $table => $prefixed_table ) { $this->$table = $prefixed_table; } if ( is_multisite() && empty( $this->blogid ) ) { return $old_prefix; } $this->prefix = $this->get_blog_prefix(); foreach ( $this->tables( 'blog' ) as $table => $prefixed_table ) { $this->$table = $prefixed_table; } foreach ( $this->tables( 'old' ) as $table => $prefixed_table ) { $this->$table = $prefixed_table; } } return $old_prefix; } /** * Sets blog ID. * * @since 3.0.0 * * @param int $blog_id * @param int $network_id Optional. Network ID. Default 0. * @return int Previous blog ID. */ public function set_blog_id( $blog_id, $network_id = 0 ) { if ( ! empty( $network_id ) ) { $this->siteid = $network_id; } $old_blog_id = $this->blogid; $this->blogid = $blog_id; $this->prefix = $this->get_blog_prefix(); foreach ( $this->tables( 'blog' ) as $table => $prefixed_table ) { $this->$table = $prefixed_table; } foreach ( $this->tables( 'old' ) as $table => $prefixed_table ) { $this->$table = $prefixed_table; } return $old_blog_id; } /** * Gets blog prefix. * * @since 3.0.0 * * @param int $blog_id Optional. Blog ID to retrieve the table prefix for. * Defaults to the current blog ID. * @return string Blog prefix. */ public function get_blog_prefix( $blog_id = null ) { if ( is_multisite() ) { if ( null === $blog_id ) { $blog_id = $this->blogid; } $blog_id = (int) $blog_id; if ( defined( 'MULTISITE' ) && ( 0 === $blog_id || 1 === $blog_id ) ) { return $this->base_prefix; } else { return $this->base_prefix . $blog_id . '_'; } } else { return $this->base_prefix; } } /** * Returns an array of WordPress tables. * * Also allows for the `CUSTOM_USER_TABLE` and `CUSTOM_USER_META_TABLE` to override the WordPress users * and usermeta tables that would otherwise be determined by the prefix. * * The `$scope` argument can take one of the following: * * - 'all' - returns 'all' and 'global' tables. No old tables are returned. * - 'blog' - returns the blog-level tables for the queried blog. * - 'global' - returns the global tables for the installation, returning multisite tables only on multisite. * - 'ms_global' - returns the multisite global tables, regardless if current installation is multisite. * - 'old' - returns tables which are deprecated. * * @since 3.0.0 * @since 6.1.0 `old` now includes deprecated multisite global tables only on multisite. * * @uses wpdb::$tables * @uses wpdb::$old_tables * @uses wpdb::$global_tables * @uses wpdb::$ms_global_tables * @uses wpdb::$old_ms_global_tables * * @param string $scope Optional. Possible values include 'all', 'global', 'ms_global', 'blog', * or 'old' tables. Default 'all'. * @param bool $prefix Optional. Whether to include table prefixes. If blog prefix is requested, * then the custom users and usermeta tables will be mapped. Default true. * @param int $blog_id Optional. The blog_id to prefix. Used only when prefix is requested. * Defaults to `wpdb::$blogid`. * @return string[] Table names. When a prefix is requested, the key is the unprefixed table name. */ public function tables( $scope = 'all', $prefix = true, $blog_id = 0 ) { switch ( $scope ) { case 'all': $tables = array_merge( $this->global_tables, $this->tables ); if ( is_multisite() ) { $tables = array_merge( $tables, $this->ms_global_tables ); } break; case 'blog': $tables = $this->tables; break; case 'global': $tables = $this->global_tables; if ( is_multisite() ) { $tables = array_merge( $tables, $this->ms_global_tables ); } break; case 'ms_global': $tables = $this->ms_global_tables; break; case 'old': $tables = $this->old_tables; if ( is_multisite() ) { $tables = array_merge( $tables, $this->old_ms_global_tables ); } break; default: return array(); } if ( $prefix ) { if ( ! $blog_id ) { $blog_id = $this->blogid; } $blog_prefix = $this->get_blog_prefix( $blog_id ); $base_prefix = $this->base_prefix; $global_tables = array_merge( $this->global_tables, $this->ms_global_tables ); foreach ( $tables as $k => $table ) { if ( in_array( $table, $global_tables, true ) ) { $tables[ $table ] = $base_prefix . $table; } else { $tables[ $table ] = $blog_prefix . $table; } unset( $tables[ $k ] ); } if ( isset( $tables['users'] ) && defined( 'CUSTOM_USER_TABLE' ) ) { $tables['users'] = CUSTOM_USER_TABLE; } if ( isset( $tables['usermeta'] ) && defined( 'CUSTOM_USER_META_TABLE' ) ) { $tables['usermeta'] = CUSTOM_USER_META_TABLE; } } return $tables; } /** * Selects a database using the current or provided database connection. * * The database name will be changed based on the current database connection. * On failure, the execution will bail and display a DB error. * * @since 0.71 * * @param string $db Database name. * @param mysqli $dbh Optional. Database connection. * Defaults to the current database handle. */ public function select( $db, $dbh = null ) { if ( is_null( $dbh ) ) { $dbh = $this->dbh; } $success = mysqli_select_db( $dbh, $db ); if ( ! $success ) { $this->ready = false; if ( ! did_action( 'template_redirect' ) ) { wp_load_translations_early(); $message = '<h1>' . __( 'Cannot select database' ) . "</h1>\n"; $message .= '<p>' . sprintf( /* translators: %s: Database name. */ __( 'The database server could be connected to (which means your username and password is okay) but the %s database could not be selected.' ), '<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>' ) . "</p>\n"; $message .= "<ul>\n"; $message .= '<li>' . __( 'Are you sure it exists?' ) . "</li>\n"; $message .= '<li>' . sprintf( /* translators: 1: Database user, 2: Database name. */ __( 'Does the user %1$s have permission to use the %2$s database?' ), '<code>' . htmlspecialchars( $this->dbuser, ENT_QUOTES ) . '</code>', '<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>' ) . "</li>\n"; $message .= '<li>' . sprintf( /* translators: %s: Database name. */ __( 'On some systems the name of your database is prefixed with your username, so it would be like <code>username_%1$s</code>. Could that be the problem?' ), htmlspecialchars( $db, ENT_QUOTES ) ) . "</li>\n"; $message .= "</ul>\n"; $message .= '<p>' . sprintf( /* translators: %s: Support forums URL. */ __( 'If you do not know how to set up a database you should <strong>contact your host</strong>. If all else fails you may find help at the <a href="%s">WordPress support forums</a>.' ), __( 'https://wordpress.org/support/forums/' ) ) . "</p>\n"; $this->bail( $message, 'db_select_fail' ); } } } /** * Do not use, deprecated. * * Use esc_sql() or wpdb::prepare() instead. * * @since 2.8.0 * @deprecated 3.6.0 Use wpdb::prepare() * @see wpdb::prepare() * @see esc_sql() * * @param string $data * @return string */ public function _weak_escape( $data ) { if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) { _deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' ); } return addslashes( $data ); } /** * Real escape using mysqli_real_escape_string(). * * @since 2.8.0 * * @see mysqli_real_escape_string() * * @param string $data String to escape. * @return string Escaped string. */ public function _real_escape( $data ) { if ( ! is_scalar( $data ) ) { return ''; } if ( $this->dbh ) { $escaped = mysqli_real_escape_string( $this->dbh, $data ); } else { $class = get_class( $this ); wp_load_translations_early(); /* translators: %s: Database access abstraction class, usually wpdb or a class extending wpdb. */ _doing_it_wrong( $class, sprintf( __( '%s must set a database connection for use with escaping.' ), $class ), '3.6.0' ); $escaped = addslashes( $data ); } return $this->add_placeholder_escape( $escaped ); } /** * Escapes data. Works on arrays. * * @since 2.8.0 * * @uses wpdb::_real_escape() * * @param string|array $data Data to escape. * @return string|array Escaped data, in the same type as supplied. */ public function _escape( $data ) { if ( is_array( $data ) ) { foreach ( $data as $k => $v ) { if ( is_array( $v ) ) { $data[ $k ] = $this->_escape( $v ); } else { $data[ $k ] = $this->_real_escape( $v ); } } } else { $data = $this->_real_escape( $data ); } return $data; } /** * Do not use, deprecated. * * Use esc_sql() or wpdb::prepare() instead. * * @since 0.71 * @deprecated 3.6.0 Use wpdb::prepare() * @see wpdb::prepare() * @see esc_sql() * * @param string|array $data Data to escape. * @return string|array Escaped data, in the same type as supplied. */ public function escape( $data ) { if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) { _deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' ); } if ( is_array( $data ) ) { foreach ( $data as $k => $v ) { if ( is_array( $v ) ) { $data[ $k ] = $this->escape( $v, 'recursive' ); } else { $data[ $k ] = $this->_weak_escape( $v, 'internal' ); } } } else { $data = $this->_weak_escape( $data, 'internal' ); } return $data; } /** * Escapes content by reference for insertion into the database, for security. * * @uses wpdb::_real_escape() * * @since 2.3.0 * * @param string $data String to escape. */ public function escape_by_ref( &$data ) { if ( ! is_float( $data ) ) { $data = $this->_real_escape( $data ); } } /** * Quotes an identifier such as a table or field name. * * @since 6.2.0 * * @param string $identifier Identifier to escape. * @return string Escaped identifier. */ public function quote_identifier( $identifier ) { return '`' . $this->_escape_identifier_value( $identifier ) . '`'; } /** * Escapes an identifier value without adding the surrounding quotes. * * - Permitted characters in quoted identifiers include the full Unicode * Basic Multilingual Plane (BMP), except U+0000. * - To quote the identifier itself, you need to double the character, e.g. `a``b`. * * @since 6.2.0 * * @link https://dev.mysql.com/doc/refman/8.0/en/identifiers.html * * @param string $identifier Identifier to escape. * @return string Escaped identifier. */ private function _escape_identifier_value( $identifier ) { return str_replace( '`', '``', $identifier ); } /** * Prepares a SQL query for safe execution. * * Uses `sprintf()`-like syntax. The following placeholders can be used in the query string: * * - `%d` (integer) * - `%f` (float) * - `%s` (string) * - `%i` (identifier, e.g. table/field names) * * All placeholders MUST be left unquoted in the query string. A corresponding argument * MUST be passed for each placeholder. * * Note: There is one exception to the above: for compatibility with old behavior, * numbered or formatted string placeholders (eg, `%1$s`, `%5s`) will not have quotes * added by this function, so should be passed with appropriate quotes around them. * * Literal percentage signs (`%`) in the query string must be written as `%%`. Percentage wildcards * (for example, to use in LIKE syntax) must be passed via a substitution argument containing * the complete LIKE string, these cannot be inserted directly in the query string. * Also see wpdb::esc_like(). * * Arguments may be passed as individual arguments to the method, or as a single array * containing all arguments. A combination of the two is not supported. * * Examples: * * $wpdb->prepare( * "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `other_field` LIKE %s", * array( 'foo', 1337, '%bar' ) * ); * * $wpdb->prepare( * "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s", * 'foo' * ); * * $wpdb->prepare( * "SELECT * FROM %i WHERE %i = %s", * $table, * $field, * $value * ); * * @since 2.3.0 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by updating the function signature. The second parameter was changed * from `$args` to `...$args`. * @since 6.2.0 Added `%i` for identifiers, e.g. table or field names. * Check support via `wpdb::has_cap( 'identifier_placeholders' )`. * This preserves compatibility with `sprintf()`, as the C version uses * `%d` and `$i` as a signed integer, whereas PHP only supports `%d`. * * @link https://www.php.net/sprintf Description of syntax. * * @param string $query Query statement with `sprintf()`-like placeholders. * @param array|mixed $args The array of variables to substitute into the query's placeholders * if being called with an array of arguments, or the first variable * to substitute into the query's placeholders if being called with * individual arguments. * @param mixed ...$args Further variables to substitute into the query's placeholders * if being called with individual arguments. * @return string|void Sanitized query string, if there is a query to prepare. */ public function prepare( $query, ...$args ) { if ( is_null( $query ) ) { return; } /* * This is not meant to be foolproof -- but it will catch obviously incorrect usage. * * Note: str_contains() is not used here, as this file can be included * directly outside of WordPress core, e.g. by HyperDB, in which case * the polyfills from wp-includes/compat.php are not loaded. */ if ( false === strpos( $query, '%' ) ) { wp_load_translations_early(); _doing_it_wrong( 'wpdb::prepare', sprintf( /* translators: %s: wpdb::prepare() */ __( 'The query argument of %s must have a placeholder.' ), 'wpdb::prepare()' ), '3.9.0' ); } /* * Specify the formatting allowed in a placeholder. The following are allowed: * * - Sign specifier, e.g. $+d * - Numbered placeholders, e.g. %1$s * - Padding specifier, including custom padding characters, e.g. %05s, %'#5s * - Alignment specifier, e.g. %05-s * - Precision specifier, e.g. %.2f */ $allowed_format = '(?:[1-9][0-9]*[$])?[-+0-9]*(?: |0|\'.)?[-+0-9]*(?:\.[0-9]+)?'; /* * If a %s placeholder already has quotes around it, removing the existing quotes * and re-inserting them ensures the quotes are consistent. * * For backward compatibility, this is only applied to %s, and not to placeholders like %1$s, * which are frequently used in the middle of longer strings, or as table name placeholders. */ $query = str_replace( "'%s'", '%s', $query ); // Strip any existing single quotes. $query = str_replace( '"%s"', '%s', $query ); // Strip any existing double quotes. // Escape any unescaped percents (i.e. anything unrecognised). $query = preg_replace( "/%(?:%|$|(?!($allowed_format)?[sdfFi]))/", '%%\\1', $query ); // Extract placeholders from the query. $split_query = preg_split( "/(^|[^%]|(?:%%)+)(%(?:$allowed_format)?[sdfFi])/", $query, -1, PREG_SPLIT_DELIM_CAPTURE ); $split_query_count = count( $split_query ); /* * Split always returns with 1 value before the first placeholder (even with $query = "%s"), * then 3 additional values per placeholder. */ $placeholder_count = ( ( $split_query_count - 1 ) / 3 ); // If args were passed as an array, as in vsprintf(), move them up. $passed_as_array = ( isset( $args[0] ) && is_array( $args[0] ) && 1 === count( $args ) ); if ( $passed_as_array ) { $args = $args[0]; } $new_query = ''; $key = 2; // Keys 0 and 1 in $split_query contain values before the first placeholder. $arg_id = 0; $arg_identifiers = array(); $arg_strings = array(); while ( $key < $split_query_count ) { $placeholder = $split_query[ $key ]; $format = substr( $placeholder, 1, -1 ); $type = substr( $placeholder, -1 ); if ( 'f' === $type && true === $this->allow_unsafe_unquoted_parameters /* * Note: str_ends_with() is not used here, as this file can be included * directly outside of WordPress core, e.g. by HyperDB, in which case * the polyfills from wp-includes/compat.php are not loaded. */ && '%' === substr( $split_query[ $key - 1 ], -1, 1 ) ) { /* * Before WP 6.2 the "force floats to be locale-unaware" RegEx didn't * convert "%%%f" to "%%%F" (note the uppercase F). * This was because it didn't check to see if the leading "%" was escaped. * And because the "Escape any unescaped percents" RegEx used "[sdF]" in its * negative lookahead assertion, when there was an odd number of "%", it added * an extra "%", to give the fully escaped "%%%%f" (not a placeholder). */ $s = $split_query[ $key - 2 ] . $split_query[ $key - 1 ]; $k = 1; $l = strlen( $s ); while ( $k <= $l && '%' === $s[ $l - $k ] ) { ++$k; } $placeholder = '%' . ( $k % 2 ? '%' : '' ) . $format . $type; --$placeholder_count; } else { // Force floats to be locale-unaware. if ( 'f' === $type ) { $type = 'F'; $placeholder = '%' . $format . $type; } if ( 'i' === $type ) { $placeholder = '`%' . $format . 's`'; // Using a simple strpos() due to previous checking (e.g. $allowed_format). $argnum_pos = strpos( $format, '$' ); if ( false !== $argnum_pos ) { // sprintf() argnum starts at 1, $arg_id from 0. $arg_identifiers[] = ( ( (int) substr( $format, 0, $argnum_pos ) ) - 1 ); } else { $arg_identifiers[] = $arg_id; } } elseif ( 'd' !== $type && 'F' !== $type ) { /* * i.e. ( 's' === $type ), where 'd' and 'F' keeps $placeholder unchanged, * and we ensure string escaping is used as a safe default (e.g. even if 'x'). */ $argnum_pos = strpos( $format, '$' ); if ( false !== $argnum_pos ) { $arg_strings[] = ( ( (int) substr( $format, 0, $argnum_pos ) ) - 1 ); } else { $arg_strings[] = $arg_id; } /* * Unquoted strings for backward compatibility (dangerous). * First, "numbered or formatted string placeholders (eg, %1$s, %5s)". * Second, if "%s" has a "%" before it, even if it's unrelated (e.g. "LIKE '%%%s%%'"). */ if ( true !== $this->allow_unsafe_unquoted_parameters /* * Note: str_ends_with() is not used here, as this file can be included * directly outside of WordPress core, e.g. by HyperDB, in which case * the polyfills from wp-includes/compat.php are not loaded. */ || ( '' === $format && '%' !== substr( $split_query[ $key - 1 ], -1, 1 ) ) ) { $placeholder = "'%" . $format . "s'"; } } } // Glue (-2), any leading characters (-1), then the new $placeholder. $new_query .= $split_query[ $key - 2 ] . $split_query[ $key - 1 ] . $placeholder; $key += 3; ++$arg_id; } // Replace $query; and add remaining $query characters, or index 0 if there were no placeholders. $query = $new_query . $split_query[ $key - 2 ]; $dual_use = array_intersect( $arg_identifiers, $arg_strings ); if ( count( $dual_use ) > 0 ) { wp_load_translations_early(); $used_placeholders = array(); $key = 2; $arg_id = 0; // Parse again (only used when there is an error). while ( $key < $split_query_count ) { $placeholder = $split_query[ $key ]; $format = substr( $placeholder, 1, -1 ); $argnum_pos = strpos( $format, '$' ); if ( false !== $argnum_pos ) { $arg_pos = ( ( (int) substr( $format, 0, $argnum_pos ) ) - 1 ); } else { $arg_pos = $arg_id; } $used_placeholders[ $arg_pos ][] = $placeholder; $key += 3; ++$arg_id; } $conflicts = array(); foreach ( $dual_use as $arg_pos ) { $conflicts[] = implode( ' and ', $used_placeholders[ $arg_pos ] ); } _doing_it_wrong( 'wpdb::prepare', sprintf( /* translators: %s: A list of placeholders found to be a problem. */ __( 'Arguments cannot be prepared as both an Identifier and Value. Found the following conflicts: %s' ), implode( ', ', $conflicts ) ), '6.2.0' ); return; } $args_count = count( $args ); if ( $args_count !== $placeholder_count ) { if ( 1 === $placeholder_count && $passed_as_array ) { /* * If the passed query only expected one argument, * but the wrong number of arguments was sent as an array, bail. */ wp_load_translations_early(); _doing_it_wrong( 'wpdb::prepare', __( 'The query only expected one placeholder, but an array of multiple placeholders was sent.' ), '4.9.0' ); return; } else { /* * If we don't have the right number of placeholders, * but they were passed as individual arguments, * or we were expecting multiple arguments in an array, throw a warning. */ wp_load_translations_early(); _doing_it_wrong( 'wpdb::prepare', sprintf( /* translators: 1: Number of placeholders, 2: Number of arguments passed. */ __( 'The query does not contain the correct number of placeholders (%1$d) for the number of arguments passed (%2$d).' ), $placeholder_count, $args_count ), '4.8.3' ); /* * If we don't have enough arguments to match the placeholders, * return an empty string to avoid a fatal error on PHP 8. */ if ( $args_count < $placeholder_count ) { $max_numbered_placeholder = 0; for ( $i = 2, $l = $split_query_count; $i < $l; $i += 3 ) { // Assume a leading number is for a numbered placeholder, e.g. '%3$s'. $argnum = (int) substr( $split_query[ $i ], 1 ); if ( $max_numbered_placeholder < $argnum ) { $max_numbered_placeholder = $argnum; } } if ( ! $max_numbered_placeholder || $args_count < $max_numbered_placeholder ) { return ''; } } } } $args_escaped = array(); foreach ( $args as $i => $value ) { if ( in_array( $i, $arg_identifiers, true ) ) { $args_escaped[] = $this->_escape_identifier_value( $value ); } elseif ( is_int( $value ) || is_float( $value ) ) { $args_escaped[] = $value; } else { if ( ! is_scalar( $value ) && ! is_null( $value ) ) { wp_load_translations_early(); _doing_it_wrong( 'wpdb::prepare', sprintf( /* translators: %s: Value type. */ __( 'Unsupported value type (%s).' ), gettype( $value ) ), '4.8.2' ); // Preserving old behavior, where values are escaped as strings. $value = ''; } $args_escaped[] = $this->_real_escape( $value ); } } $query = vsprintf( $query, $args_escaped ); return $this->add_placeholder_escape( $query ); } /** * First half of escaping for `LIKE` special characters `%` and `_` before preparing for SQL. * * Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security. * * Example Prepared Statement: * * $wild = '%'; * $find = 'only 43% of planets'; * $like = $wild . $wpdb->esc_like( $find ) . $wild; * $sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like ); * * Example Escape Chain: * * $sql = esc_sql( $wpdb->esc_like( $input ) ); * * @since 4.0.0 * * @param string $text The raw text to be escaped. The input typed by the user * should have no extra or deleted slashes. * @return string Text in the form of a LIKE phrase. The output is not SQL safe. * Call wpdb::prepare() or wpdb::_real_escape() next. */ public function esc_like( $text ) { return addcslashes( $text, '_%\\' ); } /** * Prints SQL/DB error. * * @since 0.71 * * @global array $EZSQL_ERROR Stores error information of query and error string. * * @param string $str The error to display. * @return void|false Void if the showing of errors is enabled, false if disabled. */ public function print_error( $str = '' ) { global $EZSQL_ERROR; if ( ! $str ) { $str = mysqli_error( $this->dbh ); } $EZSQL_ERROR[] = array( 'query' => $this->last_query, 'error_str' => $str, ); if ( $this->suppress_errors ) { return false; } $caller = $this->get_caller(); if ( $caller ) { // Not translated, as this will only appear in the error log. $error_str = sprintf( 'WordPress database error %1$s for query %2$s made by %3$s', $str, $this->last_query, $caller ); } else { $error_str = sprintf( 'WordPress database error %1$s for query %2$s', $str, $this->last_query ); } error_log( $error_str ); // Are we showing errors? if ( ! $this->show_errors ) { return false; } wp_load_translations_early(); // If there is an error then take note of it. if ( is_multisite() ) { $msg = sprintf( "%s [%s]\n%s\n", __( 'WordPress database error:' ), $str, $this->last_query ); if ( defined( 'ERRORLOGFILE' ) ) { error_log( $msg, 3, ERRORLOGFILE ); } if ( defined( 'DIEONDBERROR' ) ) { wp_die( $msg ); } } else { $str = htmlspecialchars( $str, ENT_QUOTES ); $query = htmlspecialchars( $this->last_query, ENT_QUOTES ); printf( '<div id="error"><p class="wpdberror"><strong>%s</strong> [%s]<br /><code>%s</code></p></div>', __( 'WordPress database error:' ), $str, $query ); } } /** * Enables showing of database errors. * * This function should be used only to enable showing of errors. * wpdb::hide_errors() should be used instead for hiding errors. * * @since 0.71 * * @see wpdb::hide_errors() * * @param bool $show Optional. Whether to show errors. Default true. * @return bool Whether showing of errors was previously active. */ public function show_errors( $show = true ) { $errors = $this->show_errors; $this->show_errors = $show; return $errors; } /** * Disables showing of database errors. * * By default database errors are not shown. * * @since 0.71 * * @see wpdb::show_errors() * * @return bool Whether showing of errors was previously active. */ public function hide_errors() { $show = $this->show_errors; $this->show_errors = false; return $show; } /** * Enables or disables suppressing of database errors. * * By default database errors are suppressed. * * @since 2.5.0 * * @see wpdb::hide_errors() * * @param bool $suppress Optional. Whether to suppress errors. Default true. * @return bool Whether suppressing of errors was previously active. */ public function suppress_errors( $suppress = true ) { $errors = $this->suppress_errors; $this->suppress_errors = (bool) $suppress; return $errors; } /** * Kills cached query results. * * @since 0.71 */ public function flush() { $this->last_result = array(); $this->col_info = null; $this->last_query = null; $this->rows_affected = 0; $this->num_rows = 0; $this->last_error = ''; if ( $this->result instanceof mysqli_result ) { mysqli_free_result( $this->result ); $this->result = null; // Confidence check before using the handle. if ( empty( $this->dbh ) || ! ( $this->dbh instanceof mysqli ) ) { return; } // Clear out any results from a multi-query. while ( mysqli_more_results( $this->dbh ) ) { mysqli_next_result( $this->dbh ); } } } /** * Connects to and selects database. * * If `$allow_bail` is false, the lack of database connection will need to be handled manually. * * @since 3.0.0 * @since 3.9.0 $allow_bail parameter added. * * @param bool $allow_bail Optional. Allows the function to bail. Default true. * @return bool True with a successful connection, false on failure. */ public function db_connect( $allow_bail = true ) { $this->is_mysql = true; $client_flags = defined( 'MYSQL_CLIENT_FLAGS' ) ? MYSQL_CLIENT_FLAGS : 0; /* * Switch error reporting off because WordPress handles its own. * This is due to the default value change from `MYSQLI_REPORT_OFF` * to `MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT` in PHP 8.1. */ mysqli_report( MYSQLI_REPORT_OFF ); $this->dbh = mysqli_init(); $host = $this->dbhost; $port = null; $socket = null; $is_ipv6 = false; $host_data = $this->parse_db_host( $this->dbhost ); if ( $host_data ) { list( $host, $port, $socket, $is_ipv6 ) = $host_data; } /* * If using the `mysqlnd` library, the IPv6 address needs to be enclosed * in square brackets, whereas it doesn't while using the `libmysqlclient` library. * @see https://bugs.php.net/bug.php?id=67563 */ if ( $is_ipv6 && extension_loaded( 'mysqlnd' ) ) { $host = "[$host]"; } if ( WP_DEBUG ) { mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags ); } else { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged @mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags ); } if ( $this->dbh->connect_errno ) { $this->dbh = null; } if ( ! $this->dbh && $allow_bail ) { wp_load_translations_early(); // Load custom DB error template, if present. if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) { require_once WP_CONTENT_DIR . '/db-error.php'; die(); } $message = '<h1>' . __( 'Error establishing a database connection' ) . "</h1>\n"; $message .= '<p>' . sprintf( /* translators: 1: wp-config.php, 2: Database host. */ __( 'This either means that the username and password information in your %1$s file is incorrect or that contact with the database server at %2$s could not be established. This could mean your host’s database server is down.' ), '<code>wp-config.php</code>', '<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>' ) . "</p>\n"; $message .= "<ul>\n"; $message .= '<li>' . __( 'Are you sure you have the correct username and password?' ) . "</li>\n"; $message .= '<li>' . __( 'Are you sure you have typed the correct hostname?' ) . "</li>\n"; $message .= '<li>' . __( 'Are you sure the database server is running?' ) . "</li>\n"; $message .= "</ul>\n"; $message .= '<p>' . sprintf( /* translators: %s: Support forums URL. */ __( 'If you are unsure what these terms mean you should probably contact your host. If you still need help you can always visit the <a href="%s">WordPress support forums</a>.' ), __( 'https://wordpress.org/support/forums/' ) ) . "</p>\n"; $this->bail( $message, 'db_connect_fail' ); return false; } elseif ( $this->dbh ) { if ( ! $this->has_connected ) { $this->init_charset(); } $this->has_connected = true; $this->set_charset( $this->dbh ); $this->ready = true; $this->set_sql_mode(); $this->select( $this->dbname, $this->dbh ); return true; } return false; } /** * Parses the DB_HOST setting to interpret it for mysqli_real_connect(). * * mysqli_real_connect() doesn't support the host param including a port or socket * like mysql_connect() does. This duplicates how mysql_connect() detects a port * and/or socket file. * * @since 4.9.0 * * @param string $host The DB_HOST setting to parse. * @return array|false { * Array containing the host, the port, the socket and * whether it is an IPv6 address, in that order. * False if the host couldn't be parsed. * * @type string $0 Host name. * @type string|null $1 Port. * @type string|null $2 Socket. * @type bool $3 Whether it is an IPv6 address. * } */ public function parse_db_host( $host ) { $socket = null; $is_ipv6 = false; // First peel off the socket parameter from the right, if it exists. $socket_pos = strpos( $host, ':/' ); if ( false !== $socket_pos ) { $socket = substr( $host, $socket_pos + 1 ); $host = substr( $host, 0, $socket_pos ); } /* * We need to check for an IPv6 address first. * An IPv6 address will always contain at least two colons. */ if ( substr_count( $host, ':' ) > 1 ) { $pattern = '#^(?:\[)?(?P<host>[0-9a-fA-F:]+)(?:\]:(?P<port>[\d]+))?#'; $is_ipv6 = true; } else { // We seem to be dealing with an IPv4 address. $pattern = '#^(?P<host>[^:/]*)(?::(?P<port>[\d]+))?#'; } $matches = array(); $result = preg_match( $pattern, $host, $matches ); if ( 1 !== $result ) { // Couldn't parse the address, bail. return false; } $host = ! empty( $matches['host'] ) ? $matches['host'] : ''; // Port cannot be a string; must be null or an integer. $port = ! empty( $matches['port'] ) ? absint( $matches['port'] ) : null; return array( $host, $port, $socket, $is_ipv6 ); } /** * Checks that the connection to the database is still up. If not, try to reconnect. * * If this function is unable to reconnect, it will forcibly die, or if called * after the {@see 'template_redirect'} hook has been fired, return false instead. * * If `$allow_bail` is false, the lack of database connection will need to be handled manually. * * @since 3.9.0 * * @param bool $allow_bail Optional. Allows the function to bail. Default true. * @return bool|void True if the connection is up. */ public function check_connection( $allow_bail = true ) { // Check if the connection is alive. if ( ! empty( $this->dbh ) && mysqli_query( $this->dbh, 'DO 1' ) !== false ) { return true; } $error_reporting = false; // Disable warnings, as we don't want to see a multitude of "unable to connect" messages. if ( WP_DEBUG ) { $error_reporting = error_reporting(); error_reporting( $error_reporting & ~E_WARNING ); } for ( $tries = 1; $tries <= $this->reconnect_retries; $tries++ ) { /* * On the last try, re-enable warnings. We want to see a single instance * of the "unable to connect" message on the bail() screen, if it appears. */ if ( $this->reconnect_retries === $tries && WP_DEBUG ) { error_reporting( $error_reporting ); } if ( $this->db_connect( false ) ) { if ( $error_reporting ) { error_reporting( $error_reporting ); } return true; } sleep( 1 ); } /* * If template_redirect has already happened, it's too late for wp_die()/dead_db(). * Let's just return and hope for the best. */ if ( did_action( 'template_redirect' ) ) { return false; } if ( ! $allow_bail ) { return false; } wp_load_translations_early(); $message = '<h1>' . __( 'Error reconnecting to the database' ) . "</h1>\n"; $message .= '<p>' . sprintf( /* translators: %s: Database host. */ __( 'This means that the contact with the database server at %s was lost. This could mean your host’s database server is down.' ), '<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>' ) . "</p>\n"; $message .= "<ul>\n"; $message .= '<li>' . __( 'Are you sure the database server is running?' ) . "</li>\n"; $message .= '<li>' . __( 'Are you sure the database server is not under particularly heavy load?' ) . "</li>\n"; $message .= "</ul>\n"; $message .= '<p>' . sprintf( /* translators: %s: Support forums URL. */ __( 'If you are unsure what these terms mean you should probably contact your host. If you still need help you can always visit the <a href="%s">WordPress support forums</a>.' ), __( 'https://wordpress.org/support/forums/' ) ) . "</p>\n"; // We weren't able to reconnect, so we better bail. $this->bail( $message, 'db_connect_fail' ); /* * Call dead_db() if bail didn't die, because this database is no more. * It has ceased to be (at least temporarily). */ dead_db(); } /** * Performs a database query, using current database connection. * * More information can be found on the documentation page. * * @since 0.71 * * @link https://developer.wordpress.org/reference/classes/wpdb/ * * @param string $query Database query. * @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows * affected/selected for all other queries. Boolean false on error. */ public function query( $query ) { if ( ! $this->ready ) { $this->check_current_query = true; return false; } /** * Filters the database query. * * Some queries are made before the plugins have been loaded, * and thus cannot be filtered with this method. * * @since 2.1.0 * * @param string $query Database query. */ $query = apply_filters( 'query', $query ); if ( ! $query ) { $this->insert_id = 0; return false; } $this->flush(); // Log how the function was called. $this->func_call = "\$db->query(\"$query\")"; // If we're writing to the database, make sure the query will write safely. if ( $this->check_current_query && ! $this->check_ascii( $query ) ) { $stripped_query = $this->strip_invalid_text_from_query( $query ); /* * strip_invalid_text_from_query() can perform queries, so we need * to flush again, just to make sure everything is clear. */ $this->flush(); if ( $stripped_query !== $query ) { $this->insert_id = 0; $this->last_query = $query; wp_load_translations_early(); $this->last_error = __( 'WordPress database error: Could not perform query because it contains invalid data.' ); return false; } } $this->check_current_query = true; // Keep track of the last query for debug. $this->last_query = $query; $this->_do_query( $query ); // Database server has gone away, try to reconnect. $mysql_errno = 0; if ( $this->dbh instanceof mysqli ) { $mysql_errno = mysqli_errno( $this->dbh ); } else { /* * $dbh is defined, but isn't a real connection. * Something has gone horribly wrong, let's try a reconnect. */ $mysql_errno = 2006; } if ( empty( $this->dbh ) || 2006 === $mysql_errno ) { if ( $this->check_connection() ) { $this->_do_query( $query ); } else { $this->insert_id = 0; return false; } } // If there is an error then take note of it. if ( $this->dbh instanceof mysqli ) { $this->last_error = mysqli_error( $this->dbh ); } else { $this->last_error = __( 'Unable to retrieve the error message from the database server' ); } if ( $this->last_error ) { // Clear insert_id on a subsequent failed insert. if ( $this->insert_id && preg_match( '/^\s*(insert|replace)\s/i', $query ) ) { $this->insert_id = 0; } $this->print_error(); return false; } if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) { $return_val = $this->result; } elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) { $this->rows_affected = mysqli_affected_rows( $this->dbh ); // Take note of the insert_id. if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) { $this->insert_id = mysqli_insert_id( $this->dbh ); } // Return number of rows affected. $return_val = $this->rows_affected; } else { $num_rows = 0; if ( $this->result instanceof mysqli_result ) { while ( $row = mysqli_fetch_object( $this->result ) ) { $this->last_result[ $num_rows ] = $row; ++$num_rows; } } // Log and return the number of rows selected. $this->num_rows = $num_rows; $return_val = $num_rows; } return $return_val; } /** * Internal function to perform the mysqli_query() call. * * @since 3.9.0 * * @see wpdb::query() * * @param string $query The query to run. */ private function _do_query( $query ) { if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) { $this->timer_start(); } if ( ! empty( $this->dbh ) ) { $this->result = mysqli_query( $this->dbh, $query ); } ++$this->num_queries; if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) { $this->log_query( $query, $this->timer_stop(), $this->get_caller(), $this->time_start, array() ); } } /** * Logs query data. * * @since 5.3.0 * * @param string $query The query's SQL. * @param float $query_time Total time spent on the query, in seconds. * @param string $query_callstack Comma-separated list of the calling functions. * @param float $query_start Unix timestamp of the time at the start of the query. * @param array $query_data Custom query data. */ public function log_query( $query, $query_time, $query_callstack, $query_start, $query_data ) { /** * Filters the custom data to log alongside a query. * * Caution should be used when modifying any of this data, it is recommended that any additional * information you need to store about a query be added as a new associative array element. * * @since 5.3.0 * * @param array $query_data Custom query data. * @param string $query The query's SQL. * @param float $query_time Total time spent on the query, in seconds. * @param string $query_callstack Comma-separated list of the calling functions. * @param float $query_start Unix timestamp of the time at the start of the query. */ $query_data = apply_filters( 'log_query_custom_data', $query_data, $query, $query_time, $query_callstack, $query_start ); $this->queries[] = array( $query, $query_time, $query_callstack, $query_start, $query_data, ); } /** * Generates and returns a placeholder escape string for use in queries returned by ::prepare(). * * @since 4.8.3 * * @return string String to escape placeholders. */ public function placeholder_escape() { static $placeholder; if ( ! $placeholder ) { // Old WP installs may not have AUTH_SALT defined. $salt = defined( 'AUTH_SALT' ) && AUTH_SALT ? AUTH_SALT : (string) rand(); $placeholder = '{' . hash_hmac( 'sha256', uniqid( $salt, true ), $salt ) . '}'; } /* * Add the filter to remove the placeholder escaper. Uses priority 0, so that anything * else attached to this filter will receive the query with the placeholder string removed. */ if ( false === has_filter( 'query', array( $this, 'remove_placeholder_escape' ) ) ) { add_filter( 'query', array( $this, 'remove_placeholder_escape' ), 0 ); } return $placeholder; } /** * Adds a placeholder escape string, to escape anything that resembles a printf() placeholder. * * @since 4.8.3 * * @param string $query The query to escape. * @return string The query with the placeholder escape string inserted where necessary. */ public function add_placeholder_escape( $query ) { /* * To prevent returning anything that even vaguely resembles a placeholder, * we clobber every % we can find. */ return str_replace( '%', $this->placeholder_escape(), $query ); } /** * Removes the placeholder escape strings from a query. * * @since 4.8.3 * * @param string $query The query from which the placeholder will be removed. * @return string The query with the placeholder removed. */ public function remove_placeholder_escape( $query ) { return str_replace( $this->placeholder_escape(), '%', $query ); } /** * Inserts a row into the table. * * Examples: * * $wpdb->insert( * 'table', * array( * 'column1' => 'foo', * 'column2' => 'bar', * ) * ); * $wpdb->insert( * 'table', * array( * 'column1' => 'foo', * 'column2' => 1337, * ), * array( * '%s', * '%d', * ) * ); * * @since 2.5.0 * * @see wpdb::prepare() * @see wpdb::$field_types * @see wp_set_wpdb_vars() * * @param string $table Table name. * @param array $data Data to insert (in column => value pairs). * Both `$data` columns and `$data` values should be "raw" (neither should be SQL escaped). * Sending a null value will cause the column to be set to NULL - the corresponding * format is ignored in this case. * @param string[]|string $format Optional. An array of formats to be mapped to each of the value in `$data`. * If string, that format will be used for all of the values in `$data`. * A format is one of '%d', '%f', '%s' (integer, float, string). * If omitted, all values in `$data` will be treated as strings unless otherwise * specified in wpdb::$field_types. Default null. * @return int|false The number of rows inserted, or false on error. */ public function insert( $table, $data, $format = null ) { return $this->_insert_replace_helper( $table, $data, $format, 'INSERT' ); } /** * Replaces a row in the table or inserts it if it does not exist, based on a PRIMARY KEY or a UNIQUE index. * * A REPLACE works exactly like an INSERT, except that if an old row in the table has the same value as a new row * for a PRIMARY KEY or a UNIQUE index, the old row is deleted before the new row is inserted. * * Examples: * * $wpdb->replace( * 'table', * array( * 'ID' => 123, * 'column1' => 'foo', * 'column2' => 'bar', * ) * ); * $wpdb->replace( * 'table', * array( * 'ID' => 456, * 'column1' => 'foo', * 'column2' => 1337, * ), * array( * '%d', * '%s', * '%d', * ) * ); * * @since 3.0.0 * * @see wpdb::prepare() * @see wpdb::$field_types * @see wp_set_wpdb_vars() * * @param string $table Table name. * @param array $data Data to insert (in column => value pairs). * Both `$data` columns and `$data` values should be "raw" (neither should be SQL escaped). * A primary key or unique index is required to perform a replace operation. * Sending a null value will cause the column to be set to NULL - the corresponding * format is ignored in this case. * @param string[]|string $format Optional. An array of formats to be mapped to each of the value in `$data`. * If string, that format will be used for all of the values in `$data`. * A format is one of '%d', '%f', '%s' (integer, float, string). * If omitted, all values in `$data` will be treated as strings unless otherwise * specified in wpdb::$field_types. Default null. * @return int|false The number of rows affected, or false on error. */ public function replace( $table, $data, $format = null ) { return $this->_insert_replace_helper( $table, $data, $format, 'REPLACE' ); } /** * Helper function for insert and replace. * * Runs an insert or replace query based on `$type` argument. * * @since 3.0.0 * * @see wpdb::prepare() * @see wpdb::$field_types * @see wp_set_wpdb_vars() * * @param string $table Table name. * @param array $data Data to insert (in column => value pairs). * Both `$data` columns and `$data` values should be "raw" (neither should be SQL escaped). * Sending a null value will cause the column to be set to NULL - the corresponding * format is ignored in this case. * @param string[]|string $format Optional. An array of formats to be mapped to each of the value in `$data`. * If string, that format will be used for all of the values in `$data`. * A format is one of '%d', '%f', '%s' (integer, float, string). * If omitted, all values in `$data` will be treated as strings unless otherwise * specified in wpdb::$field_types. Default null. * @param string $type Optional. Type of operation. Either 'INSERT' or 'REPLACE'. * Default 'INSERT'. * @return int|false The number of rows affected, or false on error. */ public function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) { $this->insert_id = 0; if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ), true ) ) { return false; } $data = $this->process_fields( $table, $data, $format ); if ( false === $data ) { return false; } $formats = array(); $values = array(); foreach ( $data as $value ) { if ( is_null( $value['value'] ) ) { $formats[] = 'NULL'; continue; } $formats[] = $value['format']; $values[] = $value['value']; } $fields = '`' . implode( '`, `', array_keys( $data ) ) . '`'; $formats = implode( ', ', $formats ); $sql = "$type INTO `$table` ($fields) VALUES ($formats)"; $this->check_current_query = false; return $this->query( $this->prepare( $sql, $values ) ); } /** * Updates a row in the table. * * Examples: * * $wpdb->update( * 'table', * array( * 'column1' => 'foo', * 'column2' => 'bar', * ), * array( * 'ID' => 1, * ) * ); * $wpdb->update( * 'table', * array( * 'column1' => 'foo', * 'column2' => 1337, * ), * array( * 'ID' => 1, * ), * array( * '%s', * '%d', * ), * array( * '%d', * ) * ); * * @since 2.5.0 * * @see wpdb::prepare() * @see wpdb::$field_types * @see wp_set_wpdb_vars() * * @param string $table Table name. * @param array $data Data to update (in column => value pairs). * Both $data columns and $data values should be "raw" (neither should be SQL escaped). * Sending a null value will cause the column to be set to NULL - the corresponding * format is ignored in this case. * @param array $where A named array of WHERE clauses (in column => value pairs). * Multiple clauses will be joined with ANDs. * Both $where columns and $where values should be "raw". * Sending a null value will create an IS NULL comparison - the corresponding * format will be ignored in this case. * @param string[]|string $format Optional. An array of formats to be mapped to each of the values in $data. * If string, that format will be used for all of the values in $data. * A format is one of '%d', '%f', '%s' (integer, float, string). * If omitted, all values in $data will be treated as strings unless otherwise * specified in wpdb::$field_types. Default null. * @param string[]|string $where_format Optional. An array of formats to be mapped to each of the values in $where. * If string, that format will be used for all of the items in $where. * A format is one of '%d', '%f', '%s' (integer, float, string). * If omitted, all values in $where will be treated as strings unless otherwise * specified in wpdb::$field_types. Default null. * @return int|false The number of rows updated, or false on error. */ public function update( $table, $data, $where, $format = null, $where_format = null ) { if ( ! is_array( $data ) || ! is_array( $where ) ) { return false; } $data = $this->process_fields( $table, $data, $format ); if ( false === $data ) { return false; } $where = $this->process_fields( $table, $where, $where_format ); if ( false === $where ) { return false; } $fields = array(); $conditions = array(); $values = array(); foreach ( $data as $field => $value ) { if ( is_null( $value['value'] ) ) { $fields[] = "`$field` = NULL"; continue; } $fields[] = "`$field` = " . $value['format']; $values[] = $value['value']; } foreach ( $where as $field => $value ) { if ( is_null( $value['value'] ) ) { $conditions[] = "`$field` IS NULL"; continue; } $conditions[] = "`$field` = " . $value['format']; $values[] = $value['value']; } $fields = implode( ', ', $fields ); $conditions = implode( ' AND ', $conditions ); $sql = "UPDATE `$table` SET $fields WHERE $conditions"; $this->check_current_query = false; return $this->query( $this->prepare( $sql, $values ) ); } /** * Deletes a row in the table. * * Examples: * * $wpdb->delete( * 'table', * array( * 'ID' => 1, * ) * ); * $wpdb->delete( * 'table', * array( * 'ID' => 1, * ), * array( * '%d', * ) * ); * * @since 3.4.0 * * @see wpdb::prepare() * @see wpdb::$field_types * @see wp_set_wpdb_vars() * * @param string $table Table name. * @param array $where A named array of WHERE clauses (in column => value pairs). * Multiple clauses will be joined with ANDs. * Both $where columns and $where values should be "raw". * Sending a null value will create an IS NULL comparison - the corresponding * format will be ignored in this case. * @param string[]|string $where_format Optional. An array of formats to be mapped to each of the values in $where. * If string, that format will be used for all of the items in $where. * A format is one of '%d', '%f', '%s' (integer, float, string). * If omitted, all values in $data will be treated as strings unless otherwise * specified in wpdb::$field_types. Default null. * @return int|false The number of rows deleted, or false on error. */ public function delete( $table, $where, $where_format = null ) { if ( ! is_array( $where ) ) { return false; } $where = $this->process_fields( $table, $where, $where_format ); if ( false === $where ) { return false; } $conditions = array(); $values = array(); foreach ( $where as $field => $value ) { if ( is_null( $value['value'] ) ) { $conditions[] = "`$field` IS NULL"; continue; } $conditions[] = "`$field` = " . $value['format']; $values[] = $value['value']; } $conditions = implode( ' AND ', $conditions ); $sql = "DELETE FROM `$table` WHERE $conditions"; $this->check_current_query = false; return $this->query( $this->prepare( $sql, $values ) ); } /** * Processes arrays of field/value pairs and field formats. * * This is a helper method for wpdb's CRUD methods, which take field/value pairs * for inserts, updates, and where clauses. This method first pairs each value * with a format. Then it determines the charset of that field, using that * to determine if any invalid text would be stripped. If text is stripped, * then field processing is rejected and the query fails. * * @since 4.2.0 * * @param string $table Table name. * @param array $data Array of values keyed by their field names. * @param string[]|string $format Formats or format to be mapped to the values in the data. * @return array|false An array of fields that contain paired value and formats. * False for invalid values. */ protected function process_fields( $table, $data, $format ) { $data = $this->process_field_formats( $data, $format ); if ( false === $data ) { return false; } $data = $this->process_field_charsets( $data, $table ); if ( false === $data ) { return false; } $data = $this->process_field_lengths( $data, $table ); if ( false === $data ) { return false; } $converted_data = $this->strip_invalid_text( $data ); if ( $data !== $converted_data ) { $problem_fields = array(); foreach ( $data as $field => $value ) { if ( $value !== $converted_data[ $field ] ) { $problem_fields[] = $field; } } wp_load_translations_early(); if ( 1 === count( $problem_fields ) ) { $this->last_error = sprintf( /* translators: %s: Database field where the error occurred. */ __( 'WordPress database error: Processing the value for the following field failed: %s. The supplied value may be too long or contains invalid data.' ), reset( $problem_fields ) ); } else { $this->last_error = sprintf( /* translators: %s: Database fields where the error occurred. */ __( 'WordPress database error: Processing the values for the following fields failed: %s. The supplied values may be too long or contain invalid data.' ), implode( ', ', $problem_fields ) ); } return false; } return $data; } /** * Prepares arrays of value/format pairs as passed to wpdb CRUD methods. * * @since 4.2.0 * * @param array $data Array of values keyed by their field names. * @param string[]|string $format Formats or format to be mapped to the values in the data. * @return array { * Array of values and formats keyed by their field names. * * @type array ...$0 { * Value and format for this field. * * @type mixed $value The value to be formatted. * @type string $format The format to be mapped to the value. * } * } */ protected function process_field_formats( $data, $format ) { $formats = (array) $format; $original_formats = $formats; foreach ( $data as $field => $value ) { $value = array( 'value' => $value, 'format' => '%s', ); if ( ! empty( $format ) ) { $value['format'] = array_shift( $formats ); if ( ! $value['format'] ) { $value['format'] = reset( $original_formats ); } } elseif ( isset( $this->field_types[ $field ] ) ) { $value['format'] = $this->field_types[ $field ]; } $data[ $field ] = $value; } return $data; } /** * Adds field charsets to field/value/format arrays generated by wpdb::process_field_formats(). * * @since 4.2.0 * * @param array $data { * Array of values and formats keyed by their field names, * as it comes from the wpdb::process_field_formats() method. * * @type array ...$0 { * Value and format for this field. * * @type mixed $value The value to be formatted. * @type string $format The format to be mapped to the value. * } * } * @param string $table Table name. * @return array|false { * The same array of data with additional 'charset' keys, or false if * the charset for the table cannot be found. * * @type array ...$0 { * Value, format, and charset for this field. * * @type mixed $value The value to be formatted. * @type string $format The format to be mapped to the value. * @type string|false $charset The charset to be used for the value. * } * } */ protected function process_field_charsets( $data, $table ) { foreach ( $data as $field => $value ) { if ( '%d' === $value['format'] || '%f' === $value['format'] ) { /* * We can skip this field if we know it isn't a string. * This checks %d/%f versus ! %s because its sprintf() could take more. */ $value['charset'] = false; } else { $value['charset'] = $this->get_col_charset( $table, $field ); if ( is_wp_error( $value['charset'] ) ) { return false; } } $data[ $field ] = $value; } return $data; } /** * For string fields, records the maximum string length that field can safely save. * * @since 4.2.1 * * @param array $data { * Array of values, formats, and charsets keyed by their field names, * as it comes from the wpdb::process_field_charsets() method. * * @type array ...$0 { * Value, format, and charset for this field. * * @type mixed $value The value to be formatted. * @type string $format The format to be mapped to the value. * @type string|false $charset The charset to be used for the value. * } * } * @param string $table Table name. * @return array|false { * The same array of data with additional 'length' keys, or false if * information for the table cannot be found. * * @type array ...$0 { * Value, format, charset, and length for this field. * * @type mixed $value The value to be formatted. * @type string $format The format to be mapped to the value. * @type string|false $charset The charset to be used for the value. * @type array|false $length { * Information about the maximum length of the value. * False if the column has no length. * * @type string $type One of 'byte' or 'char'. * @type int $length The column length. * } * } * } */ protected function process_field_lengths( $data, $table ) { foreach ( $data as $field => $value ) { if ( '%d' === $value['format'] || '%f' === $value['format'] ) { /* * We can skip this field if we know it isn't a string. * This checks %d/%f versus ! %s because its sprintf() could take more. */ $value['length'] = false; } else { $value['length'] = $this->get_col_length( $table, $field ); if ( is_wp_error( $value['length'] ) ) { return false; } } $data[ $field ] = $value; } return $data; } /** * Retrieves one value from the database. * * Executes a SQL query and returns the value from the SQL result. * If the SQL result contains more than one column and/or more than one row, * the value in the column and row specified is returned. If $query is null, * the value in the specified column and row from the previous SQL result is returned. * * @since 0.71 * * @param string|null $query Optional. SQL query. Defaults to null, use the result from the previous query. * @param int $x Optional. Column of value to return. Indexed from 0. Default 0. * @param int $y Optional. Row of value to return. Indexed from 0. Default 0. * @return string|null Database query result (as string), or null on failure. */ public function get_var( $query = null, $x = 0, $y = 0 ) { $this->func_call = "\$db->get_var(\"$query\", $x, $y)"; if ( $query ) { if ( $this->check_current_query && $this->check_safe_collation( $query ) ) { $this->check_current_query = false; } $this->query( $query ); } // Extract var out of cached results based on x,y vals. if ( ! empty( $this->last_result[ $y ] ) ) { $values = array_values( get_object_vars( $this->last_result[ $y ] ) ); } // If there is a value return it, else return null. return ( isset( $values[ $x ] ) && '' !== $values[ $x ] ) ? $values[ $x ] : null; } /** * Retrieves one row from the database. * * Executes a SQL query and returns the row from the SQL result. * * @since 0.71 * * @param string|null $query SQL query. * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which * correspond to an stdClass object, an associative array, or a numeric array, * respectively. Default OBJECT. * @param int $y Optional. Row to return. Indexed from 0. Default 0. * @return array|object|null|void Database query result in format specified by $output or null on failure. */ public function get_row( $query = null, $output = OBJECT, $y = 0 ) { $this->func_call = "\$db->get_row(\"$query\",$output,$y)"; if ( $query ) { if ( $this->check_current_query && $this->check_safe_collation( $query ) ) { $this->check_current_query = false; } $this->query( $query ); } else { return null; } if ( ! isset( $this->last_result[ $y ] ) ) { return null; } if ( OBJECT === $output ) { return $this->last_result[ $y ] ? $this->last_result[ $y ] : null; } elseif ( ARRAY_A === $output ) { return $this->last_result[ $y ] ? get_object_vars( $this->last_result[ $y ] ) : null; } elseif ( ARRAY_N === $output ) { return $this->last_result[ $y ] ? array_values( get_object_vars( $this->last_result[ $y ] ) ) : null; } elseif ( OBJECT === strtoupper( $output ) ) { // Back compat for OBJECT being previously case-insensitive. return $this->last_result[ $y ] ? $this->last_result[ $y ] : null; } else { $this->print_error( ' $db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N' ); } } /** * Retrieves one column from the database. * * Executes a SQL query and returns the column from the SQL result. * If the SQL result contains more than one column, the column specified is returned. * If $query is null, the specified column from the previous SQL result is returned. * * @since 0.71 * * @param string|null $query Optional. SQL query. Defaults to previous query. * @param int $x Optional. Column to return. Indexed from 0. Default 0. * @return array Database query result. Array indexed from 0 by SQL result row number. */ public function get_col( $query = null, $x = 0 ) { if ( $query ) { if ( $this->check_current_query && $this->check_safe_collation( $query ) ) { $this->check_current_query = false; } $this->query( $query ); } $new_array = array(); // Extract the column values. if ( $this->last_result ) { for ( $i = 0, $j = count( $this->last_result ); $i < $j; $i++ ) { $new_array[ $i ] = $this->get_var( null, $x, $i ); } } return $new_array; } /** * Retrieves an entire SQL result set from the database (i.e., many rows). * * Executes a SQL query and returns the entire SQL result. * * @since 0.71 * * @param string $query SQL query. * @param string $output Optional. Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants. * With one of the first three, return an array of rows indexed * from 0 by SQL result row number. Each row is an associative array * (column => value, ...), a numerically indexed array (0 => value, ...), * or an object ( ->column = value ), respectively. With OBJECT_K, * return an associative array of row objects keyed by the value * of each row's first column's value. Duplicate keys are discarded. * Default OBJECT. * @return array|object|null Database query results. */ public function get_results( $query = null, $output = OBJECT ) { $this->func_call = "\$db->get_results(\"$query\", $output)"; if ( $query ) { if ( $this->check_current_query && $this->check_safe_collation( $query ) ) { $this->check_current_query = false; } $this->query( $query ); } else { return null; } $new_array = array(); if ( OBJECT === $output ) { // Return an integer-keyed array of row objects. return $this->last_result; } elseif ( OBJECT_K === $output ) { /* * Return an array of row objects with keys from column 1. * (Duplicates are discarded.) */ if ( $this->last_result ) { foreach ( $this->last_result as $row ) { $var_by_ref = get_object_vars( $row ); $key = array_shift( $var_by_ref ); if ( ! isset( $new_array[ $key ] ) ) { $new_array[ $key ] = $row; } } } return $new_array; } elseif ( ARRAY_A === $output || ARRAY_N === $output ) { // Return an integer-keyed array of... if ( $this->last_result ) { if ( ARRAY_N === $output ) { foreach ( (array) $this->last_result as $row ) { // ...integer-keyed row arrays. $new_array[] = array_values( get_object_vars( $row ) ); } } else { foreach ( (array) $this->last_result as $row ) { // ...column name-keyed row arrays. $new_array[] = get_object_vars( $row ); } } } return $new_array; } elseif ( strtoupper( $output ) === OBJECT ) { // Back compat for OBJECT being previously case-insensitive. return $this->last_result; } return null; } /** * Retrieves the character set for the given table. * * @since 4.2.0 * * @param string $table Table name. * @return string|WP_Error Table character set, WP_Error object if it couldn't be found. */ protected function get_table_charset( $table ) { $tablekey = strtolower( $table ); /** * Filters the table charset value before the DB is checked. * * Returning a non-null value from the filter will effectively short-circuit * checking the DB for the charset, returning that value instead. * * @since 4.2.0 * * @param string|WP_Error|null $charset The character set to use, WP_Error object * if it couldn't be found. Default null. * @param string $table The name of the table being checked. */ $charset = apply_filters( 'pre_get_table_charset', null, $table ); if ( null !== $charset ) { return $charset; } if ( isset( $this->table_charset[ $tablekey ] ) ) { return $this->table_charset[ $tablekey ]; } $charsets = array(); $columns = array(); $table_parts = explode( '.', $table ); $table = '`' . implode( '`.`', $table_parts ) . '`'; $results = $this->get_results( "SHOW FULL COLUMNS FROM $table" ); if ( ! $results ) { return new WP_Error( 'wpdb_get_table_charset_failure', __( 'Could not retrieve table charset.' ) ); } foreach ( $results as $column ) { $columns[ strtolower( $column->Field ) ] = $column; } $this->col_meta[ $tablekey ] = $columns; foreach ( $columns as $column ) { if ( ! empty( $column->Collation ) ) { list( $charset ) = explode( '_', $column->Collation ); $charsets[ strtolower( $charset ) ] = true; } list( $type ) = explode( '(', $column->Type ); // A binary/blob means the whole query gets treated like this. if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ), true ) ) { $this->table_charset[ $tablekey ] = 'binary'; return 'binary'; } } // utf8mb3 is an alias for utf8. if ( isset( $charsets['utf8mb3'] ) ) { $charsets['utf8'] = true; unset( $charsets['utf8mb3'] ); } // Check if we have more than one charset in play. $count = count( $charsets ); if ( 1 === $count ) { $charset = key( $charsets ); } elseif ( 0 === $count ) { // No charsets, assume this table can store whatever. $charset = false; } else { // More than one charset. Remove latin1 if present and recalculate. unset( $charsets['latin1'] ); $count = count( $charsets ); if ( 1 === $count ) { // Only one charset (besides latin1). $charset = key( $charsets ); } elseif ( 2 === $count && isset( $charsets['utf8'], $charsets['utf8mb4'] ) ) { // Two charsets, but they're utf8 and utf8mb4, use utf8. $charset = 'utf8'; } else { // Two mixed character sets. ascii. $charset = 'ascii'; } } $this->table_charset[ $tablekey ] = $charset; return $charset; } /** * Retrieves the character set for the given column. * * @since 4.2.0 * * @param string $table Table name. * @param string $column Column name. * @return string|false|WP_Error Column character set as a string. False if the column has * no character set. WP_Error object if there was an error. */ public function get_col_charset( $table, $column ) { $tablekey = strtolower( $table ); $columnkey = strtolower( $column ); /** * Filters the column charset value before the DB is checked. * * Passing a non-null value to the filter will short-circuit * checking the DB for the charset, returning that value instead. * * @since 4.2.0 * * @param string|null|false|WP_Error $charset The character set to use. Default null. * @param string $table The name of the table being checked. * @param string $column The name of the column being checked. */ $charset = apply_filters( 'pre_get_col_charset', null, $table, $column ); if ( null !== $charset ) { return $charset; } // Skip this entirely if this isn't a MySQL database. if ( empty( $this->is_mysql ) ) { return false; } if ( empty( $this->table_charset[ $tablekey ] ) ) { // This primes column information for us. $table_charset = $this->get_table_charset( $table ); if ( is_wp_error( $table_charset ) ) { return $table_charset; } } // If still no column information, return the table charset. if ( empty( $this->col_meta[ $tablekey ] ) ) { return $this->table_charset[ $tablekey ]; } // If this column doesn't exist, return the table charset. if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) { return $this->table_charset[ $tablekey ]; } // Return false when it's not a string column. if ( empty( $this->col_meta[ $tablekey ][ $columnkey ]->Collation ) ) { return false; } list( $charset ) = explode( '_', $this->col_meta[ $tablekey ][ $columnkey ]->Collation ); return $charset; } /** * Retrieves the maximum string length allowed in a given column. * * The length may either be specified as a byte length or a character length. * * @since 4.2.1 * * @param string $table Table name. * @param string $column Column name. * @return array|false|WP_Error { * Array of column length information, false if the column has no length (for * example, numeric column), WP_Error object if there was an error. * * @type string $type One of 'byte' or 'char'. * @type int $length The column length. * } */ public function get_col_length( $table, $column ) { $tablekey = strtolower( $table ); $columnkey = strtolower( $column ); // Skip this entirely if this isn't a MySQL database. if ( empty( $this->is_mysql ) ) { return false; } if ( empty( $this->col_meta[ $tablekey ] ) ) { // This primes column information for us. $table_charset = $this->get_table_charset( $table ); if ( is_wp_error( $table_charset ) ) { return $table_charset; } } if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) { return false; } $typeinfo = explode( '(', $this->col_meta[ $tablekey ][ $columnkey ]->Type ); $type = strtolower( $typeinfo[0] ); if ( ! empty( $typeinfo[1] ) ) { $length = trim( $typeinfo[1], ')' ); } else { $length = false; } switch ( $type ) { case 'char': case 'varchar': return array( 'type' => 'char', 'length' => (int) $length, ); case 'binary': case 'varbinary': return array( 'type' => 'byte', 'length' => (int) $length, ); case 'tinyblob': case 'tinytext': return array( 'type' => 'byte', 'length' => 255, // 2^8 - 1 ); case 'blob': case 'text': return array( 'type' => 'byte', 'length' => 65535, // 2^16 - 1 ); case 'mediumblob': case 'mediumtext': return array( 'type' => 'byte', 'length' => 16777215, // 2^24 - 1 ); case 'longblob': case 'longtext': return array( 'type' => 'byte', 'length' => 4294967295, // 2^32 - 1 ); default: return false; } } /** * Checks if a string is ASCII. * * The negative regex is faster for non-ASCII strings, as it allows * the search to finish as soon as it encounters a non-ASCII character. * * @since 4.2.0 * * @param string $input_string String to check. * @return bool True if ASCII, false if not. */ protected function check_ascii( $input_string ) { if ( function_exists( 'mb_check_encoding' ) ) { if ( mb_check_encoding( $input_string, 'ASCII' ) ) { return true; } } elseif ( ! preg_match( '/[^\x00-\x7F]/', $input_string ) ) { return true; } return false; } /** * Checks if the query is accessing a collation considered safe. * * @since 4.2.0 * * @param string $query The query to check. * @return bool True if the collation is safe, false if it isn't. */ protected function check_safe_collation( $query ) { if ( $this->checking_collation ) { return true; } // We don't need to check the collation for queries that don't read data. $query = ltrim( $query, "\r\n\t (" ); if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $query ) ) { return true; } // All-ASCII queries don't need extra checking. if ( $this->check_ascii( $query ) ) { return true; } $table = $this->get_table_from_query( $query ); if ( ! $table ) { return false; } $this->checking_collation = true; $collation = $this->get_table_charset( $table ); $this->checking_collation = false; // Tables with no collation, or latin1 only, don't need extra checking. if ( false === $collation || 'latin1' === $collation ) { return true; } $table = strtolower( $table ); if ( empty( $this->col_meta[ $table ] ) ) { return false; } // If any of the columns don't have one of these collations, it needs more confidence checking. $safe_collations = array( 'utf8_bin', 'utf8_general_ci', 'utf8mb3_bin', 'utf8mb3_general_ci', 'utf8mb4_bin', 'utf8mb4_general_ci', ); foreach ( $this->col_meta[ $table ] as $col ) { if ( empty( $col->Collation ) ) { continue; } if ( ! in_array( $col->Collation, $safe_collations, true ) ) { return false; } } return true; } /** * Strips any invalid characters based on value/charset pairs. * * @since 4.2.0 * * @param array $data Array of value arrays. Each value array has the keys 'value', 'charset', and 'length'. * An optional 'ascii' key can be set to false to avoid redundant ASCII checks. * @return array|WP_Error The $data parameter, with invalid characters removed from each value. * This works as a passthrough: any additional keys such as 'field' are * retained in each value array. If we cannot remove invalid characters, * a WP_Error object is returned. */ protected function strip_invalid_text( $data ) { $db_check_string = false; foreach ( $data as &$value ) { $charset = $value['charset']; if ( is_array( $value['length'] ) ) { $length = $value['length']['length']; $truncate_by_byte_length = 'byte' === $value['length']['type']; } else { $length = false; /* * Since we have no length, we'll never truncate. Initialize the variable to false. * True would take us through an unnecessary (for this case) codepath below. */ $truncate_by_byte_length = false; } // There's no charset to work with. if ( false === $charset ) { continue; } // Column isn't a string. if ( ! is_string( $value['value'] ) ) { continue; } $needs_validation = true; if ( // latin1 can store any byte sequence. 'latin1' === $charset || // ASCII is always OK. ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) ) { $truncate_by_byte_length = true; $needs_validation = false; } if ( $truncate_by_byte_length ) { mbstring_binary_safe_encoding(); if ( false !== $length && strlen( $value['value'] ) > $length ) { $value['value'] = substr( $value['value'], 0, $length ); } reset_mbstring_encoding(); if ( ! $needs_validation ) { continue; } } // utf8 can be handled by regex, which is a bunch faster than a DB lookup. if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) { $regex = '/ ( (?: [\x00-\x7F] # single-byte sequences 0xxxxxxx | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 | [\xE1-\xEC][\x80-\xBF]{2} | \xED[\x80-\x9F][\x80-\xBF] | [\xEE-\xEF][\x80-\xBF]{2}'; if ( 'utf8mb4' === $charset ) { $regex .= ' | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 | [\xF1-\xF3][\x80-\xBF]{3} | \xF4[\x80-\x8F][\x80-\xBF]{2} '; } $regex .= '){1,40} # ...one or more times ) | . # anything else /x'; $value['value'] = preg_replace( $regex, '$1', $value['value'] ); if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) { $value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' ); } continue; } // We couldn't use any local conversions, send it to the DB. $value['db'] = true; $db_check_string = true; } unset( $value ); // Remove by reference. if ( $db_check_string ) { $queries = array(); foreach ( $data as $col => $value ) { if ( ! empty( $value['db'] ) ) { // We're going to need to truncate by characters or bytes, depending on the length value we have. if ( isset( $value['length']['type'] ) && 'byte' === $value['length']['type'] ) { // Using binary causes LEFT() to truncate by bytes. $charset = 'binary'; } else { $charset = $value['charset']; } if ( $this->charset ) { $connection_charset = $this->charset; } else { $connection_charset = mysqli_character_set_name( $this->dbh ); } if ( is_array( $value['length'] ) ) { $length = sprintf( '%.0f', $value['length']['length'] ); $queries[ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING $charset ), $length ) USING $connection_charset )", $value['value'] ); } elseif ( 'binary' !== $charset ) { // If we don't have a length, there's no need to convert binary - it will always return the same result. $queries[ $col ] = $this->prepare( "CONVERT( CONVERT( %s USING $charset ) USING $connection_charset )", $value['value'] ); } unset( $data[ $col ]['db'] ); } } $sql = array(); foreach ( $queries as $column => $query ) { if ( ! $query ) { continue; } $sql[] = $query . " AS x_$column"; } $this->check_current_query = false; $row = $this->get_row( 'SELECT ' . implode( ', ', $sql ), ARRAY_A ); if ( ! $row ) { return new WP_Error( 'wpdb_strip_invalid_text_failure', __( 'Could not strip invalid text.' ) ); } foreach ( array_keys( $data ) as $column ) { if ( isset( $row[ "x_$column" ] ) ) { $data[ $column ]['value'] = $row[ "x_$column" ]; } } } return $data; } /** * Strips any invalid characters from the query. * * @since 4.2.0 * * @param string $query Query to convert. * @return string|WP_Error The converted query, or a WP_Error object if the conversion fails. */ protected function strip_invalid_text_from_query( $query ) { // We don't need to check the collation for queries that don't read data. $trimmed_query = ltrim( $query, "\r\n\t (" ); if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $trimmed_query ) ) { return $query; } $table = $this->get_table_from_query( $query ); if ( $table ) { $charset = $this->get_table_charset( $table ); if ( is_wp_error( $charset ) ) { return $charset; } // We can't reliably strip text from tables containing binary/blob columns. if ( 'binary' === $charset ) { return $query; } } else { $charset = $this->charset; } $data = array( 'value' => $query, 'charset' => $charset, 'ascii' => false, 'length' => false, ); $data = $this->strip_invalid_text( array( $data ) ); if ( is_wp_error( $data ) ) { return $data; } return $data[0]['value']; } /** * Strips any invalid characters from the string for a given table and column. * * @since 4.2.0 * * @param string $table Table name. * @param string $column Column name. * @param string $value The text to check. * @return string|WP_Error The converted string, or a WP_Error object if the conversion fails. */ public function strip_invalid_text_for_column( $table, $column, $value ) { if ( ! is_string( $value ) ) { return $value; } $charset = $this->get_col_charset( $table, $column ); if ( ! $charset ) { // Not a string column. return $value; } elseif ( is_wp_error( $charset ) ) { // Bail on real errors. return $charset; } $data = array( $column => array( 'value' => $value, 'charset' => $charset, 'length' => $this->get_col_length( $table, $column ), ), ); $data = $this->strip_invalid_text( $data ); if ( is_wp_error( $data ) ) { return $data; } return $data[ $column ]['value']; } /** * Finds the first table name referenced in a query. * * @since 4.2.0 * * @param string $query The query to search. * @return string|false The table name found, or false if a table couldn't be found. */ protected function get_table_from_query( $query ) { // Remove characters that can legally trail the table name. $query = rtrim( $query, ';/-#' ); // Allow (select...) union [...] style queries. Use the first query's table name. $query = ltrim( $query, "\r\n\t (" ); // Strip everything between parentheses except nested selects. $query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', $query ); // Strip any leading SET STATEMENT statements. $query = preg_replace( '/^SET STATEMENT.+?\sFOR\s+/is', '', $query ); // Quickly match most common queries. if ( preg_match( '/^\s*(?:' . 'SELECT.*?\s+FROM' . '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?' . '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?' . '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?' . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:.+?FROM)?' . ')\s+((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)/is', $query, $maybe ) ) { return str_replace( '`', '', $maybe[1] ); } // SHOW TABLE STATUS and SHOW TABLES WHERE Name = 'wp_posts' if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES).+WHERE\s+Name\s*=\s*("|\')((?:[0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)\\1/is', $query, $maybe ) ) { return $maybe[2]; } /* * SHOW TABLE STATUS LIKE and SHOW TABLES LIKE 'wp\_123\_%' * This quoted LIKE operand seldom holds a full table name. * It is usually a pattern for matching a prefix so we just * strip the trailing % and unescape the _ to get 'wp_123_' * which drop-ins can use for routing these SQL statements. */ if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES)\s+(?:WHERE\s+Name\s+)?LIKE\s*("|\')((?:[\\\\0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)%?\\1/is', $query, $maybe ) ) { return str_replace( '\\_', '_', $maybe[2] ); } // Big pattern for the rest of the table-related queries. if ( preg_match( '/^\s*(?:' . '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM' . '|DESCRIBE|DESC|EXPLAIN|HANDLER' . '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?' . '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE' . '|TRUNCATE(?:\s+TABLE)?' . '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?' . '|ALTER(?:\s+IGNORE)?\s+TABLE' . '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?' . '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON' . '|DROP\s+INDEX.*\s+ON' . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE' . '|(?:GRANT|REVOKE).*ON\s+TABLE' . '|SHOW\s+(?:.*FROM|.*TABLE)' . ')\s+\(*\s*((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)\s*\)*/is', $query, $maybe ) ) { return str_replace( '`', '', $maybe[1] ); } return false; } /** * Loads the column metadata from the last query. * * @since 3.5.0 */ protected function load_col_info() { if ( $this->col_info ) { return; } $num_fields = mysqli_num_fields( $this->result ); for ( $i = 0; $i < $num_fields; $i++ ) { $this->col_info[ $i ] = mysqli_fetch_field( $this->result ); } } /** * Retrieves column metadata from the last query. * * @since 0.71 * * @param string $info_type Optional. Possible values include 'name', 'table', 'def', 'max_length', * 'not_null', 'primary_key', 'multiple_key', 'unique_key', 'numeric', * 'blob', 'type', 'unsigned', 'zerofill'. Default 'name'. * @param int $col_offset Optional. 0: col name. 1: which table the col's in. 2: col's max length. * 3: if the col is numeric. 4: col's type. Default -1. * @return mixed Column results. */ public function get_col_info( $info_type = 'name', $col_offset = -1 ) { $this->load_col_info(); if ( $this->col_info ) { if ( -1 === $col_offset ) { $i = 0; $new_array = array(); foreach ( (array) $this->col_info as $col ) { $new_array[ $i ] = $col->{$info_type}; ++$i; } return $new_array; } else { return $this->col_info[ $col_offset ]->{$info_type}; } } } /** * Starts the timer, for debugging purposes. * * @since 1.5.0 * * @return true */ public function timer_start() { $this->time_start = microtime( true ); return true; } /** * Stops the debugging timer. * * @since 1.5.0 * * @return float Total time spent on the query, in seconds. */ public function timer_stop() { return ( microtime( true ) - $this->time_start ); } /** * Wraps errors in a nice header and footer and dies. * * Will not die if wpdb::$show_errors is false. * * @since 1.5.0 * * @param string $message The error message. * @param string $error_code Optional. A computer-readable string to identify the error. * Default '500'. * @return void|false Void if the showing of errors is enabled, false if disabled. */ public function bail( $message, $error_code = '500' ) { if ( $this->show_errors ) { $error = ''; if ( $this->dbh instanceof mysqli ) { $error = mysqli_error( $this->dbh ); } elseif ( mysqli_connect_errno() ) { $error = mysqli_connect_error(); } if ( $error ) { $message = '<p><code>' . $error . "</code></p>\n" . $message; } wp_die( $message ); } else { if ( class_exists( 'WP_Error', false ) ) { $this->error = new WP_Error( $error_code, $message ); } else { $this->error = $message; } return false; } } /** * Closes the current database connection. * * @since 4.5.0 * * @return bool True if the connection was successfully closed, * false if it wasn't, or if the connection doesn't exist. */ public function close() { if ( ! $this->dbh ) { return false; } $closed = mysqli_close( $this->dbh ); if ( $closed ) { $this->dbh = null; $this->ready = false; $this->has_connected = false; } return $closed; } /** * Determines whether the database server is at least the required minimum version. * * @since 2.5.0 * * @global string $required_mysql_version The minimum required MySQL version string. * @return void|WP_Error */ public function check_database_version() { global $required_mysql_version; $wp_version = wp_get_wp_version(); // Make sure the server has the required MySQL version. if ( version_compare( $this->db_version(), $required_mysql_version, '<' ) ) { /* translators: 1: WordPress version number, 2: Minimum required MySQL version number. */ return new WP_Error( 'database_version', sprintf( __( '<strong>Error:</strong> WordPress %1$s requires MySQL %2$s or higher' ), $wp_version, $required_mysql_version ) ); } } /** * Determines whether the database supports collation. * * Called when WordPress is generating the table scheme. * * Use `wpdb::has_cap( 'collation' )`. * * @since 2.5.0 * @deprecated 3.5.0 Use wpdb::has_cap() * * @return bool True if collation is supported, false if not. */ public function supports_collation() { _deprecated_function( __FUNCTION__, '3.5.0', 'wpdb::has_cap( \'collation\' )' ); return $this->has_cap( 'collation' ); } /** * Retrieves the database character collate. * * @since 3.5.0 * * @return string The database character collate. */ public function get_charset_collate() { $charset_collate = ''; if ( ! empty( $this->charset ) ) { $charset_collate = "DEFAULT CHARACTER SET $this->charset"; } if ( ! empty( $this->collate ) ) { $charset_collate .= " COLLATE $this->collate"; } return $charset_collate; } /** * Determines whether the database or WPDB supports a particular feature. * * Capability sniffs for the database server and current version of WPDB. * * Database sniffs are based on the version of the database server in use. * * WPDB sniffs are added as new features are introduced to allow theme and plugin * developers to determine feature support. This is to account for drop-ins which may * introduce feature support at a different time to WordPress. * * @since 2.7.0 * @since 4.1.0 Added support for the 'utf8mb4' feature. * @since 4.6.0 Added support for the 'utf8mb4_520' feature. * @since 6.2.0 Added support for the 'identifier_placeholders' feature. * @since 6.6.0 The `utf8mb4` feature now always returns true. * * @see wpdb::db_version() * * @param string $db_cap The feature to check for. Accepts 'collation', 'group_concat', * 'subqueries', 'set_charset', 'utf8mb4', 'utf8mb4_520', * or 'identifier_placeholders'. * @return bool True when the database feature is supported, false otherwise. */ public function has_cap( $db_cap ) { $db_version = $this->db_version(); $db_server_info = $this->db_server_info(); /* * Account for MariaDB version being prefixed with '5.5.5-' on older PHP versions. * * Note: str_contains() is not used here, as this file can be included * directly outside of WordPress core, e.g. by HyperDB, in which case * the polyfills from wp-includes/compat.php are not loaded. */ if ( '5.5.5' === $db_version && false !== strpos( $db_server_info, 'MariaDB' ) && PHP_VERSION_ID < 80016 // PHP 8.0.15 or older. ) { // Strip the '5.5.5-' prefix and set the version to the correct value. $db_server_info = preg_replace( '/^5\.5\.5-(.*)/', '$1', $db_server_info ); $db_version = preg_replace( '/[^0-9.].*/', '', $db_server_info ); } switch ( strtolower( $db_cap ) ) { case 'collation': // @since 2.5.0 case 'group_concat': // @since 2.7.0 case 'subqueries': // @since 2.7.0 return version_compare( $db_version, '4.1', '>=' ); case 'set_charset': return version_compare( $db_version, '5.0.7', '>=' ); case 'utf8mb4': // @since 4.1.0 return true; case 'utf8mb4_520': // @since 4.6.0 return version_compare( $db_version, '5.6', '>=' ); case 'identifier_placeholders': // @since 6.2.0 /* * As of WordPress 6.2, wpdb::prepare() supports identifiers via '%i', * e.g. table/field names. */ return true; } return false; } /** * Retrieves a comma-separated list of the names of the functions that called wpdb. * * @since 2.5.0 * * @return string Comma-separated list of the calling functions. */ public function get_caller() { return wp_debug_backtrace_summary( __CLASS__ ); } /** * Retrieves the database server version number. * * @since 2.7.0 * * @return string|null Version number on success, null on failure. */ public function db_version() { return preg_replace( '/[^0-9.].*/', '', $this->db_server_info() ); } /** * Returns the raw version string of the database server. * * @since 5.5.0 * * @return string Database server version as a string. */ public function db_server_info() { return mysqli_get_server_info( $this->dbh ); } } https-migration-20260622101832.php 0000666 00000011205 15226762357 0012010 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-customize-new-menu-control.php 0000666 00000003256 15226762357 0014266 0 ustar 00 <?php /** * Customize API: WP_Customize_New_Menu_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 * @deprecated 4.9.0 This file is no longer used as of the menu creation UX introduced in #40104. */ _deprecated_file( basename( __FILE__ ), '4.9.0' ); /** * Customize control class for new menus. * * @since 4.3.0 * @deprecated 4.9.0 This class is no longer used as of the menu creation UX introduced in #40104. * * @see WP_Customize_Control */ class WP_Customize_New_Menu_Control extends WP_Customize_Control { /** * Control type. * * @since 4.3.0 * @var string */ public $type = 'new_menu'; /** * Constructor. * * @since 4.9.0 * @deprecated 4.9.0 * * @see WP_Customize_Control::__construct() * * @param WP_Customize_Manager $manager Customizer bootstrap instance. * @param string $id The control ID. * @param array $args Optional. Arguments to override class property defaults. * See WP_Customize_Control::__construct() for information * on accepted arguments. Default empty array. */ public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) { _deprecated_function( __METHOD__, '4.9.0' ); parent::__construct( $manager, $id, $args ); } /** * Render the control's content. * * @since 4.3.0 * @deprecated 4.9.0 */ public function render_content() { _deprecated_function( __METHOD__, '4.9.0' ); ?> <button type="button" class="button button-primary" id="create-new-menu-submit"><?php _e( 'Create Menu' ); ?></button> <span class="spinner"></span> <?php } } https-detection-20260606073112.php 0000666 00000013341 15226762357 0012001 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260606092011.php 0000666 00000011205 15226762357 0012010 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260607003229.php 0000666 00000011205 15226762357 0012014 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } wp-diff.php 0000666 00000001437 15226762357 0006641 0 ustar 00 <?php /** * WordPress Diff bastard child of old MediaWiki Diff Formatter. * * Basically all that remains is the table structure and some method names. * * @package WordPress * @subpackage Diff */ // Don't load directly. if ( ! defined( 'ABSPATH' ) ) { die( '-1' ); } if ( ! class_exists( 'Text_Diff', false ) ) { /** Text_Diff class */ require ABSPATH . WPINC . '/Text/Diff.php'; /** Text_Diff_Renderer class */ require ABSPATH . WPINC . '/Text/Diff/Renderer.php'; /** Text_Diff_Renderer_inline class */ require ABSPATH . WPINC . '/Text/Diff/Renderer/inline.php'; /** Text_Exception class */ require ABSPATH . WPINC . '/Text/Exception.php'; } require ABSPATH . WPINC . '/class-wp-text-diff-renderer-table.php'; require ABSPATH . WPINC . '/class-wp-text-diff-renderer-inline.php'; https-migration-20260605110157.php 0000666 00000011205 15226762357 0012011 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-customize-nav-menus-panel.php 0000666 00000006355 15226762357 0014066 0 ustar 00 <?php /** * Customize API: WP_Customize_Nav_Menus_Panel class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Nav Menus Panel Class * * Needed to add screen options. * * @since 4.3.0 * * @see WP_Customize_Panel */ class WP_Customize_Nav_Menus_Panel extends WP_Customize_Panel { /** * Control type. * * @since 4.3.0 * @var string */ public $type = 'nav_menus'; /** * Render screen options for Menus. * * @since 4.3.0 */ public function render_screen_options() { // Adds the screen options. require_once ABSPATH . 'wp-admin/includes/nav-menu.php'; add_filter( 'manage_nav-menus_columns', 'wp_nav_menu_manage_columns' ); // Display screen options. $screen = WP_Screen::get( 'nav-menus.php' ); $screen->render_screen_options( array( 'wrap' => false ) ); } /** * Returns the advanced options for the nav menus page. * * Link title attribute added as it's a relatively advanced concept for new users. * * @since 4.3.0 * @deprecated 4.5.0 Deprecated in favor of wp_nav_menu_manage_columns(). */ public function wp_nav_menu_manage_columns() { _deprecated_function( __METHOD__, '4.5.0', 'wp_nav_menu_manage_columns' ); require_once ABSPATH . 'wp-admin/includes/nav-menu.php'; return wp_nav_menu_manage_columns(); } /** * An Underscore (JS) template for this panel's content (but not its container). * * Class variables for this panel class are available in the `data` JS object; * export custom variables by overriding WP_Customize_Panel::json(). * * @since 4.3.0 * * @see WP_Customize_Panel::print_template() */ protected function content_template() { ?> <li class="panel-meta customize-info accordion-section <# if ( ! data.description ) { #> cannot-expand<# } #>"> <button type="button" class="customize-panel-back" tabindex="-1"> <span class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ _e( 'Back' ); ?> </span> </button> <div class="accordion-section-title"> <span class="preview-notice"> <?php /* translators: %s: The site/panel title in the Customizer. */ printf( __( 'You are customizing %s' ), '<strong class="panel-title">{{ data.title }}</strong>' ); ?> </span> <button type="button" class="customize-help-toggle dashicons dashicons-editor-help" aria-expanded="false"> <span class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ _e( 'Help' ); ?> </span> </button> <button type="button" class="customize-screen-options-toggle" aria-expanded="false"> <span class="screen-reader-text"> <?php /* translators: Hidden accessibility text. */ _e( 'Menu Options' ); ?> </span> </button> </div> <# if ( data.description ) { #> <div class="description customize-panel-description">{{{ data.description }}}</div> <# } #> <div id="screen-options-wrap"> <?php $this->render_screen_options(); ?> </div> </li> <?php // NOTE: The following is a workaround for an inability to treat (and thus label) a list of sections as a whole. ?> <li class="customize-control-title customize-section-title-nav_menus-heading"><?php _e( 'Menus' ); ?></li> <?php } } class-wp-customize-nav-menu-section.php 0000666 00000001314 15226762357 0014236 0 ustar 00 <?php /** * Customize API: WP_Customize_Nav_Menu_Section class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Menu Section Class * * Custom section only needed in JS. * * @since 4.3.0 * * @see WP_Customize_Section */ class WP_Customize_Nav_Menu_Section extends WP_Customize_Section { /** * Control type. * * @since 4.3.0 * @var string */ public $type = 'nav_menu'; /** * Get section parameters for JS. * * @since 4.3.0 * @return array Exported parameters. */ public function json() { $exported = parent::json(); $exported['menu_id'] = (int) preg_replace( '/^nav_menu\[(-?\d+)\]/', '$1', $this->id ); return $exported; } } https-detection-20260606073112-20260621211309.php 0000666 00000013341 15226762357 0013361 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260605103029.php 0000666 00000011205 15226762357 0012011 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } class-wp-customize-theme-control.php 0000666 00000027161 15226762357 0013636 0 ustar 00 <?php /** * Customize API: WP_Customize_Theme_Control class * * @package WordPress * @subpackage Customize * @since 4.4.0 */ /** * Customize Theme Control class. * * @since 4.2.0 * * @see WP_Customize_Control */ class WP_Customize_Theme_Control extends WP_Customize_Control { /** * Customize control type. * * @since 4.2.0 * @var string */ public $type = 'theme'; /** * Theme object. * * @since 4.2.0 * @var WP_Theme */ public $theme; /** * Refresh the parameters passed to the JavaScript via JSON. * * @since 4.2.0 * * @see WP_Customize_Control::to_json() */ public function to_json() { parent::to_json(); $this->json['theme'] = $this->theme; } /** * Don't render the control content from PHP, as it's rendered via JS on load. * * @since 4.2.0 */ public function render_content() {} /** * Render a JS template for theme display. * * @since 4.2.0 */ public function content_template() { /* translators: %s: Theme name. */ $details_label = sprintf( __( 'Details for theme: %s' ), '{{ data.theme.name }}' ); /* translators: %s: Theme name. */ $customize_label = sprintf( __( 'Customize theme: %s' ), '{{ data.theme.name }}' ); /* translators: %s: Theme name. */ $preview_label = sprintf( __( 'Live preview theme: %s' ), '{{ data.theme.name }}' ); /* translators: %s: Theme name. */ $install_label = sprintf( __( 'Install and preview theme: %s' ), '{{ data.theme.name }}' ); ?> <# if ( data.theme.active ) { #> <div class="theme active" tabindex="0" aria-describedby="{{ data.section }}-{{ data.theme.id }}-action"> <# } else { #> <div class="theme" tabindex="0" aria-describedby="{{ data.section }}-{{ data.theme.id }}-action"> <# } #> <# if ( data.theme.screenshot && data.theme.screenshot[0] ) { #> <div class="theme-screenshot"> <img data-src="{{ data.theme.screenshot[0] }}?ver={{ data.theme.version }}" alt="" /> </div> <# } else { #> <div class="theme-screenshot blank"></div> <# } #> <span class="more-details theme-details" id="{{ data.section }}-{{ data.theme.id }}-action" aria-label="<?php echo esc_attr( $details_label ); ?>"><?php _e( 'Theme Details' ); ?></span> <div class="theme-author"> <?php /* translators: Theme author name. */ printf( _x( 'By %s', 'theme author' ), '{{ data.theme.author }}' ); ?> </div> <# if ( 'installed' === data.theme.type && data.theme.hasUpdate ) { #> <# if ( data.theme.updateResponse.compatibleWP && data.theme.updateResponse.compatiblePHP ) { #> <div class="update-message notice inline notice-warning notice-alt" data-slug="{{ data.theme.id }}"> <p> <?php if ( is_multisite() ) { _e( 'New version available.' ); } else { printf( /* translators: %s: "Update now" button. */ __( 'New version available. %s' ), '<button class="button-link update-theme" type="button">' . __( 'Update now' ) . '</button>' ); } ?> </p> </div> <# } else { #> <div class="update-message notice inline notice-error notice-alt" data-slug="{{ data.theme.id }}"> <p> <# if ( ! data.theme.updateResponse.compatibleWP && ! data.theme.updateResponse.compatiblePHP ) { #> <?php printf( /* translators: %s: Theme name. */ __( 'There is a new version of %s available, but it does not work with your versions of WordPress and PHP.' ), '{{{ data.theme.name }}}' ); if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { printf( /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ ' ' . __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ), self_admin_url( 'update-core.php' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } elseif ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . __( '<a href="%s">Please update WordPress</a>.' ), self_admin_url( 'update-core.php' ) ); } elseif ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } else if ( ! data.theme.updateResponse.compatibleWP ) { #> <?php printf( /* translators: %s: Theme name. */ __( 'There is a new version of %s available, but it does not work with your version of WordPress.' ), '{{{ data.theme.name }}}' ); if ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . __( '<a href="%s">Please update WordPress</a>.' ), self_admin_url( 'update-core.php' ) ); } ?> <# } else if ( ! data.theme.updateResponse.compatiblePHP ) { #> <?php printf( /* translators: %s: Theme name. */ __( 'There is a new version of %s available, but it does not work with your version of PHP.' ), '{{{ data.theme.name }}}' ); if ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } #> </p> </div> <# } #> <# } #> <# if ( ! data.theme.compatibleWP || ! data.theme.compatiblePHP ) { #> <div class="notice notice-error notice-alt"><p> <# if ( ! data.theme.compatibleWP && ! data.theme.compatiblePHP ) { #> <?php _e( 'This theme does not work with your versions of WordPress and PHP.' ); if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { printf( /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ ' ' . __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ), self_admin_url( 'update-core.php' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } elseif ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . __( '<a href="%s">Please update WordPress</a>.' ), self_admin_url( 'update-core.php' ) ); } elseif ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } else if ( ! data.theme.compatibleWP ) { #> <?php _e( 'This theme does not work with your version of WordPress.' ); if ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . __( '<a href="%s">Please update WordPress</a>.' ), self_admin_url( 'update-core.php' ) ); } ?> <# } else if ( ! data.theme.compatiblePHP ) { #> <?php _e( 'This theme does not work with your version of PHP.' ); if ( current_user_can( 'update_php' ) ) { printf( /* translators: %s: URL to Update PHP page. */ ' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ), esc_url( wp_get_update_php_url() ) ); wp_update_php_annotation( '</p><p><em>', '</em>' ); } ?> <# } #> </p></div> <# } #> <# if ( data.theme.active ) { #> <div class="theme-id-container"> <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name"> <span><?php _ex( 'Previewing:', 'theme' ); ?></span> {{ data.theme.name }} </h3> <div class="theme-actions"> <button type="button" class="button button-primary customize-theme" aria-label="<?php echo esc_attr( $customize_label ); ?>"><?php _e( 'Customize' ); ?></button> </div> </div> <?php wp_admin_notice( _x( 'Installed', 'theme' ), array( 'type' => 'success', 'additional_classes' => array( 'notice-alt' ), ) ); ?> <# } else if ( 'installed' === data.theme.type ) { #> <# if ( data.theme.blockTheme ) { #> <div class="theme-id-container"> <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> <div class="theme-actions"> <# if ( data.theme.actions.activate ) { #> <?php /* translators: %s: Theme name. */ $aria_label = sprintf( _x( 'Activate %s', 'theme' ), '{{ data.name }}' ); ?> <a href="{{{ data.theme.actions.activate }}}" class="button button-primary activate" aria-label="<?php echo esc_attr( $aria_label ); ?>"><?php _e( 'Activate' ); ?></a> <# } #> </div> </div> <?php $customizer_not_supported_message = __( 'This theme doesn\'t support Customizer.' ); ?> <# if ( data.theme.actions.activate ) { #> <?php $customizer_not_supported_message .= ' ' . sprintf( /* translators: %s: URL to the themes page (also it activates the theme). */ __( 'However, you can still <a href="%s">activate this theme</a>, and use the Site Editor to customize it.' ), '{{{ data.theme.actions.activate }}}' ); ?> <# } #> <?php wp_admin_notice( $customizer_not_supported_message, array( 'type' => 'error', 'additional_classes' => array( 'notice-alt' ), ) ); ?> <# } else { #> <div class="theme-id-container"> <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> <div class="theme-actions"> <# if ( data.theme.compatibleWP && data.theme.compatiblePHP ) { #> <button type="button" class="button button-primary preview-theme" aria-label="<?php echo esc_attr( $preview_label ); ?>" data-slug="{{ data.theme.id }}"><?php _e( 'Live Preview' ); ?></button> <# } else { #> <button type="button" class="button button-primary disabled" aria-label="<?php echo esc_attr( $preview_label ); ?>"><?php _e( 'Live Preview' ); ?></button> <# } #> </div> </div> <?php wp_admin_notice( _x( 'Installed', 'theme' ), array( 'type' => 'success', 'additional_classes' => array( 'notice-alt' ), ) ); ?> <# } #> <# } else { #> <div class="theme-id-container"> <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> <div class="theme-actions"> <# if ( data.theme.compatibleWP && data.theme.compatiblePHP ) { #> <button type="button" class="button button-primary theme-install preview" aria-label="<?php echo esc_attr( $install_label ); ?>" data-slug="{{ data.theme.id }}" data-name="{{ data.theme.name }}"><?php _e( 'Install & Preview' ); ?></button> <# } else { #> <button type="button" class="button button-primary disabled" aria-label="<?php echo esc_attr( $install_label ); ?>" disabled><?php _e( 'Install & Preview' ); ?></button> <# } #> </div> </div> <# } #> </div> <?php } } class-wp-navigation-fallback-20260621210825.php 0000666 00000021751 15226762357 0014313 0 ustar 00 <?php /** * WP_Navigation_Fallback class * * Manages fallback behavior for Navigation menus. * * @package WordPress * @subpackage Navigation * @since 6.3.0 */ /** * Manages fallback behavior for Navigation menus. * * @since 6.3.0 */ class WP_Navigation_Fallback { /** * Updates the wp_navigation custom post type schema, in order to expose * additional fields in the embeddable links of WP_REST_Navigation_Fallback_Controller. * * The Navigation Fallback endpoint may embed the full Navigation Menu object * into the response as the `self` link. By default, the Posts Controller * will only expose a limited subset of fields but the editor requires * additional fields to be available in order to utilize the menu. * * Used with the `rest_wp_navigation_item_schema` hook. * * @since 6.4.0 * * @param array $schema The schema for the `wp_navigation` post. * @return array The modified schema. */ public static function update_wp_navigation_post_schema( $schema ) { // Expose top level fields. $schema['properties']['status']['context'] = array_merge( $schema['properties']['status']['context'], array( 'embed' ) ); $schema['properties']['content']['context'] = array_merge( $schema['properties']['content']['context'], array( 'embed' ) ); /* * Exposes sub properties of content field. * These sub properties aren't exposed by the posts controller by default, * for requests where context is `embed`. * * @see WP_REST_Posts_Controller::get_item_schema() */ $schema['properties']['content']['properties']['raw']['context'] = array_merge( $schema['properties']['content']['properties']['raw']['context'], array( 'embed' ) ); $schema['properties']['content']['properties']['rendered']['context'] = array_merge( $schema['properties']['content']['properties']['rendered']['context'], array( 'embed' ) ); $schema['properties']['content']['properties']['block_version']['context'] = array_merge( $schema['properties']['content']['properties']['block_version']['context'], array( 'embed' ) ); /* * Exposes sub properties of title field. * These sub properties aren't exposed by the posts controller by default, * for requests where context is `embed`. * * @see WP_REST_Posts_Controller::get_item_schema() */ $schema['properties']['title']['properties']['raw']['context'] = array_merge( $schema['properties']['title']['properties']['raw']['context'], array( 'embed' ) ); return $schema; } /** * Gets (and/or creates) an appropriate fallback Navigation Menu. * * @since 6.3.0 * * @return WP_Post|null the fallback Navigation Post or null. */ public static function get_fallback() { /** * Filters whether or not a fallback should be created. * * @since 6.3.0 * * @param bool $create Whether to create a fallback navigation menu. Default true. */ $should_create_fallback = apply_filters( 'wp_navigation_should_create_fallback', true ); $fallback = static::get_most_recently_published_navigation(); if ( $fallback || ! $should_create_fallback ) { return $fallback; } $fallback = static::create_classic_menu_fallback(); if ( $fallback && ! is_wp_error( $fallback ) ) { // Return the newly created fallback post object which will now be the most recently created navigation menu. return $fallback instanceof WP_Post ? $fallback : static::get_most_recently_published_navigation(); } $fallback = static::create_default_fallback(); if ( $fallback && ! is_wp_error( $fallback ) ) { // Return the newly created fallback post object which will now be the most recently created navigation menu. return $fallback instanceof WP_Post ? $fallback : static::get_most_recently_published_navigation(); } return null; } /** * Finds the most recently published `wp_navigation` post type. * * @since 6.3.0 * * @return WP_Post|null the first non-empty Navigation or null. */ private static function get_most_recently_published_navigation() { $parsed_args = array( 'post_type' => 'wp_navigation', 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'order' => 'DESC', 'orderby' => 'date', 'post_status' => 'publish', 'posts_per_page' => 1, ); $navigation_post = new WP_Query( $parsed_args ); if ( count( $navigation_post->posts ) > 0 ) { return $navigation_post->posts[0]; } return null; } /** * Creates a Navigation Menu post from a Classic Menu. * * @since 6.3.0 * * @return int|WP_Error The post ID of the default fallback menu or a WP_Error object. */ private static function create_classic_menu_fallback() { // See if we have a classic menu. $classic_nav_menu = static::get_fallback_classic_menu(); if ( ! $classic_nav_menu ) { return new WP_Error( 'no_classic_menus', __( 'No Classic Menus found.' ) ); } // If there is a classic menu then convert it to blocks. $classic_nav_menu_blocks = WP_Classic_To_Block_Menu_Converter::convert( $classic_nav_menu ); if ( is_wp_error( $classic_nav_menu_blocks ) ) { return $classic_nav_menu_blocks; } if ( empty( $classic_nav_menu_blocks ) ) { return new WP_Error( 'cannot_convert_classic_menu', __( 'Unable to convert Classic Menu to blocks.' ) ); } // Create a new navigation menu from the classic menu. $classic_menu_fallback = wp_insert_post( array( 'post_content' => $classic_nav_menu_blocks, 'post_title' => $classic_nav_menu->name, 'post_name' => $classic_nav_menu->slug, 'post_status' => 'publish', 'post_type' => 'wp_navigation', ), true // So that we can check whether the result is an error. ); return $classic_menu_fallback; } /** * Determines the most appropriate classic navigation menu to use as a fallback. * * @since 6.3.0 * * @return WP_Term|null The most appropriate classic navigation menu to use as a fallback. */ private static function get_fallback_classic_menu() { $classic_nav_menus = wp_get_nav_menus(); if ( ! $classic_nav_menus || is_wp_error( $classic_nav_menus ) ) { return null; } $nav_menu = static::get_nav_menu_at_primary_location(); if ( $nav_menu ) { return $nav_menu; } $nav_menu = static::get_nav_menu_with_primary_slug( $classic_nav_menus ); if ( $nav_menu ) { return $nav_menu; } return static::get_most_recently_created_nav_menu( $classic_nav_menus ); } /** * Sorts the classic menus and returns the most recently created one. * * @since 6.3.0 * * @param WP_Term[] $classic_nav_menus Array of classic nav menu term objects. * @return WP_Term The most recently created classic nav menu. */ private static function get_most_recently_created_nav_menu( $classic_nav_menus ) { usort( $classic_nav_menus, static function ( $a, $b ) { return $b->term_id - $a->term_id; } ); return $classic_nav_menus[0]; } /** * Returns the classic menu with the slug `primary` if it exists. * * @since 6.3.0 * * @param WP_Term[] $classic_nav_menus Array of classic nav menu term objects. * @return WP_Term|null The classic nav menu with the slug `primary` or null. */ private static function get_nav_menu_with_primary_slug( $classic_nav_menus ) { foreach ( $classic_nav_menus as $classic_nav_menu ) { if ( 'primary' === $classic_nav_menu->slug ) { return $classic_nav_menu; } } return null; } /** * Gets the classic menu assigned to the `primary` navigation menu location * if it exists. * * @since 6.3.0 * * @return WP_Term|null The classic nav menu assigned to the `primary` location or null. */ private static function get_nav_menu_at_primary_location() { $locations = get_nav_menu_locations(); if ( isset( $locations['primary'] ) ) { $primary_menu = wp_get_nav_menu_object( $locations['primary'] ); if ( $primary_menu ) { return $primary_menu; } } return null; } /** * Creates a default Navigation Block Menu fallback. * * @since 6.3.0 * * @return int|WP_Error The post ID of the default fallback menu or a WP_Error object. */ private static function create_default_fallback() { $default_blocks = static::get_default_fallback_blocks(); // Create a new navigation menu from the fallback blocks. $default_fallback = wp_insert_post( array( 'post_content' => $default_blocks, 'post_title' => _x( 'Navigation', 'Title of a Navigation menu' ), 'post_name' => 'navigation', 'post_status' => 'publish', 'post_type' => 'wp_navigation', ), true // So that we can check whether the result is an error. ); return $default_fallback; } /** * Gets the rendered markup for the default fallback blocks. * * @since 6.3.0 * * @return string default blocks markup to use a the fallback. */ private static function get_default_fallback_blocks() { $registry = WP_Block_Type_Registry::get_instance(); // If `core/page-list` is not registered then use empty blocks. return $registry->is_registered( 'core/page-list' ) ? '<!-- wp:page-list /-->' : ''; } } https-migration-20260605232827.php 0000666 00000011205 15226762357 0012022 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } widgets.php 0000666 00000212731 15226762357 0006754 0 ustar 00 <?php /** * Core Widgets API * * This API is used for creating dynamic sidebar without hardcoding functionality into * themes. * * Includes both internal WordPress routines and theme-use routines. * * This functionality was found in a plugin before the WordPress 2.2 release, which * included it in the core from that point on. * * @link https://wordpress.org/documentation/article/manage-wordpress-widgets/ * @link https://developer.wordpress.org/themes/functionality/widgets/ * * @package WordPress * @subpackage Widgets * @since 2.2.0 */ // // Global Variables. // /** @ignore */ global $wp_registered_sidebars, $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates; /** * Stores the sidebars, since many themes can have more than one. * * @since 2.2.0 * * @global array $wp_registered_sidebars The registered sidebars. */ $wp_registered_sidebars = array(); /** * Stores the registered widgets. * * @since 2.2.0 * * @global array $wp_registered_widgets The registered widgets. */ $wp_registered_widgets = array(); /** * Stores the registered widget controls (options). * * @since 2.2.0 * * @global array $wp_registered_widget_controls The registered widget controls. */ $wp_registered_widget_controls = array(); /** * Stores the registered widget updates. * * @since 2.8.0 * * @global array $wp_registered_widget_updates The registered widget updates. */ $wp_registered_widget_updates = array(); /** * Private * * @global array $_wp_sidebars_widgets */ $_wp_sidebars_widgets = array(); /** * Private * * @global array $_wp_deprecated_widgets_callbacks */ $GLOBALS['_wp_deprecated_widgets_callbacks'] = array( 'wp_widget_pages', 'wp_widget_pages_control', 'wp_widget_calendar', 'wp_widget_calendar_control', 'wp_widget_archives', 'wp_widget_archives_control', 'wp_widget_links', 'wp_widget_meta', 'wp_widget_meta_control', 'wp_widget_search', 'wp_widget_recent_entries', 'wp_widget_recent_entries_control', 'wp_widget_tag_cloud', 'wp_widget_tag_cloud_control', 'wp_widget_categories', 'wp_widget_categories_control', 'wp_widget_text', 'wp_widget_text_control', 'wp_widget_rss', 'wp_widget_rss_control', 'wp_widget_recent_comments', 'wp_widget_recent_comments_control', ); // // Template tags & API functions. // /** * Registers a widget. * * Registers a WP_Widget widget * * @since 2.8.0 * @since 4.6.0 Updated the `$widget` parameter to also accept a WP_Widget instance object * instead of simply a `WP_Widget` subclass name. * * @see WP_Widget * * @global WP_Widget_Factory $wp_widget_factory * * @param string|WP_Widget $widget Either the name of a `WP_Widget` subclass or an instance of a `WP_Widget` subclass. */ function register_widget( $widget ) { global $wp_widget_factory; $wp_widget_factory->register( $widget ); } /** * Unregisters a widget. * * Unregisters a WP_Widget widget. Useful for un-registering default widgets. * Run within a function hooked to the {@see 'widgets_init'} action. * * @since 2.8.0 * @since 4.6.0 Updated the `$widget` parameter to also accept a WP_Widget instance object * instead of simply a `WP_Widget` subclass name. * * @see WP_Widget * * @global WP_Widget_Factory $wp_widget_factory * * @param string|WP_Widget $widget Either the name of a `WP_Widget` subclass or an instance of a `WP_Widget` subclass. */ function unregister_widget( $widget ) { global $wp_widget_factory; $wp_widget_factory->unregister( $widget ); } /** * Creates multiple sidebars. * * If you wanted to quickly create multiple sidebars for a theme or internally. * This function will allow you to do so. If you don't pass the 'name' and/or * 'id' in `$args`, then they will be built for you. * * @since 2.2.0 * * @see register_sidebar() The second parameter is documented by register_sidebar() and is the same here. * * @global array $wp_registered_sidebars The new sidebars are stored in this array by sidebar ID. * * @param int $number Optional. Number of sidebars to create. Default 1. * @param array|string $args { * Optional. Array or string of arguments for building a sidebar. * * @type string $id The base string of the unique identifier for each sidebar. If provided, and multiple * sidebars are being defined, the ID will have "-2" appended, and so on. * Default 'sidebar-' followed by the number the sidebar creation is currently at. * @type string $name The name or title for the sidebars displayed in the admin dashboard. If registering * more than one sidebar, include '%d' in the string as a placeholder for the uniquely * assigned number for each sidebar. * Default 'Sidebar' for the first sidebar, otherwise 'Sidebar %d'. * } */ function register_sidebars( $number = 1, $args = array() ) { global $wp_registered_sidebars; $number = (int) $number; if ( is_string( $args ) ) { parse_str( $args, $args ); } for ( $i = 1; $i <= $number; $i++ ) { $_args = $args; if ( $number > 1 ) { if ( isset( $args['name'] ) ) { $_args['name'] = sprintf( $args['name'], $i ); } else { /* translators: %d: Sidebar number. */ $_args['name'] = sprintf( __( 'Sidebar %d' ), $i ); } } else { $_args['name'] = isset( $args['name'] ) ? $args['name'] : __( 'Sidebar' ); } /* * Custom specified ID's are suffixed if they exist already. * Automatically generated sidebar names need to be suffixed regardless starting at -0. */ if ( isset( $args['id'] ) ) { $_args['id'] = $args['id']; $n = 2; // Start at -2 for conflicting custom IDs. while ( is_registered_sidebar( $_args['id'] ) ) { $_args['id'] = $args['id'] . '-' . $n++; } } else { $n = count( $wp_registered_sidebars ); do { $_args['id'] = 'sidebar-' . ++$n; } while ( is_registered_sidebar( $_args['id'] ) ); } register_sidebar( $_args ); } } /** * Builds the definition for a single sidebar and returns the ID. * * Accepts either a string or an array and then parses that against a set * of default arguments for the new sidebar. WordPress will automatically * generate a sidebar ID and name based on the current number of registered * sidebars if those arguments are not included. * * When allowing for automatic generation of the name and ID parameters, keep * in mind that the incrementor for your sidebar can change over time depending * on what other plugins and themes are installed. * * If theme support for 'widgets' has not yet been added when this function is * called, it will be automatically enabled through the use of add_theme_support(). * * @since 2.2.0 * @since 5.6.0 Added the `before_sidebar` and `after_sidebar` arguments. * @since 5.9.0 Added the `show_in_rest` argument. * * @global array $wp_registered_sidebars The registered sidebars. * * @param array|string $args { * Optional. Array or string of arguments for the sidebar being registered. * * @type string $name The name or title of the sidebar displayed in the Widgets * interface. Default 'Sidebar $instance'. * @type string $id The unique identifier by which the sidebar will be called. * Default 'sidebar-$instance'. * @type string $description Description of the sidebar, displayed in the Widgets interface. * Default empty string. * @type string $class Extra CSS class to assign to the sidebar in the Widgets interface. * Default empty. * @type string $before_widget HTML content to prepend to each widget's HTML output when assigned * to this sidebar. Receives the widget's ID attribute as `%1$s` * and class name as `%2$s`. Default is an opening list item element. * @type string $after_widget HTML content to append to each widget's HTML output when assigned * to this sidebar. Default is a closing list item element. * @type string $before_title HTML content to prepend to the sidebar title when displayed. * Default is an opening h2 element. * @type string $after_title HTML content to append to the sidebar title when displayed. * Default is a closing h2 element. * @type string $before_sidebar HTML content to prepend to the sidebar when displayed. * Receives the `$id` argument as `%1$s` and `$class` as `%2$s`. * Outputs after the {@see 'dynamic_sidebar_before'} action. * Default empty string. * @type string $after_sidebar HTML content to append to the sidebar when displayed. * Outputs before the {@see 'dynamic_sidebar_after'} action. * Default empty string. * @type bool $show_in_rest Whether to show this sidebar publicly in the REST API. * Defaults to only showing the sidebar to administrator users. * } * @return string Sidebar ID added to $wp_registered_sidebars global. */ function register_sidebar( $args = array() ) { global $wp_registered_sidebars; $i = count( $wp_registered_sidebars ) + 1; $id_is_empty = empty( $args['id'] ); $defaults = array( /* translators: %d: Sidebar number. */ 'name' => sprintf( __( 'Sidebar %d' ), $i ), 'id' => "sidebar-$i", 'description' => '', 'class' => '', 'before_widget' => '<li id="%1$s" class="widget %2$s">', 'after_widget' => "</li>\n", 'before_title' => '<h2 class="widgettitle">', 'after_title' => "</h2>\n", 'before_sidebar' => '', 'after_sidebar' => '', 'show_in_rest' => false, ); /** * Filters the sidebar default arguments. * * @since 5.3.0 * * @see register_sidebar() * * @param array $defaults The default sidebar arguments. */ $sidebar = wp_parse_args( $args, apply_filters( 'register_sidebar_defaults', $defaults ) ); if ( $id_is_empty ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: The 'id' argument, 2: Sidebar name, 3: Recommended 'id' value. */ __( 'No %1$s was set in the arguments array for the "%2$s" sidebar. Defaulting to "%3$s". Manually set the %1$s to "%3$s" to silence this notice and keep existing sidebar content.' ), '<code>id</code>', $sidebar['name'], $sidebar['id'] ), '4.2.0' ); } $wp_registered_sidebars[ $sidebar['id'] ] = $sidebar; add_theme_support( 'widgets' ); /** * Fires once a sidebar has been registered. * * @since 3.0.0 * * @param array $sidebar Parsed arguments for the registered sidebar. */ do_action( 'register_sidebar', $sidebar ); return $sidebar['id']; } /** * Removes a sidebar from the list. * * @since 2.2.0 * * @global array $wp_registered_sidebars The registered sidebars. * * @param string|int $sidebar_id The ID of the sidebar when it was registered. */ function unregister_sidebar( $sidebar_id ) { global $wp_registered_sidebars; unset( $wp_registered_sidebars[ $sidebar_id ] ); } /** * Checks if a sidebar is registered. * * @since 4.4.0 * * @global array $wp_registered_sidebars The registered sidebars. * * @param string|int $sidebar_id The ID of the sidebar when it was registered. * @return bool True if the sidebar is registered, false otherwise. */ function is_registered_sidebar( $sidebar_id ) { global $wp_registered_sidebars; return isset( $wp_registered_sidebars[ $sidebar_id ] ); } /** * Registers an instance of a widget. * * The default widget option is 'classname' that can be overridden. * * The function can also be used to un-register widgets when `$output_callback` * parameter is an empty string. * * @since 2.2.0 * @since 5.3.0 Formalized the existing and already documented `...$params` parameter * by adding it to the function signature. * @since 5.8.0 Added show_instance_in_rest option. * * @global array $wp_registered_widgets Uses stored registered widgets. * @global array $wp_registered_widget_controls Stores the registered widget controls (options). * @global array $wp_registered_widget_updates The registered widget updates. * @global array $_wp_deprecated_widgets_callbacks * * @param int|string $id Widget ID. * @param string $name Widget display title. * @param callable $output_callback Run when widget is called. * @param array $options { * Optional. An array of supplementary widget options for the instance. * * @type string $classname Class name for the widget's HTML container. Default is a shortened * version of the output callback name. * @type string $description Widget description for display in the widget administration * panel and/or theme. * @type bool $show_instance_in_rest Whether to show the widget's instance settings in the REST API. * Only available for WP_Widget based widgets. * } * @param mixed ...$params Optional additional parameters to pass to the callback function when it's called. */ function wp_register_sidebar_widget( $id, $name, $output_callback, $options = array(), ...$params ) { global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates, $_wp_deprecated_widgets_callbacks; $id = strtolower( $id ); if ( empty( $output_callback ) ) { unset( $wp_registered_widgets[ $id ] ); return; } $id_base = _get_widget_id_base( $id ); if ( in_array( $output_callback, $_wp_deprecated_widgets_callbacks, true ) && ! is_callable( $output_callback ) ) { unset( $wp_registered_widget_controls[ $id ] ); unset( $wp_registered_widget_updates[ $id_base ] ); return; } $defaults = array( 'classname' => $output_callback ); $options = wp_parse_args( $options, $defaults ); $widget = array( 'name' => $name, 'id' => $id, 'callback' => $output_callback, 'params' => $params, ); $widget = array_merge( $widget, $options ); if ( is_callable( $output_callback ) && ( ! isset( $wp_registered_widgets[ $id ] ) || did_action( 'widgets_init' ) ) ) { /** * Fires once for each registered widget. * * @since 3.0.0 * * @param array $widget An array of default widget arguments. */ do_action( 'wp_register_sidebar_widget', $widget ); $wp_registered_widgets[ $id ] = $widget; } } /** * Retrieves description for widget. * * When registering widgets, the options can also include 'description' that * describes the widget for display on the widget administration panel or * in the theme. * * @since 2.5.0 * * @global array $wp_registered_widgets The registered widgets. * * @param int|string $id Widget ID. * @return string|void Widget description, if available. */ function wp_widget_description( $id ) { if ( ! is_scalar( $id ) ) { return; } global $wp_registered_widgets; if ( isset( $wp_registered_widgets[ $id ]['description'] ) ) { return esc_html( $wp_registered_widgets[ $id ]['description'] ); } } /** * Retrieves description for a sidebar. * * When registering sidebars a 'description' parameter can be included that * describes the sidebar for display on the widget administration panel. * * @since 2.9.0 * * @global array $wp_registered_sidebars The registered sidebars. * * @param string $id sidebar ID. * @return string|void Sidebar description, if available. */ function wp_sidebar_description( $id ) { if ( ! is_scalar( $id ) ) { return; } global $wp_registered_sidebars; if ( isset( $wp_registered_sidebars[ $id ]['description'] ) ) { return wp_kses( $wp_registered_sidebars[ $id ]['description'], 'sidebar_description' ); } } /** * Remove widget from sidebar. * * @since 2.2.0 * * @param int|string $id Widget ID. */ function wp_unregister_sidebar_widget( $id ) { /** * Fires just before a widget is removed from a sidebar. * * @since 3.0.0 * * @param int|string $id The widget ID. */ do_action( 'wp_unregister_sidebar_widget', $id ); wp_register_sidebar_widget( $id, '', '' ); wp_unregister_widget_control( $id ); } /** * Registers widget control callback for customizing options. * * @since 2.2.0 * @since 5.3.0 Formalized the existing and already documented `...$params` parameter * by adding it to the function signature. * * @global array $wp_registered_widget_controls The registered widget controls. * @global array $wp_registered_widget_updates The registered widget updates. * @global array $wp_registered_widgets The registered widgets. * @global array $_wp_deprecated_widgets_callbacks * * @param int|string $id Sidebar ID. * @param string $name Sidebar display name. * @param callable $control_callback Run when sidebar is displayed. * @param array $options { * Optional. Array or string of control options. Default empty array. * * @type int $height Never used. Default 200. * @type int $width Width of the fully expanded control form (but try hard to use the default width). * Default 250. * @type int|string $id_base Required for multi-widgets, i.e widgets that allow multiple instances such as the * text widget. The widget ID will end up looking like `{$id_base}-{$unique_number}`. * } * @param mixed ...$params Optional additional parameters to pass to the callback function when it's called. */ function wp_register_widget_control( $id, $name, $control_callback, $options = array(), ...$params ) { global $wp_registered_widget_controls, $wp_registered_widget_updates, $wp_registered_widgets, $_wp_deprecated_widgets_callbacks; $id = strtolower( $id ); $id_base = _get_widget_id_base( $id ); if ( empty( $control_callback ) ) { unset( $wp_registered_widget_controls[ $id ] ); unset( $wp_registered_widget_updates[ $id_base ] ); return; } if ( in_array( $control_callback, $_wp_deprecated_widgets_callbacks, true ) && ! is_callable( $control_callback ) ) { unset( $wp_registered_widgets[ $id ] ); return; } if ( isset( $wp_registered_widget_controls[ $id ] ) && ! did_action( 'widgets_init' ) ) { return; } $defaults = array( 'width' => 250, 'height' => 200, ); // Height is never used. $options = wp_parse_args( $options, $defaults ); $options['width'] = (int) $options['width']; $options['height'] = (int) $options['height']; $widget = array( 'name' => $name, 'id' => $id, 'callback' => $control_callback, 'params' => $params, ); $widget = array_merge( $widget, $options ); $wp_registered_widget_controls[ $id ] = $widget; if ( isset( $wp_registered_widget_updates[ $id_base ] ) ) { return; } if ( isset( $widget['params'][0]['number'] ) ) { $widget['params'][0]['number'] = -1; } unset( $widget['width'], $widget['height'], $widget['name'], $widget['id'] ); $wp_registered_widget_updates[ $id_base ] = $widget; } /** * Registers the update callback for a widget. * * @since 2.8.0 * @since 5.3.0 Formalized the existing and already documented `...$params` parameter * by adding it to the function signature. * * @global array $wp_registered_widget_updates The registered widget updates. * * @param string $id_base The base ID of a widget created by extending WP_Widget. * @param callable $update_callback Update callback method for the widget. * @param array $options Optional. Widget control options. See wp_register_widget_control(). * Default empty array. * @param mixed ...$params Optional additional parameters to pass to the callback function when it's called. */ function _register_widget_update_callback( $id_base, $update_callback, $options = array(), ...$params ) { global $wp_registered_widget_updates; if ( isset( $wp_registered_widget_updates[ $id_base ] ) ) { if ( empty( $update_callback ) ) { unset( $wp_registered_widget_updates[ $id_base ] ); } return; } $widget = array( 'callback' => $update_callback, 'params' => $params, ); $widget = array_merge( $widget, $options ); $wp_registered_widget_updates[ $id_base ] = $widget; } /** * Registers the form callback for a widget. * * @since 2.8.0 * @since 5.3.0 Formalized the existing and already documented `...$params` parameter * by adding it to the function signature. * * @global array $wp_registered_widget_controls The registered widget controls. * * @param int|string $id Widget ID. * @param string $name Name attribute for the widget. * @param callable $form_callback Form callback. * @param array $options Optional. Widget control options. See wp_register_widget_control(). * Default empty array. * @param mixed ...$params Optional additional parameters to pass to the callback function when it's called. */ function _register_widget_form_callback( $id, $name, $form_callback, $options = array(), ...$params ) { global $wp_registered_widget_controls; $id = strtolower( $id ); if ( empty( $form_callback ) ) { unset( $wp_registered_widget_controls[ $id ] ); return; } if ( isset( $wp_registered_widget_controls[ $id ] ) && ! did_action( 'widgets_init' ) ) { return; } $defaults = array( 'width' => 250, 'height' => 200, ); $options = wp_parse_args( $options, $defaults ); $options['width'] = (int) $options['width']; $options['height'] = (int) $options['height']; $widget = array( 'name' => $name, 'id' => $id, 'callback' => $form_callback, 'params' => $params, ); $widget = array_merge( $widget, $options ); $wp_registered_widget_controls[ $id ] = $widget; } /** * Removes control callback for widget. * * @since 2.2.0 * * @param int|string $id Widget ID. */ function wp_unregister_widget_control( $id ) { wp_register_widget_control( $id, '', '' ); } /** * Displays dynamic sidebar. * * By default this displays the default sidebar or 'sidebar-1'. If your theme specifies the 'id' or * 'name' parameter for its registered sidebars you can pass an ID or name as the $index parameter. * Otherwise, you can pass in a numerical index to display the sidebar at that index. * * @since 2.2.0 * * @global array $wp_registered_sidebars The registered sidebars. * @global array $wp_registered_widgets The registered widgets. * * @param int|string $index Optional. Index, name or ID of dynamic sidebar. Default 1. * @return bool True, if widget sidebar was found and called. False if not found or not called. */ function dynamic_sidebar( $index = 1 ) { global $wp_registered_sidebars, $wp_registered_widgets; if ( is_int( $index ) ) { $index = "sidebar-$index"; } else { $index = sanitize_title( $index ); foreach ( (array) $wp_registered_sidebars as $key => $value ) { if ( sanitize_title( $value['name'] ) === $index ) { $index = $key; break; } } } $sidebars_widgets = wp_get_sidebars_widgets(); if ( empty( $wp_registered_sidebars[ $index ] ) || empty( $sidebars_widgets[ $index ] ) || ! is_array( $sidebars_widgets[ $index ] ) ) { /** This action is documented in wp-includes/widget.php */ do_action( 'dynamic_sidebar_before', $index, false ); /** This action is documented in wp-includes/widget.php */ do_action( 'dynamic_sidebar_after', $index, false ); /** This filter is documented in wp-includes/widget.php */ return apply_filters( 'dynamic_sidebar_has_widgets', false, $index ); } $sidebar = $wp_registered_sidebars[ $index ]; $sidebar['before_sidebar'] = sprintf( $sidebar['before_sidebar'], $sidebar['id'], $sidebar['class'] ); /** * Fires before widgets are rendered in a dynamic sidebar. * * Note: The action also fires for empty sidebars, and on both the front end * and back end, including the Inactive Widgets sidebar on the Widgets screen. * * @since 3.9.0 * * @param int|string $index Index, name, or ID of the dynamic sidebar. * @param bool $has_widgets Whether the sidebar is populated with widgets. * Default true. */ do_action( 'dynamic_sidebar_before', $index, true ); if ( ! is_admin() && ! empty( $sidebar['before_sidebar'] ) ) { echo $sidebar['before_sidebar']; } $did_one = false; foreach ( (array) $sidebars_widgets[ $index ] as $id ) { if ( ! isset( $wp_registered_widgets[ $id ] ) ) { continue; } $params = array_merge( array( array_merge( $sidebar, array( 'widget_id' => $id, 'widget_name' => $wp_registered_widgets[ $id ]['name'], ) ), ), (array) $wp_registered_widgets[ $id ]['params'] ); // Substitute HTML `id` and `class` attributes into `before_widget`. $classname_ = ''; foreach ( (array) $wp_registered_widgets[ $id ]['classname'] as $cn ) { if ( is_string( $cn ) ) { $classname_ .= '_' . $cn; } elseif ( is_object( $cn ) ) { $classname_ .= '_' . get_class( $cn ); } } $classname_ = ltrim( $classname_, '_' ); $params[0]['before_widget'] = sprintf( $params[0]['before_widget'], str_replace( '\\', '_', $id ), $classname_ ); /** * Filters the parameters passed to a widget's display callback. * * Note: The filter is evaluated on both the front end and back end, * including for the Inactive Widgets sidebar on the Widgets screen. * * @since 2.5.0 * * @see register_sidebar() * * @param array $params { * @type array $args { * An array of widget display arguments. * * @type string $name Name of the sidebar the widget is assigned to. * @type string $id ID of the sidebar the widget is assigned to. * @type string $description The sidebar description. * @type string $class CSS class applied to the sidebar container. * @type string $before_widget HTML markup to prepend to each widget in the sidebar. * @type string $after_widget HTML markup to append to each widget in the sidebar. * @type string $before_title HTML markup to prepend to the widget title when displayed. * @type string $after_title HTML markup to append to the widget title when displayed. * @type string $widget_id ID of the widget. * @type string $widget_name Name of the widget. * } * @type array $widget_args { * An array of multi-widget arguments. * * @type int $number Number increment used for multiples of the same widget. * } * } */ $params = apply_filters( 'dynamic_sidebar_params', $params ); $callback = $wp_registered_widgets[ $id ]['callback']; /** * Fires before a widget's display callback is called. * * Note: The action fires on both the front end and back end, including * for widgets in the Inactive Widgets sidebar on the Widgets screen. * * The action is not fired for empty sidebars. * * @since 3.0.0 * * @param array $widget { * An associative array of widget arguments. * * @type string $name Name of the widget. * @type string $id Widget ID. * @type callable $callback When the hook is fired on the front end, `$callback` is an array * containing the widget object. Fired on the back end, `$callback` * is 'wp_widget_control', see `$_callback`. * @type array $params An associative array of multi-widget arguments. * @type string $classname CSS class applied to the widget container. * @type string $description The widget description. * @type array $_callback When the hook is fired on the back end, `$_callback` is populated * with an array containing the widget object, see `$callback`. * } */ do_action( 'dynamic_sidebar', $wp_registered_widgets[ $id ] ); if ( is_callable( $callback ) ) { call_user_func_array( $callback, $params ); $did_one = true; } } if ( ! is_admin() && ! empty( $sidebar['after_sidebar'] ) ) { echo $sidebar['after_sidebar']; } /** * Fires after widgets are rendered in a dynamic sidebar. * * Note: The action also fires for empty sidebars, and on both the front end * and back end, including the Inactive Widgets sidebar on the Widgets screen. * * @since 3.9.0 * * @param int|string $index Index, name, or ID of the dynamic sidebar. * @param bool $has_widgets Whether the sidebar is populated with widgets. * Default true. */ do_action( 'dynamic_sidebar_after', $index, true ); /** * Filters whether a sidebar has widgets. * * Note: The filter is also evaluated for empty sidebars, and on both the front end * and back end, including the Inactive Widgets sidebar on the Widgets screen. * * @since 3.9.0 * * @param bool $did_one Whether at least one widget was rendered in the sidebar. * Default false. * @param int|string $index Index, name, or ID of the dynamic sidebar. */ return apply_filters( 'dynamic_sidebar_has_widgets', $did_one, $index ); } /** * Determines whether a given widget is displayed on the front end. * * Either $callback or $id_base can be used. * $id_base is the first argument when extending WP_Widget class. * Without the optional $widget_id parameter, returns the ID of the first sidebar * in which the first instance of the widget with the given callback or $id_base is found. * With the $widget_id parameter, returns the ID of the sidebar where * the widget with that callback/$id_base AND that ID is found. * * NOTE: $widget_id and $id_base are the same for single widgets. To be effective * this function has to run after widgets have initialized, at action {@see 'init'} or later. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.2.0 * * @global array $wp_registered_widgets The registered widgets. * * @param callable|false $callback Optional. Widget callback to check. Default false. * @param string|false $widget_id Optional. Widget ID. Optional, but needed for checking. * Default false. * @param string|false $id_base Optional. The base ID of a widget created by extending WP_Widget. * Default false. * @param bool $skip_inactive Optional. Whether to check in 'wp_inactive_widgets'. * Default true. * @return string|false ID of the sidebar in which the widget is active, * false if the widget is not active. */ function is_active_widget( $callback = false, $widget_id = false, $id_base = false, $skip_inactive = true ) { global $wp_registered_widgets; $sidebars_widgets = wp_get_sidebars_widgets(); if ( is_array( $sidebars_widgets ) ) { foreach ( $sidebars_widgets as $sidebar => $widgets ) { if ( $skip_inactive && ( 'wp_inactive_widgets' === $sidebar || str_starts_with( $sidebar, 'orphaned_widgets' ) ) ) { continue; } if ( is_array( $widgets ) ) { foreach ( $widgets as $widget ) { if ( ( $callback && isset( $wp_registered_widgets[ $widget ]['callback'] ) && $wp_registered_widgets[ $widget ]['callback'] === $callback ) || ( $id_base && _get_widget_id_base( $widget ) === $id_base ) ) { if ( ! $widget_id || $widget_id === $wp_registered_widgets[ $widget ]['id'] ) { return $sidebar; } } } } } } return false; } /** * Determines whether the dynamic sidebar is enabled and used by the theme. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.2.0 * * @global array $wp_registered_widgets The registered widgets. * @global array $wp_registered_sidebars The registered sidebars. * * @return bool True if using widgets, false otherwise. */ function is_dynamic_sidebar() { global $wp_registered_widgets, $wp_registered_sidebars; $sidebars_widgets = get_option( 'sidebars_widgets' ); foreach ( (array) $wp_registered_sidebars as $index => $sidebar ) { if ( ! empty( $sidebars_widgets[ $index ] ) ) { foreach ( (array) $sidebars_widgets[ $index ] as $widget ) { if ( array_key_exists( $widget, $wp_registered_widgets ) ) { return true; } } } } return false; } /** * Determines whether a sidebar contains widgets. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.8.0 * * @param string|int $index Sidebar name, id or number to check. * @return bool True if the sidebar has widgets, false otherwise. */ function is_active_sidebar( $index ) { $index = ( is_int( $index ) ) ? "sidebar-$index" : sanitize_title( $index ); $sidebars_widgets = wp_get_sidebars_widgets(); $is_active_sidebar = ! empty( $sidebars_widgets[ $index ] ); /** * Filters whether a dynamic sidebar is considered "active". * * @since 3.9.0 * * @param bool $is_active_sidebar Whether or not the sidebar should be considered "active". * In other words, whether the sidebar contains any widgets. * @param int|string $index Index, name, or ID of the dynamic sidebar. */ return apply_filters( 'is_active_sidebar', $is_active_sidebar, $index ); } // // Internal Functions. // /** * Retrieves the full list of sidebars and their widget instance IDs. * * Will upgrade sidebar widget list, if needed. Will also save updated list, if * needed. * * @since 2.2.0 * @access private * * @global array $_wp_sidebars_widgets * @global array $sidebars_widgets * * @param bool $deprecated Not used (argument deprecated). * @return array Upgraded list of widgets to version 3 array format when called from the admin. */ function wp_get_sidebars_widgets( $deprecated = true ) { if ( true !== $deprecated ) { _deprecated_argument( __FUNCTION__, '2.8.1' ); } global $_wp_sidebars_widgets, $sidebars_widgets; /* * If loading from front page, consult $_wp_sidebars_widgets rather than options * to see if wp_convert_widget_settings() has made manipulations in memory. */ if ( ! is_admin() ) { if ( empty( $_wp_sidebars_widgets ) ) { $_wp_sidebars_widgets = get_option( 'sidebars_widgets', array() ); } $sidebars_widgets = $_wp_sidebars_widgets; } else { $sidebars_widgets = get_option( 'sidebars_widgets', array() ); } if ( is_array( $sidebars_widgets ) && isset( $sidebars_widgets['array_version'] ) ) { unset( $sidebars_widgets['array_version'] ); } /** * Filters the list of sidebars and their widgets. * * @since 2.7.0 * * @param array $sidebars_widgets An associative array of sidebars and their widgets. */ return apply_filters( 'sidebars_widgets', $sidebars_widgets ); } /** * Retrieves the registered sidebar with the given ID. * * @since 5.9.0 * * @global array $wp_registered_sidebars The registered sidebars. * * @param string $id The sidebar ID. * @return array|null The discovered sidebar, or null if it is not registered. */ function wp_get_sidebar( $id ) { global $wp_registered_sidebars; foreach ( (array) $wp_registered_sidebars as $sidebar ) { if ( $sidebar['id'] === $id ) { return $sidebar; } } if ( 'wp_inactive_widgets' === $id ) { return array( 'id' => 'wp_inactive_widgets', 'name' => __( 'Inactive widgets' ), ); } return null; } /** * Sets the sidebar widget option to update sidebars. * * @since 2.2.0 * @access private * * @global array $_wp_sidebars_widgets * @param array $sidebars_widgets Sidebar widgets and their settings. */ function wp_set_sidebars_widgets( $sidebars_widgets ) { global $_wp_sidebars_widgets; // Clear cached value used in wp_get_sidebars_widgets(). $_wp_sidebars_widgets = null; if ( ! isset( $sidebars_widgets['array_version'] ) ) { $sidebars_widgets['array_version'] = 3; } update_option( 'sidebars_widgets', $sidebars_widgets ); } /** * Retrieves default registered sidebars list. * * @since 2.2.0 * @access private * * @global array $wp_registered_sidebars The registered sidebars. * * @return array */ function wp_get_widget_defaults() { global $wp_registered_sidebars; $defaults = array(); foreach ( (array) $wp_registered_sidebars as $index => $sidebar ) { $defaults[ $index ] = array(); } return $defaults; } /** * Converts the widget settings from single to multi-widget format. * * @since 2.8.0 * * @global array $_wp_sidebars_widgets * * @param string $base_name Root ID for all widgets of this type. * @param string $option_name Option name for this widget type. * @param array $settings The array of widget instance settings. * @return array The array of widget settings converted to multi-widget format. */ function wp_convert_widget_settings( $base_name, $option_name, $settings ) { // This test may need expanding. $single = false; $changed = false; if ( empty( $settings ) ) { $single = true; } else { foreach ( array_keys( $settings ) as $number ) { if ( 'number' === $number ) { continue; } if ( ! is_numeric( $number ) ) { $single = true; break; } } } if ( $single ) { $settings = array( 2 => $settings ); // If loading from the front page, update sidebar in memory but don't save to options. if ( is_admin() ) { $sidebars_widgets = get_option( 'sidebars_widgets' ); } else { if ( empty( $GLOBALS['_wp_sidebars_widgets'] ) ) { $GLOBALS['_wp_sidebars_widgets'] = get_option( 'sidebars_widgets', array() ); } $sidebars_widgets = &$GLOBALS['_wp_sidebars_widgets']; } foreach ( (array) $sidebars_widgets as $index => $sidebar ) { if ( is_array( $sidebar ) ) { foreach ( $sidebar as $i => $name ) { if ( $base_name === $name ) { $sidebars_widgets[ $index ][ $i ] = "$name-2"; $changed = true; break 2; } } } } if ( is_admin() && $changed ) { update_option( 'sidebars_widgets', $sidebars_widgets ); } } $settings['_multiwidget'] = 1; if ( is_admin() ) { update_option( $option_name, $settings ); } return $settings; } /** * Outputs an arbitrary widget as a template tag. * * @since 2.8.0 * * @global WP_Widget_Factory $wp_widget_factory * * @param string $widget The widget's PHP class name (see class-wp-widget.php). * @param array $instance Optional. The widget's instance settings. Default empty array. * @param array $args { * Optional. Array of arguments to configure the display of the widget. * * @type string $before_widget HTML content that will be prepended to the widget's HTML output. * Default `<div class="widget %s">`, where `%s` is the widget's class name. * @type string $after_widget HTML content that will be appended to the widget's HTML output. * Default `</div>`. * @type string $before_title HTML content that will be prepended to the widget's title when displayed. * Default `<h2 class="widgettitle">`. * @type string $after_title HTML content that will be appended to the widget's title when displayed. * Default `</h2>`. * } */ function the_widget( $widget, $instance = array(), $args = array() ) { global $wp_widget_factory; if ( ! isset( $wp_widget_factory->widgets[ $widget ] ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: %s: register_widget() */ __( 'Widgets need to be registered using %s, before they can be displayed.' ), '<code>register_widget()</code>' ), '4.9.0' ); return; } $widget_obj = $wp_widget_factory->widgets[ $widget ]; if ( ! ( $widget_obj instanceof WP_Widget ) ) { return; } $default_args = array( 'before_widget' => '<div class="widget %s">', 'after_widget' => '</div>', 'before_title' => '<h2 class="widgettitle">', 'after_title' => '</h2>', ); $args = wp_parse_args( $args, $default_args ); $args['before_widget'] = sprintf( $args['before_widget'], $widget_obj->widget_options['classname'] ); $instance = wp_parse_args( $instance ); /** This filter is documented in wp-includes/class-wp-widget.php */ $instance = apply_filters( 'widget_display_callback', $instance, $widget_obj, $args ); if ( false === $instance ) { return; } /** * Fires before rendering the requested widget. * * @since 3.0.0 * * @param string $widget The widget's class name. * @param array $instance The current widget instance's settings. * @param array $args An array of the widget's sidebar arguments. */ do_action( 'the_widget', $widget, $instance, $args ); $widget_obj->_set( -1 ); $widget_obj->widget( $args, $instance ); } /** * Retrieves the widget ID base value. * * @since 2.8.0 * * @param string $id Widget ID. * @return string Widget ID base. */ function _get_widget_id_base( $id ) { return preg_replace( '/-[0-9]+$/', '', $id ); } /** * Handles sidebars config after theme change. * * @access private * @since 3.3.0 * * @global array $sidebars_widgets */ function _wp_sidebars_changed() { global $sidebars_widgets; if ( ! is_array( $sidebars_widgets ) ) { $sidebars_widgets = wp_get_sidebars_widgets(); } retrieve_widgets( true ); } /** * Validates and remaps any "orphaned" widgets to wp_inactive_widgets sidebar, * and saves the widget settings. This has to run at least on each theme change. * * For example, let's say theme A has a "footer" sidebar, and theme B doesn't have one. * After switching from theme A to theme B, all the widgets previously assigned * to the footer would be inaccessible. This function detects this scenario, and * moves all the widgets previously assigned to the footer under wp_inactive_widgets. * * Despite the word "retrieve" in the name, this function actually updates the database * and the global `$sidebars_widgets`. For that reason it should not be run on front end, * unless the `$theme_changed` value is 'customize' (to bypass the database write). * * @since 2.8.0 * * @global array $wp_registered_sidebars The registered sidebars. * @global array $sidebars_widgets * @global array $wp_registered_widgets The registered widgets. * * @param string|bool $theme_changed Whether the theme was changed as a boolean. A value * of 'customize' defers updates for the Customizer. * @return array Updated sidebars widgets. */ function retrieve_widgets( $theme_changed = false ) { global $wp_registered_sidebars, $sidebars_widgets, $wp_registered_widgets; $registered_sidebars_keys = array_keys( $wp_registered_sidebars ); $registered_widgets_ids = array_keys( $wp_registered_widgets ); if ( ! is_array( get_theme_mod( 'sidebars_widgets' ) ) ) { if ( empty( $sidebars_widgets ) ) { return array(); } unset( $sidebars_widgets['array_version'] ); $sidebars_widgets_keys = array_keys( $sidebars_widgets ); sort( $sidebars_widgets_keys ); sort( $registered_sidebars_keys ); if ( $sidebars_widgets_keys === $registered_sidebars_keys ) { $sidebars_widgets = _wp_remove_unregistered_widgets( $sidebars_widgets, $registered_widgets_ids ); return $sidebars_widgets; } } // Discard invalid, theme-specific widgets from sidebars. $sidebars_widgets = _wp_remove_unregistered_widgets( $sidebars_widgets, $registered_widgets_ids ); $sidebars_widgets = wp_map_sidebars_widgets( $sidebars_widgets ); // Replace non-array values inside the array with an empty array. foreach ( $sidebars_widgets as $key => $value ) { if ( ! is_array( $value ) ) { $sidebars_widgets[ $key ] = array(); } } // Find hidden/lost multi-widget instances. $shown_widgets = array_merge( ...array_values( $sidebars_widgets ) ); $lost_widgets = array_diff( $registered_widgets_ids, $shown_widgets ); foreach ( $lost_widgets as $key => $widget_id ) { $number = preg_replace( '/.+?-([0-9]+)$/', '$1', $widget_id ); // Only keep active and default widgets. if ( is_numeric( $number ) && (int) $number < 2 ) { unset( $lost_widgets[ $key ] ); } } $sidebars_widgets['wp_inactive_widgets'] = array_merge( $lost_widgets, (array) $sidebars_widgets['wp_inactive_widgets'] ); if ( 'customize' !== $theme_changed ) { // Update the widgets settings in the database. wp_set_sidebars_widgets( $sidebars_widgets ); } return $sidebars_widgets; } /** * Compares a list of sidebars with their widgets against an allowed list. * * @since 4.9.0 * @since 4.9.2 Always tries to restore widget assignments from previous data, not just if sidebars needed mapping. * * @global array $wp_registered_sidebars The registered sidebars. * * @param array $existing_sidebars_widgets List of sidebars and their widget instance IDs. * @return array Mapped sidebars widgets. */ function wp_map_sidebars_widgets( $existing_sidebars_widgets ) { global $wp_registered_sidebars; $new_sidebars_widgets = array( 'wp_inactive_widgets' => array(), ); // Short-circuit if there are no sidebars to map. if ( ! is_array( $existing_sidebars_widgets ) || empty( $existing_sidebars_widgets ) ) { return $new_sidebars_widgets; } foreach ( $existing_sidebars_widgets as $sidebar => $widgets ) { if ( 'wp_inactive_widgets' === $sidebar || str_starts_with( $sidebar, 'orphaned_widgets' ) ) { $new_sidebars_widgets['wp_inactive_widgets'] = array_merge( $new_sidebars_widgets['wp_inactive_widgets'], (array) $widgets ); unset( $existing_sidebars_widgets[ $sidebar ] ); } } // If old and new theme have just one sidebar, map it and we're done. if ( 1 === count( $existing_sidebars_widgets ) && 1 === count( $wp_registered_sidebars ) ) { $new_sidebars_widgets[ key( $wp_registered_sidebars ) ] = array_pop( $existing_sidebars_widgets ); return $new_sidebars_widgets; } // Map locations with the same slug. $existing_sidebars = array_keys( $existing_sidebars_widgets ); foreach ( $wp_registered_sidebars as $sidebar => $name ) { if ( in_array( $sidebar, $existing_sidebars, true ) ) { $new_sidebars_widgets[ $sidebar ] = $existing_sidebars_widgets[ $sidebar ]; unset( $existing_sidebars_widgets[ $sidebar ] ); } elseif ( ! array_key_exists( $sidebar, $new_sidebars_widgets ) ) { $new_sidebars_widgets[ $sidebar ] = array(); } } // If there are more sidebars, try to map them. if ( ! empty( $existing_sidebars_widgets ) ) { /* * If old and new theme both have sidebars that contain phrases * from within the same group, make an educated guess and map it. */ $common_slug_groups = array( array( 'sidebar', 'primary', 'main', 'right' ), array( 'second', 'left' ), array( 'sidebar-2', 'footer', 'bottom' ), array( 'header', 'top' ), ); // Go through each group... foreach ( $common_slug_groups as $slug_group ) { // ...and see if any of these slugs... foreach ( $slug_group as $slug ) { // ...and any of the new sidebars... foreach ( $wp_registered_sidebars as $new_sidebar => $args ) { // ...actually match! if ( false === stripos( $new_sidebar, $slug ) && false === stripos( $slug, $new_sidebar ) ) { continue; } // Then see if any of the existing sidebars... foreach ( $existing_sidebars_widgets as $sidebar => $widgets ) { // ...and any slug in the same group... foreach ( $slug_group as $slug ) { // ... have a match as well. if ( false === stripos( $sidebar, $slug ) && false === stripos( $slug, $sidebar ) ) { continue; } // Make sure this sidebar wasn't mapped and removed previously. if ( ! empty( $existing_sidebars_widgets[ $sidebar ] ) ) { // We have a match that can be mapped! $new_sidebars_widgets[ $new_sidebar ] = array_merge( $new_sidebars_widgets[ $new_sidebar ], $existing_sidebars_widgets[ $sidebar ] ); // Remove the mapped sidebar so it can't be mapped again. unset( $existing_sidebars_widgets[ $sidebar ] ); // Go back and check the next new sidebar. continue 3; } } // End foreach ( $slug_group as $slug ). } // End foreach ( $existing_sidebars_widgets as $sidebar => $widgets ). } // End foreach ( $wp_registered_sidebars as $new_sidebar => $args ). } // End foreach ( $slug_group as $slug ). } // End foreach ( $common_slug_groups as $slug_group ). } // Move any left over widgets to inactive sidebar. foreach ( $existing_sidebars_widgets as $widgets ) { if ( is_array( $widgets ) && ! empty( $widgets ) ) { $new_sidebars_widgets['wp_inactive_widgets'] = array_merge( $new_sidebars_widgets['wp_inactive_widgets'], $widgets ); } } // Sidebars_widgets settings from when this theme was previously active. $old_sidebars_widgets = get_theme_mod( 'sidebars_widgets' ); $old_sidebars_widgets = isset( $old_sidebars_widgets['data'] ) ? $old_sidebars_widgets['data'] : false; if ( is_array( $old_sidebars_widgets ) ) { // Remove empty sidebars, no need to map those. $old_sidebars_widgets = array_filter( $old_sidebars_widgets ); // Only check sidebars that are empty or have not been mapped to yet. foreach ( $new_sidebars_widgets as $new_sidebar => $new_widgets ) { if ( array_key_exists( $new_sidebar, $old_sidebars_widgets ) && ! empty( $new_widgets ) ) { unset( $old_sidebars_widgets[ $new_sidebar ] ); } } // Remove orphaned widgets, we're only interested in previously active sidebars. foreach ( $old_sidebars_widgets as $sidebar => $widgets ) { if ( str_starts_with( $sidebar, 'orphaned_widgets' ) ) { unset( $old_sidebars_widgets[ $sidebar ] ); } } $old_sidebars_widgets = _wp_remove_unregistered_widgets( $old_sidebars_widgets ); // Replace non-array values inside the array with an empty array. foreach ( $new_sidebars_widgets as $key => $value ) { if ( ! is_array( $value ) ) { $new_sidebars_widgets[ $key ] = array(); } } if ( ! empty( $old_sidebars_widgets ) ) { // Go through each remaining sidebar... foreach ( $old_sidebars_widgets as $old_sidebar => $old_widgets ) { // ...and check every new sidebar... foreach ( $new_sidebars_widgets as $new_sidebar => $new_widgets ) { // ...for every widget we're trying to revive. foreach ( $old_widgets as $key => $widget_id ) { $active_key = array_search( $widget_id, $new_widgets, true ); // If the widget is used elsewhere... if ( false !== $active_key ) { // ...and that elsewhere is inactive widgets... if ( 'wp_inactive_widgets' === $new_sidebar ) { // ...remove it from there and keep the active version... unset( $new_sidebars_widgets['wp_inactive_widgets'][ $active_key ] ); } else { // ...otherwise remove it from the old sidebar and keep it in the new one. unset( $old_sidebars_widgets[ $old_sidebar ][ $key ] ); } } // End if ( $active_key ). } // End foreach ( $old_widgets as $key => $widget_id ). } // End foreach ( $new_sidebars_widgets as $new_sidebar => $new_widgets ). } // End foreach ( $old_sidebars_widgets as $old_sidebar => $old_widgets ). } // End if ( ! empty( $old_sidebars_widgets ) ). // Restore widget settings from when theme was previously active. $new_sidebars_widgets = array_merge( $new_sidebars_widgets, $old_sidebars_widgets ); } return $new_sidebars_widgets; } /** * Compares a list of sidebars with their widgets against an allowed list. * * @since 4.9.0 * * @global array $wp_registered_widgets The registered widgets. * * @param array $sidebars_widgets List of sidebars and their widget instance IDs. * @param array $allowed_widget_ids Optional. List of widget IDs to compare against. Default: Registered widgets. * @return array Sidebars with allowed widgets. */ function _wp_remove_unregistered_widgets( $sidebars_widgets, $allowed_widget_ids = array() ) { if ( empty( $allowed_widget_ids ) ) { $allowed_widget_ids = array_keys( $GLOBALS['wp_registered_widgets'] ); } foreach ( $sidebars_widgets as $sidebar => $widgets ) { if ( is_array( $widgets ) ) { $sidebars_widgets[ $sidebar ] = array_intersect( $widgets, $allowed_widget_ids ); } } return $sidebars_widgets; } /** * Displays the RSS entries in a list. * * @since 2.5.0 * * @param string|array|object $rss RSS url. * @param array $args Widget arguments. */ function wp_widget_rss_output( $rss, $args = array() ) { if ( is_string( $rss ) ) { $rss = fetch_feed( $rss ); } elseif ( is_array( $rss ) && isset( $rss['url'] ) ) { $args = $rss; $rss = fetch_feed( $rss['url'] ); } elseif ( ! is_object( $rss ) ) { return; } if ( is_wp_error( $rss ) ) { if ( is_admin() || current_user_can( 'manage_options' ) ) { echo '<p><strong>' . __( 'RSS Error:' ) . '</strong> ' . esc_html( $rss->get_error_message() ) . '</p>'; } return; } $default_args = array( 'show_author' => 0, 'show_date' => 0, 'show_summary' => 0, 'items' => 0, ); $args = wp_parse_args( $args, $default_args ); $items = (int) $args['items']; if ( $items < 1 || 20 < $items ) { $items = 10; } $show_summary = (int) $args['show_summary']; $show_author = (int) $args['show_author']; $show_date = (int) $args['show_date']; if ( ! $rss->get_item_quantity() ) { echo '<ul><li>' . __( 'An error has occurred, which probably means the feed is down. Try again later.' ) . '</li></ul>'; $rss->__destruct(); unset( $rss ); return; } echo '<ul>'; foreach ( $rss->get_items( 0, $items ) as $item ) { $link = $item->get_link(); while ( ! empty( $link ) && stristr( $link, 'http' ) !== $link ) { $link = substr( $link, 1 ); } $link = esc_url( strip_tags( $link ) ); $title = esc_html( trim( strip_tags( $item->get_title() ) ) ); if ( empty( $title ) ) { $title = __( 'Untitled' ); } $desc = html_entity_decode( $item->get_description(), ENT_QUOTES, get_option( 'blog_charset' ) ); $desc = esc_attr( wp_trim_words( $desc, 55, ' […]' ) ); $summary = ''; if ( $show_summary ) { $summary = $desc; // Change existing [...] to […]. if ( str_ends_with( $summary, '[...]' ) ) { $summary = substr( $summary, 0, -5 ) . '[…]'; } $summary = '<div class="rssSummary">' . esc_html( $summary ) . '</div>'; } $date = ''; if ( $show_date ) { $date = $item->get_date( 'U' ); if ( $date ) { $date = ' <span class="rss-date">' . date_i18n( get_option( 'date_format' ), $date ) . '</span>'; } } $author = ''; if ( $show_author ) { $author = $item->get_author(); if ( is_object( $author ) ) { $author = $author->get_name(); $author = ' <cite>' . esc_html( strip_tags( $author ) ) . '</cite>'; } } if ( '' === $link ) { echo "<li>$title{$date}{$summary}{$author}</li>"; } elseif ( $show_summary ) { echo "<li><a class='rsswidget' href='$link'>$title</a>{$date}{$summary}{$author}</li>"; } else { echo "<li><a class='rsswidget' href='$link'>$title</a>{$date}{$author}</li>"; } } echo '</ul>'; $rss->__destruct(); unset( $rss ); } /** * Displays RSS widget options form. * * The options for what fields are displayed for the RSS form are all booleans * and are as follows: 'url', 'title', 'items', 'show_summary', 'show_author', * 'show_date'. * * @since 2.5.0 * * @param array|string $args Values for input fields. * @param array $inputs Override default display options. */ function wp_widget_rss_form( $args, $inputs = null ) { $default_inputs = array( 'url' => true, 'title' => true, 'items' => true, 'show_summary' => true, 'show_author' => true, 'show_date' => true, ); $inputs = wp_parse_args( $inputs, $default_inputs ); $args['title'] = isset( $args['title'] ) ? $args['title'] : ''; $args['url'] = isset( $args['url'] ) ? $args['url'] : ''; $args['items'] = isset( $args['items'] ) ? (int) $args['items'] : 0; if ( $args['items'] < 1 || 20 < $args['items'] ) { $args['items'] = 10; } $args['show_summary'] = isset( $args['show_summary'] ) ? (int) $args['show_summary'] : (int) $inputs['show_summary']; $args['show_author'] = isset( $args['show_author'] ) ? (int) $args['show_author'] : (int) $inputs['show_author']; $args['show_date'] = isset( $args['show_date'] ) ? (int) $args['show_date'] : (int) $inputs['show_date']; if ( ! empty( $args['error'] ) ) { echo '<p class="widget-error"><strong>' . __( 'RSS Error:' ) . '</strong> ' . esc_html( $args['error'] ) . '</p>'; } $esc_number = esc_attr( $args['number'] ); if ( $inputs['url'] ) : ?> <p><label for="rss-url-<?php echo $esc_number; ?>"><?php _e( 'Enter the RSS feed URL here:' ); ?></label> <input class="widefat" id="rss-url-<?php echo $esc_number; ?>" name="widget-rss[<?php echo $esc_number; ?>][url]" type="text" value="<?php echo esc_url( $args['url'] ); ?>" /></p> <?php endif; if ( $inputs['title'] ) : ?> <p><label for="rss-title-<?php echo $esc_number; ?>"><?php _e( 'Give the feed a title (optional):' ); ?></label> <input class="widefat" id="rss-title-<?php echo $esc_number; ?>" name="widget-rss[<?php echo $esc_number; ?>][title]" type="text" value="<?php echo esc_attr( $args['title'] ); ?>" /></p> <?php endif; if ( $inputs['items'] ) : ?> <p><label for="rss-items-<?php echo $esc_number; ?>"><?php _e( 'How many items would you like to display?' ); ?></label> <select id="rss-items-<?php echo $esc_number; ?>" name="widget-rss[<?php echo $esc_number; ?>][items]"> <?php for ( $i = 1; $i <= 20; ++$i ) { echo "<option value='$i' " . selected( $args['items'], $i, false ) . ">$i</option>"; } ?> </select></p> <?php endif; if ( $inputs['show_summary'] || $inputs['show_author'] || $inputs['show_date'] ) : ?> <p> <?php if ( $inputs['show_summary'] ) : ?> <input id="rss-show-summary-<?php echo $esc_number; ?>" name="widget-rss[<?php echo $esc_number; ?>][show_summary]" type="checkbox" value="1" <?php checked( $args['show_summary'] ); ?> /> <label for="rss-show-summary-<?php echo $esc_number; ?>"><?php _e( 'Display item content?' ); ?></label><br /> <?php endif; if ( $inputs['show_author'] ) : ?> <input id="rss-show-author-<?php echo $esc_number; ?>" name="widget-rss[<?php echo $esc_number; ?>][show_author]" type="checkbox" value="1" <?php checked( $args['show_author'] ); ?> /> <label for="rss-show-author-<?php echo $esc_number; ?>"><?php _e( 'Display item author if available?' ); ?></label><br /> <?php endif; if ( $inputs['show_date'] ) : ?> <input id="rss-show-date-<?php echo $esc_number; ?>" name="widget-rss[<?php echo $esc_number; ?>][show_date]" type="checkbox" value="1" <?php checked( $args['show_date'] ); ?> /> <label for="rss-show-date-<?php echo $esc_number; ?>"><?php _e( 'Display item date?' ); ?></label><br /> <?php endif; ?> </p> <?php endif; // End of display options. foreach ( array_keys( $default_inputs ) as $input ) : if ( 'hidden' === $inputs[ $input ] ) : $id = str_replace( '_', '-', $input ); ?> <input type="hidden" id="rss-<?php echo esc_attr( $id ); ?>-<?php echo $esc_number; ?>" name="widget-rss[<?php echo $esc_number; ?>][<?php echo esc_attr( $input ); ?>]" value="<?php echo esc_attr( $args[ $input ] ); ?>" /> <?php endif; endforeach; } /** * Processes RSS feed widget data and optionally retrieve feed items. * * The feed widget can not have more than 20 items or it will reset back to the * default, which is 10. * * The resulting array has the feed title, feed url, feed link (from channel), * feed items, error (if any), and whether to show summary, author, and date. * All respectively in the order of the array elements. * * @since 2.5.0 * * @param array $widget_rss RSS widget feed data. Expects unescaped data. * @param bool $check_feed Optional. Whether to check feed for errors. Default true. * @return array */ function wp_widget_rss_process( $widget_rss, $check_feed = true ) { $items = (int) $widget_rss['items']; if ( $items < 1 || 20 < $items ) { $items = 10; } $url = sanitize_url( strip_tags( $widget_rss['url'] ) ); $title = isset( $widget_rss['title'] ) ? trim( strip_tags( $widget_rss['title'] ) ) : ''; $show_summary = isset( $widget_rss['show_summary'] ) ? (int) $widget_rss['show_summary'] : 0; $show_author = isset( $widget_rss['show_author'] ) ? (int) $widget_rss['show_author'] : 0; $show_date = isset( $widget_rss['show_date'] ) ? (int) $widget_rss['show_date'] : 0; $error = false; $link = ''; if ( $check_feed ) { $rss = fetch_feed( $url ); if ( is_wp_error( $rss ) ) { $error = $rss->get_error_message(); } else { $link = esc_url( strip_tags( $rss->get_permalink() ) ); while ( stristr( $link, 'http' ) !== $link ) { $link = substr( $link, 1 ); } $rss->__destruct(); unset( $rss ); } } return compact( 'title', 'url', 'link', 'items', 'error', 'show_summary', 'show_author', 'show_date' ); } /** * Registers all of the default WordPress widgets on startup. * * Calls {@see 'widgets_init'} action after all of the WordPress widgets have been registered. * * @since 2.2.0 */ function wp_widgets_init() { if ( ! is_blog_installed() ) { return; } register_widget( 'WP_Widget_Pages' ); register_widget( 'WP_Widget_Calendar' ); register_widget( 'WP_Widget_Archives' ); if ( get_option( 'link_manager_enabled' ) ) { register_widget( 'WP_Widget_Links' ); } register_widget( 'WP_Widget_Media_Audio' ); register_widget( 'WP_Widget_Media_Image' ); register_widget( 'WP_Widget_Media_Gallery' ); register_widget( 'WP_Widget_Media_Video' ); register_widget( 'WP_Widget_Meta' ); register_widget( 'WP_Widget_Search' ); register_widget( 'WP_Widget_Text' ); register_widget( 'WP_Widget_Categories' ); register_widget( 'WP_Widget_Recent_Posts' ); register_widget( 'WP_Widget_Recent_Comments' ); register_widget( 'WP_Widget_RSS' ); register_widget( 'WP_Widget_Tag_Cloud' ); register_widget( 'WP_Nav_Menu_Widget' ); register_widget( 'WP_Widget_Custom_HTML' ); register_widget( 'WP_Widget_Block' ); /** * Fires after all default WordPress widgets have been registered. * * @since 2.2.0 */ do_action( 'widgets_init' ); } /** * Enables the widgets block editor. This is hooked into 'after_setup_theme' so * that the block editor is enabled by default but can be disabled by themes. * * @since 5.8.0 * * @access private */ function wp_setup_widgets_block_editor() { add_theme_support( 'widgets-block-editor' ); } /** * Determines whether or not to use the block editor to manage widgets. * Defaults to true unless a theme has removed support for widgets-block-editor * or a plugin has filtered the return value of this function. * * @since 5.8.0 * * @return bool Whether to use the block editor to manage widgets. */ function wp_use_widgets_block_editor() { /** * Filters whether to use the block editor to manage widgets. * * @since 5.8.0 * * @param bool $use_widgets_block_editor Whether to use the block editor to manage widgets. */ return apply_filters( 'use_widgets_block_editor', get_theme_support( 'widgets-block-editor' ) ); } /** * Converts a widget ID into its id_base and number components. * * @since 5.8.0 * * @param string $id Widget ID. * @return array Array containing a widget's id_base and number components. */ function wp_parse_widget_id( $id ) { $parsed = array(); if ( preg_match( '/^(.+)-(\d+)$/', $id, $matches ) ) { $parsed['id_base'] = $matches[1]; $parsed['number'] = (int) $matches[2]; } else { // Likely an old single widget. $parsed['id_base'] = $id; } return $parsed; } /** * Finds the sidebar that a given widget belongs to. * * @since 5.8.0 * * @param string $widget_id The widget ID to look for. * @return string|null The found sidebar's ID, or null if it was not found. */ function wp_find_widgets_sidebar( $widget_id ) { foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) { foreach ( $widget_ids as $maybe_widget_id ) { if ( $maybe_widget_id === $widget_id ) { return (string) $sidebar_id; } } } return null; } /** * Assigns a widget to the given sidebar. * * @since 5.8.0 * * @param string $widget_id The widget ID to assign. * @param string $sidebar_id The sidebar ID to assign to. If empty, the widget won't be added to any sidebar. */ function wp_assign_widget_to_sidebar( $widget_id, $sidebar_id ) { $sidebars = wp_get_sidebars_widgets(); foreach ( $sidebars as $maybe_sidebar_id => $widgets ) { foreach ( $widgets as $i => $maybe_widget_id ) { if ( $widget_id === $maybe_widget_id && $sidebar_id !== $maybe_sidebar_id ) { unset( $sidebars[ $maybe_sidebar_id ][ $i ] ); // We could technically break 2 here, but continue looping in case the ID is duplicated. continue 2; } } } if ( $sidebar_id ) { $sidebars[ $sidebar_id ][] = $widget_id; } wp_set_sidebars_widgets( $sidebars ); } /** * Calls the render callback of a widget and returns the output. * * @since 5.8.0 * * @global array $wp_registered_widgets The registered widgets. * @global array $wp_registered_sidebars The registered sidebars. * * @param string $widget_id Widget ID. * @param string $sidebar_id Sidebar ID. * @return string */ function wp_render_widget( $widget_id, $sidebar_id ) { global $wp_registered_widgets, $wp_registered_sidebars; if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) { return ''; } if ( isset( $wp_registered_sidebars[ $sidebar_id ] ) ) { $sidebar = $wp_registered_sidebars[ $sidebar_id ]; } elseif ( 'wp_inactive_widgets' === $sidebar_id ) { $sidebar = array(); } else { return ''; } $params = array_merge( array( array_merge( $sidebar, array( 'widget_id' => $widget_id, 'widget_name' => $wp_registered_widgets[ $widget_id ]['name'], ) ), ), (array) $wp_registered_widgets[ $widget_id ]['params'] ); // Substitute HTML `id` and `class` attributes into `before_widget`. $classname_ = ''; foreach ( (array) $wp_registered_widgets[ $widget_id ]['classname'] as $cn ) { if ( is_string( $cn ) ) { $classname_ .= '_' . $cn; } elseif ( is_object( $cn ) ) { $classname_ .= '_' . get_class( $cn ); } } $classname_ = ltrim( $classname_, '_' ); $params[0]['before_widget'] = sprintf( $params[0]['before_widget'], $widget_id, $classname_ ); /** This filter is documented in wp-includes/widgets.php */ $params = apply_filters( 'dynamic_sidebar_params', $params ); $callback = $wp_registered_widgets[ $widget_id ]['callback']; ob_start(); /** This filter is documented in wp-includes/widgets.php */ do_action( 'dynamic_sidebar', $wp_registered_widgets[ $widget_id ] ); if ( is_callable( $callback ) ) { call_user_func_array( $callback, $params ); } return ob_get_clean(); } /** * Calls the control callback of a widget and returns the output. * * @since 5.8.0 * * @global array $wp_registered_widget_controls The registered widget controls. * * @param string $id Widget ID. * @return string|null */ function wp_render_widget_control( $id ) { global $wp_registered_widget_controls; if ( ! isset( $wp_registered_widget_controls[ $id ]['callback'] ) ) { return null; } $callback = $wp_registered_widget_controls[ $id ]['callback']; $params = $wp_registered_widget_controls[ $id ]['params']; ob_start(); if ( is_callable( $callback ) ) { call_user_func_array( $callback, $params ); } return ob_get_clean(); } /** * Displays a _doing_it_wrong() message for conflicting widget editor scripts. * * The 'wp-editor' script module is exposed as window.wp.editor. This overrides * the legacy TinyMCE editor module which is required by the widgets editor. * Because of that conflict, these two shouldn't be enqueued together. * See https://core.trac.wordpress.org/ticket/53569. * * There is also another conflict related to styles where the block widgets * editor is hidden if a block enqueues 'wp-edit-post' stylesheet. * See https://core.trac.wordpress.org/ticket/53569. * * @since 5.8.0 * @access private * * @global WP_Scripts $wp_scripts * @global WP_Styles $wp_styles */ function wp_check_widget_editor_deps() { global $wp_scripts, $wp_styles; if ( $wp_scripts->query( 'wp-edit-widgets', 'enqueued' ) || $wp_scripts->query( 'wp-customize-widgets', 'enqueued' ) ) { if ( $wp_scripts->query( 'wp-editor', 'enqueued' ) ) { _doing_it_wrong( 'wp_enqueue_script()', sprintf( /* translators: 1: 'wp-editor', 2: 'wp-edit-widgets', 3: 'wp-customize-widgets'. */ __( '"%1$s" script should not be enqueued together with the new widgets editor (%2$s or %3$s).' ), 'wp-editor', 'wp-edit-widgets', 'wp-customize-widgets' ), '5.8.0' ); } if ( $wp_styles->query( 'wp-edit-post', 'enqueued' ) ) { _doing_it_wrong( 'wp_enqueue_style()', sprintf( /* translators: 1: 'wp-edit-post', 2: 'wp-edit-widgets', 3: 'wp-customize-widgets'. */ __( '"%1$s" style should not be enqueued together with the new widgets editor (%2$s or %3$s).' ), 'wp-edit-post', 'wp-edit-widgets', 'wp-customize-widgets' ), '5.8.0' ); } } } /** * Registers the previous theme's sidebars for the block themes. * * @since 6.2.0 * @access private * * @global array $wp_registered_sidebars The registered sidebars. */ function _wp_block_theme_register_classic_sidebars() { global $wp_registered_sidebars; if ( ! wp_is_block_theme() ) { return; } $classic_sidebars = get_theme_mod( 'wp_classic_sidebars' ); if ( empty( $classic_sidebars ) ) { return; } // Don't use `register_sidebar` since it will enable the `widgets` support for a theme. foreach ( $classic_sidebars as $sidebar ) { $wp_registered_sidebars[ $sidebar['id'] ] = $sidebar; } } https-detection-20260605141018.php 0000666 00000013341 15226762357 0012001 0 ustar 00 <?php /** * HTTPS detection functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether the website is using HTTPS. * * This is based on whether both the home and site URL are using HTTPS. * * @since 5.7.0 * @see wp_is_home_url_using_https() * @see wp_is_site_url_using_https() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_using_https() { if ( ! wp_is_home_url_using_https() ) { return false; } return wp_is_site_url_using_https(); } /** * Checks whether the current site URL is using HTTPS. * * @since 5.7.0 * @see home_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_home_url_using_https() { return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ); } /** * Checks whether the current site's URL where WordPress is stored is using HTTPS. * * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder) * are accessible. * * @since 5.7.0 * @see site_url() * * @return bool True if using HTTPS, false otherwise. */ function wp_is_site_url_using_https() { /* * Use direct option access for 'siteurl' and manually run the 'site_url' * filter because `site_url()` will adjust the scheme based on what the * current request is using. */ /** This filter is documented in wp-includes/link-template.php */ $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ); return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME ); } /** * Checks whether HTTPS is supported for the server and domain. * * This function makes an HTTP request through `wp_get_https_detection_errors()` * to check for HTTPS support. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments, * to avoid potential latency issues. * * @since 5.7.0 * * @return bool True if HTTPS is supported, false otherwise. */ function wp_is_https_supported() { $https_detection_errors = wp_get_https_detection_errors(); // If there are errors, HTTPS is not supported. return empty( $https_detection_errors ); } /** * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors. * * This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive, * it should be used cautiously, especially in performance-sensitive environments. * It is called when HTTPS support needs to be validated. * * @since 6.4.0 * @access private * * @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found. */ function wp_get_https_detection_errors() { /** * Short-circuits the process of detecting errors related to HTTPS support. * * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead. * * @since 6.4.0 * * @param null|WP_Error $pre Error object to short-circuit detection, * or null to continue with the default behavior. */ $support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null ); if ( is_wp_error( $support_errors ) ) { return $support_errors->errors; } $support_errors = new WP_Error(); $response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => true, ) ); if ( is_wp_error( $response ) ) { $unverified_response = wp_remote_request( home_url( '/', 'https' ), array( 'headers' => array( 'Cache-Control' => 'no-cache', ), 'sslverify' => false, ) ); if ( is_wp_error( $unverified_response ) ) { $support_errors->add( 'https_request_failed', __( 'HTTPS request failed.' ) ); } else { $support_errors->add( 'ssl_verification_failed', __( 'SSL verification failed.' ) ); } $response = $unverified_response; } if ( ! is_wp_error( $response ) ) { if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) ); } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) { $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) ); } } return $support_errors->errors; } /** * Checks whether a given HTML string is likely an output from this WordPress site. * * This function attempts to check for various common WordPress patterns whether they are included in the HTML string. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate * that it was not possible to determine ownership. * * @since 5.7.0 * @access private * * @param string $html Full HTML output string, e.g. from a HTTP response. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine. */ function wp_is_local_html_output( $html ) { // 1. Check if HTML includes the site's Really Simple Discovery link. if ( has_action( 'wp_head', 'rsd_link' ) ) { $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link(). return str_contains( $html, $pattern ); } // 2. Check if HTML includes the site's REST API link. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) { // Try both HTTPS and HTTP since the URL depends on context. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head(). return str_contains( $html, $pattern ); } // Otherwise the result cannot be determined. return null; } https-migration-20260606042318.php 0000666 00000011205 15226762357 0012015 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } https-migration-20260605132801-20260621203616.php 0000666 00000011205 15226762357 0013373 0 ustar 00 <?php /** * HTTPS migration functions. * * @package WordPress * @since 5.7.0 */ /** * Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to * add frontend filters to replace insecure site URLs that may be present in older database content. The * {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior. * * @since 5.7.0 * * @return bool True if insecure URLs should replaced, false otherwise. */ function wp_should_replace_insecure_home_url() { $should_replace_insecure_home_url = wp_is_using_https() && get_option( 'https_migration_required' ) // For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using // the same domain. && wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST ); /** * Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart. * * If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can * be used to disable that behavior, e.g. after having replaced URLs manually in the database. * * @since 5.7.0 * * @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced. */ return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url ); } /** * Replaces insecure HTTP URLs to the site in the given content, if configured to do so. * * This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if * determined via {@see wp_should_replace_insecure_home_url()}. * * @since 5.7.0 * * @param string $content Content to replace URLs in. * @return string Filtered content. */ function wp_replace_insecure_home_url( $content ) { if ( ! wp_should_replace_insecure_home_url() ) { return $content; } $https_url = home_url( '', 'https' ); $http_url = str_replace( 'https://', 'http://', $https_url ); // Also replace potentially escaped URL. $escaped_https_url = str_replace( '/', '\/', $https_url ); $escaped_http_url = str_replace( '/', '\/', $http_url ); return str_replace( array( $http_url, $escaped_http_url, ), array( $https_url, $escaped_https_url, ), $content ); } /** * Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL. * * If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants * overriding the URLs used), the changes will be reverted. In such a case the function will return false. * * @since 5.7.0 * * @return bool True on success, false on failure. */ function wp_update_urls_to_https() { // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); if ( ! wp_is_using_https() ) { /* * If this did not result in the site recognizing HTTPS as being used, * revert the change and return false. */ update_option( 'home', $orig_home ); update_option( 'siteurl', $orig_siteurl ); return false; } // Otherwise the URLs were successfully changed to use HTTPS. return true; } /** * Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS. * * If this is a fresh site, a migration will not be required, so the option will be set as `false`. * * This is hooked into the {@see 'update_option_home'} action. * * @since 5.7.0 * @access private * * @param mixed $old_url Previous value of the URL option. * @param mixed $new_url New value of the URL option. */ function wp_update_https_migration_required( $old_url, $new_url ) { // Do nothing if WordPress is being installed. if ( wp_installing() ) { return; } // Delete/reset the option if the new URL is not the HTTPS version of the old URL. if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) { delete_option( 'https_migration_required' ); return; } // If this is a fresh site, there is no content to migrate, so do not require migration. $https_migration_required = get_option( 'fresh_site' ) ? false : true; update_option( 'https_migration_required', $https_migration_required ); } session.php 0000666 00000000402 15226762357 0006757 0 ustar 00 <?php /** * Session API * * @since 4.0.0 * @deprecated 4.7.0 */ _deprecated_file( basename( __FILE__ ), '4.7.0' ); require_once ABSPATH . WPINC . '/class-wp-session-tokens.php'; require_once ABSPATH . WPINC . '/class-wp-user-meta-session-tokens.php';
| ver. 1.4 |
Github
|
.
| PHP 5.4.45 | Generation time: 0.11 |
proxy
|
phpinfo
|
Settings