'CDN', 'icon' => 'Globe', 'description' => 'Serve static assets (images, fonts, CSS, JS) from a pull-zone CDN host like BunnyCDN, KeyCDN, or your own.', ); } public function settings_schema(): array { return array( 'enabled' => array( 'type' => 'bool', 'default' => false, 'label' => 'Enable CDN', 'description' => 'Rewrite static asset URLs to the CDN hostname below. Your CDN must be a pull-zone configured to fetch from this site.', ), 'cdn_url' => array( 'type' => 'string', 'default' => '', 'label' => 'CDN URL', 'description' => 'CDN hostname, e.g. cdn.example.com. https:// and trailing slashes are stripped automatically.', ), 'included_extensions' => array( 'type' => 'list', 'default' => Cdn_Rewriter::DEFAULT_EXTENSIONS, 'item_type' => 'string', 'label' => 'Included File Extensions', 'description' => 'Only URLs ending in these extensions are rewritten. Defaults cover images, fonts, CSS, JS, and common media.', ), 'excluded_patterns' => array( 'type' => 'list', 'default' => array(), 'item_type' => 'string', 'label' => 'Excluded Patterns', 'description' => 'Glob patterns matched against the URL path. Matching URLs stay on the origin. Examples: /wp-admin/*, *.pdf, /private/*', ), ); } public function conflicts(): array { return array( array( 'plugin' => 'cdn-enabler/cdn-enabler.php', 'feature' => 'cdn.rewrite', 'strategy' => \XSpeed\Conflict_Registry::STRATEGY_REFUSE, 'reason' => 'CDN Enabler rewrites the same URLs; running both will double-rewrite or produce broken hosts.', ), ); } public function boot(): void { // Always-on: normalize cdn_url on save (admin context too). add_filter( 'pre_update_option_xspeed_module_cdn', array( $this, 'normalize_on_save' ), 10, 1 ); if ( is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || ( defined( 'DOING_CRON' ) && DOING_CRON ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { return; } $opts = $this->get_settings(); if ( empty( $opts['enabled'] ) || empty( $opts['cdn_url'] ) ) { return; } Cdn_Rewriter::reset_state(); // Late filter โ€” same convention as Lazy_Loader. Runs after // shortcodes / blocks / embeds finish injecting tags. add_filter( 'the_content', array( Cdn_Rewriter::class, 'process_html' ), 1000 ); add_filter( 'post_thumbnail_html', array( Cdn_Rewriter::class, 'process_html' ), 1000 ); add_filter( 'widget_text_content', array( Cdn_Rewriter::class, 'process_html' ), 1000 ); add_filter( 'wp_get_attachment_url', array( $this, 'rewrite_attachment_url' ), 1000 ); } public function rewrite_attachment_url( $url ) { if ( ! is_string( $url ) || '' === $url ) { return $url; } return Cdn_Rewriter::rewrite_url( $url, $this->get_settings() ); } /** * pre_update_option filter โ€” strips https:// + trailing slash from * cdn_url before storage, so we always work against a bare host. * * @param mixed $value * @return mixed */ public function normalize_on_save( $value ) { if ( ! is_array( $value ) ) { return $value; } if ( isset( $value['cdn_url'] ) ) { $value['cdn_url'] = Cdn_Rewriter::normalize_host( (string) $value['cdn_url'] ); } return $value; } public function cli_commands(): array { return array( array( 'name' => 'xspeed cdn', 'callback' => array( $this, 'cli_handler' ), 'shortdesc' => 'Show CDN settings + test rewriting a URL.', 'synopsis' => array( array( 'type' => 'positional', 'name' => 'action', 'options' => array( 'status', 'test' ), 'optional' => true, ), array( 'type' => 'assoc', 'name' => 'url', 'optional' => true, ), ), ), ); } public function cli_handler( array $args, array $assoc ): void { $action = $args[0] ?? 'status'; $opts = $this->get_settings(); if ( 'test' === $action ) { $url = isset( $assoc['url'] ) ? (string) $assoc['url'] : ''; if ( '' === $url ) { \WP_CLI::error( 'Pass --url= to test rewriting.' ); } Cdn_Rewriter::reset_state(); \WP_CLI::log( 'in: ' . $url ); \WP_CLI::log( 'out: ' . Cdn_Rewriter::rewrite_url( $url, $opts ) ); return; } \WP_CLI::log( sprintf( '%-22s %s', 'enabled', ! empty( $opts['enabled'] ) ? 'on' : 'off' ) ); \WP_CLI::log( sprintf( '%-22s %s', 'cdn_url', (string) ( $opts['cdn_url'] ?? '' ) ) ); \WP_CLI::log( sprintf( '%-22s %s', 'included_extensions', implode( ',', (array) ( $opts['included_extensions'] ?? array() ) ) ) ); \WP_CLI::log( sprintf( '%-22s %s', 'excluded_patterns', implode( ',', (array) ( $opts['excluded_patterns'] ?? array() ) ) ) ); } }