PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.0.5
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.0.5
1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 All 29 releases
xspeed / includes / modules / Cdn / CdnModule.php

CdnModule.php in xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN 1.0.5, at includes/modules/Cdn/CdnModule.php

162 lines 5.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CDN module — rewrites local asset URLs to a user-supplied pull-zone
4 * CDN hostname (BunnyCDN, KeyCDN, Cloudflare R2, custom).
5 *
6 * Tier: Free per FEATURES.md "CDN Integration" §1-6 (LiteSpeed parity).
7 *
8 * @package XSpeed
9 */
10
11 declare(strict_types=1);
12
13 namespace XSpeed\Modules\Cdn;
14
15 defined( 'ABSPATH' ) || exit;
16
17 use XSpeed\Cdn_Rewriter;
18 use XSpeed\Module;
19
20 final class CdnModule extends Module {
21
22 public const SLUG = 'cdn';
23 public const TIER = self::TIER_FREE;
24 public const VERSION = '1.0.0';
25
26 public function ui_metadata(): array {
27 return array(
28 'label' => 'CDN',
29 'icon' => 'Globe',
30 'description' => 'Serve static assets (images, fonts, CSS, JS) from a pull-zone CDN host like BunnyCDN, KeyCDN, or your own.',
31 );
32 }
33
34 public function settings_schema(): array {
35 return array(
36 'enabled' => array(
37 'type' => 'bool',
38 'default' => false,
39 'label' => 'Enable CDN',
40 'description' => 'Rewrite static asset URLs to the CDN hostname below. Your CDN must be a pull-zone configured to fetch from this site.',
41 ),
42 'cdn_url' => array(
43 'type' => 'string',
44 'default' => '',
45 'label' => 'CDN URL',
46 'description' => 'CDN hostname, e.g. cdn.example.com. https:// and trailing slashes are stripped automatically.',
47 ),
48 'included_extensions' => array(
49 'type' => 'list',
50 'default' => Cdn_Rewriter::DEFAULT_EXTENSIONS,
51 'item_type' => 'string',
52 'label' => 'Included File Extensions',
53 'description' => 'Only URLs ending in these extensions are rewritten. Defaults cover images, fonts, CSS, JS, and common media.',
54 ),
55 'excluded_patterns' => array(
56 'type' => 'list',
57 'default' => array(),
58 'item_type' => 'string',
59 'label' => 'Excluded Patterns',
60 'description' => 'Glob patterns matched against the URL path. Matching URLs stay on the origin. Examples: /wp-admin/*, *.pdf, /private/*',
61 ),
62 );
63 }
64
65 public function conflicts(): array {
66 return array(
67 array(
68 'plugin' => 'cdn-enabler/cdn-enabler.php',
69 'feature' => 'cdn.rewrite',
70 'strategy' => \XSpeed\Conflict_Registry::STRATEGY_REFUSE,
71 'reason' => 'CDN Enabler rewrites the same URLs; running both will double-rewrite or produce broken hosts.',
72 ),
73 );
74 }
75
76 public function boot(): void {
77 // Always-on: normalize cdn_url on save (admin context too).
78 add_filter( 'pre_update_option_xspeed_module_cdn', array( $this, 'normalize_on_save' ), 10, 1 );
79
80 if ( is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || ( defined( 'DOING_CRON' ) && DOING_CRON ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
81 return;
82 }
83 $opts = $this->get_settings();
84 if ( empty( $opts['enabled'] ) || empty( $opts['cdn_url'] ) ) {
85 return;
86 }
87 Cdn_Rewriter::reset_state();
88 // Late filter — same convention as Lazy_Loader. Runs after
89 // shortcodes / blocks / embeds finish injecting tags.
90 add_filter( 'the_content', array( Cdn_Rewriter::class, 'process_html' ), 1000 );
91 add_filter( 'post_thumbnail_html', array( Cdn_Rewriter::class, 'process_html' ), 1000 );
92 add_filter( 'widget_text_content', array( Cdn_Rewriter::class, 'process_html' ), 1000 );
93 add_filter( 'wp_get_attachment_url', array( $this, 'rewrite_attachment_url' ), 1000 );
94 }
95
96 public function rewrite_attachment_url( $url ) {
97 if ( ! is_string( $url ) || '' === $url ) {
98 return $url;
99 }
100 return Cdn_Rewriter::rewrite_url( $url, $this->get_settings() );
101 }
102
103 /**
104 * pre_update_option filter — strips https:// + trailing slash from
105 * cdn_url before storage, so we always work against a bare host.
106 *
107 * @param mixed $value
108 * @return mixed
109 */
110 public function normalize_on_save( $value ) {
111 if ( ! is_array( $value ) ) {
112 return $value;
113 }
114 if ( isset( $value['cdn_url'] ) ) {
115 $value['cdn_url'] = Cdn_Rewriter::normalize_host( (string) $value['cdn_url'] );
116 }
117 return $value;
118 }
119
120 public function cli_commands(): array {
121 return array(
122 array(
123 'name' => 'xspeed cdn',
124 'callback' => array( $this, 'cli_handler' ),
125 'shortdesc' => 'Show CDN settings + test rewriting a URL.',
126 'synopsis' => array(
127 array(
128 'type' => 'positional',
129 'name' => 'action',
130 'options' => array( 'status', 'test' ),
131 'optional' => true,
132 ),
133 array(
134 'type' => 'assoc',
135 'name' => 'url',
136 'optional' => true,
137 ),
138 ),
139 ),
140 );
141 }
142
143 public function cli_handler( array $args, array $assoc ): void {
144 $action = $args[0] ?? 'status';
145 $opts = $this->get_settings();
146 if ( 'test' === $action ) {
147 $url = isset( $assoc['url'] ) ? (string) $assoc['url'] : '';
148 if ( '' === $url ) {
149 \WP_CLI::error( 'Pass --url=<url> to test rewriting.' );
150 }
151 Cdn_Rewriter::reset_state();
152 \WP_CLI::log( 'in: ' . $url );
153 \WP_CLI::log( 'out: ' . Cdn_Rewriter::rewrite_url( $url, $opts ) );
154 return;
155 }
156 \WP_CLI::log( sprintf( '%-22s %s', 'enabled', ! empty( $opts['enabled'] ) ? 'on' : 'off' ) );
157 \WP_CLI::log( sprintf( '%-22s %s', 'cdn_url', (string) ( $opts['cdn_url'] ?? '' ) ) );
158 \WP_CLI::log( sprintf( '%-22s %s', 'included_extensions', implode( ',', (array) ( $opts['included_extensions'] ?? array() ) ) ) );
159 \WP_CLI::log( sprintf( '%-22s %s', 'excluded_patterns', implode( ',', (array) ( $opts['excluded_patterns'] ?? array() ) ) ) );
160 }
161 }
162