PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.0.3
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.0.3
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.3, at includes/modules/Cdn/CdnModule.php

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