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

165 lines 5.4 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 'dependsOn' => array( 'field' => 'enabled' ),
48 ),
49 'included_extensions' => array(
50 'type' => 'list',
51 'default' => Cdn_Rewriter::DEFAULT_EXTENSIONS,
52 'item_type' => 'string',
53 'label' => 'Included File Extensions',
54 'description' => 'Only URLs ending in these extensions are rewritten. Defaults cover images, fonts, CSS, JS, and common media.',
55 'dependsOn' => array( 'field' => 'enabled' ),
56 ),
57 'excluded_patterns' => array(
58 'type' => 'list',
59 'default' => array(),
60 'item_type' => 'string',
61 'label' => 'Excluded Patterns',
62 'description' => 'Glob patterns matched against the URL path. Matching URLs stay on the origin. Examples: /wp-admin/*, *.pdf, /private/*',
63 'dependsOn' => array( 'field' => 'enabled' ),
64 ),
65 );
66 }
67
68 public function conflicts(): array {
69 return array(
70 array(
71 'plugin' => 'cdn-enabler/cdn-enabler.php',
72 'feature' => 'cdn.rewrite',
73 'strategy' => \XSpeed\Conflict_Registry::STRATEGY_REFUSE,
74 'reason' => 'CDN Enabler rewrites the same URLs; running both will double-rewrite or produce broken hosts.',
75 ),
76 );
77 }
78
79 public function boot(): void {
80 // Always-on: normalize cdn_url on save (admin context too).
81 add_filter( 'pre_update_option_xspeed_module_cdn', array( $this, 'normalize_on_save' ), 10, 1 );
82
83 if ( is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || ( defined( 'DOING_CRON' ) && DOING_CRON ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
84 return;
85 }
86 $opts = $this->get_settings();
87 if ( empty( $opts['enabled'] ) || empty( $opts['cdn_url'] ) ) {
88 return;
89 }
90 Cdn_Rewriter::reset_state();
91 // Late filter — same convention as Lazy_Loader. Runs after
92 // shortcodes / blocks / embeds finish injecting tags.
93 add_filter( 'the_content', array( Cdn_Rewriter::class, 'process_html' ), 1000 );
94 add_filter( 'post_thumbnail_html', array( Cdn_Rewriter::class, 'process_html' ), 1000 );
95 add_filter( 'widget_text_content', array( Cdn_Rewriter::class, 'process_html' ), 1000 );
96 add_filter( 'wp_get_attachment_url', array( $this, 'rewrite_attachment_url' ), 1000 );
97 }
98
99 public function rewrite_attachment_url( $url ) {
100 if ( ! is_string( $url ) || '' === $url ) {
101 return $url;
102 }
103 return Cdn_Rewriter::rewrite_url( $url, $this->get_settings() );
104 }
105
106 /**
107 * pre_update_option filter — strips https:// + trailing slash from
108 * cdn_url before storage, so we always work against a bare host.
109 *
110 * @param mixed $value
111 * @return mixed
112 */
113 public function normalize_on_save( $value ) {
114 if ( ! is_array( $value ) ) {
115 return $value;
116 }
117 if ( isset( $value['cdn_url'] ) ) {
118 $value['cdn_url'] = Cdn_Rewriter::normalize_host( (string) $value['cdn_url'] );
119 }
120 return $value;
121 }
122
123 public function cli_commands(): array {
124 return array(
125 array(
126 'name' => 'xspeed cdn',
127 'callback' => array( $this, 'cli_handler' ),
128 'shortdesc' => 'Show CDN settings + test rewriting a URL.',
129 'synopsis' => array(
130 array(
131 'type' => 'positional',
132 'name' => 'action',
133 'options' => array( 'status', 'test' ),
134 'optional' => true,
135 ),
136 array(
137 'type' => 'assoc',
138 'name' => 'url',
139 'optional' => true,
140 ),
141 ),
142 ),
143 );
144 }
145
146 public function cli_handler( array $args, array $assoc ): void {
147 $action = $args[0] ?? 'status';
148 $opts = $this->get_settings();
149 if ( 'test' === $action ) {
150 $url = isset( $assoc['url'] ) ? (string) $assoc['url'] : '';
151 if ( '' === $url ) {
152 \WP_CLI::error( 'Pass --url=<url> to test rewriting.' );
153 }
154 Cdn_Rewriter::reset_state();
155 \WP_CLI::log( 'in: ' . $url );
156 \WP_CLI::log( 'out: ' . Cdn_Rewriter::rewrite_url( $url, $opts ) );
157 return;
158 }
159 \WP_CLI::log( sprintf( '%-22s %s', 'enabled', ! empty( $opts['enabled'] ) ? 'on' : 'off' ) );
160 \WP_CLI::log( sprintf( '%-22s %s', 'cdn_url', (string) ( $opts['cdn_url'] ?? '' ) ) );
161 \WP_CLI::log( sprintf( '%-22s %s', 'included_extensions', implode( ',', (array) ( $opts['included_extensions'] ?? array() ) ) ) );
162 \WP_CLI::log( sprintf( '%-22s %s', 'excluded_patterns', implode( ',', (array) ( $opts['excluded_patterns'] ?? array() ) ) ) );
163 }
164 }
165