PluginProbe
RabbitLoader / 4.0.2
RabbitLoader v4.0.2
4.0.2 4.0.1 4.0 3.2.0 3.1.1 3.1.0 3.0.5 3.0.3 3.0.4 2.17.1 2.17.2 2.17.3 2.17.4 2.17.5 2.17.6 2.17.7 2.18.0 2.18.1 2.18.2 2.18.3 2.18.4 2.18.5 2.18.6 2.18.7 2.18.8 All 129 releases
rabbit-loader / inc / class-rl-runtime.php

class-rl-runtime.php in RabbitLoader 4.0.2, at inc/class-rl-runtime.php

268 lines 8.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Runtime integration (Path 2): serves optimized pages via the ported SDK,
4 * using NATIVE WordPress calls for the bypass logic instead of the old
5 * plugin's helper-class web.
6 *
7 * Flow:
8 * advanced-cache.php → RL5_Runtime::process('ac') (very early, cache HIT serves here)
9 * plugins_loaded/init → RL5_Runtime::process('wp') (fallback + miss capture)
10 * shutdown → SDK captures origin HTML → optimizer → LONG cache
11 *
12 * The SDK (inc/RabbitLoader/SDK/*) is the caching engine and is unchanged.
13 *
14 * @package RabbitLoader
15 */
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 class RL5_Runtime {
22
23 /** @var \RabbitLoader\SDK\RabbitLoader|null */
24 private static $sdk = null;
25 private static $skip_reason = '';
26 private static $processed = false;
27
28 /**
29 * Load the SDK classes (namespace RabbitLoader\SDK).
30 */
31 private static function load_sdk_files() {
32 $dir = RL5_DIR . 'inc/RabbitLoader/SDK/';
33 require_once $dir . 'File.php';
34 require_once $dir . 'Exc.php';
35 require_once $dir . 'Util.php';
36 require_once $dir . 'WordPress.php';
37 require_once $dir . 'Cache.php';
38 require_once $dir . 'API.php';
39 require_once $dir . 'Request.php';
40 require_once $dir . 'RabbitLoader.php';
41 }
42
43 /**
44 * Cache storage directory. Kept OUTSIDE the plugin so it survives updates.
45 * wp-content/cache/rabbitloader/<did>/
46 */
47 public static function cache_dir() {
48 $base = defined( 'WP_CONTENT_DIR' ) ? WP_CONTENT_DIR : ( ABSPATH . 'wp-content' );
49 $did = RL5_Settings::did();
50 $did = $did ? $did : 'default';
51 return rtrim( $base, '/\\' ) . '/cache/rabbitloader/' . $did;
52 }
53
54 /**
55 * Build (or reuse) the SDK instance.
56 * @return \RabbitLoader\SDK\RabbitLoader|null
57 */
58 private static function sdk() {
59 if ( null !== self::$sdk ) {
60 return self::$sdk;
61 }
62 $token = RL5_Settings::api_token();
63 if ( '' === $token ) {
64 return null; // not connected → no runtime
65 }
66 self::load_sdk_files();
67
68 self::$sdk = new \RabbitLoader\SDK\RabbitLoader( $token, self::cache_dir() );
69 self::$sdk->setDebug( defined( 'WP_DEBUG' ) && WP_DEBUG );
70 self::$sdk->setPlatform( array(
71 'plugin_cms' => 'wp',
72 'plugin_v' => RL5_VERSION,
73 'cms_v' => get_bloginfo( 'version' ),
74 ) );
75 return self::$sdk;
76 }
77
78 /**
79 * Entry point. $mode = 'ac' (advanced-cache, very early) or 'wp' (hooked).
80 */
81 public static function process( $mode = 'wp' ) {
82 if ( self::$processed ) {
83 return;
84 }
85
86 $sdk = self::sdk();
87 if ( null === $sdk ) {
88 return;
89 }
90
91 try {
92 \RabbitLoader\SDK\Util::sendHeader( 'x-rl-mode: ' . $mode, false );
93
94 // Ignore tracking params so they don't fragment the cache.
95 $sdk->ignoreParams( array(
96 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
97 'gclid', 'fbclid', 'msclkid', 'mc_cid', 'mc_eid', '_ga', 'ref',
98 ) );
99
100 // ME mode (serve optimized only to the owner for testing).
101 if ( '1' === (string) RL5_Settings::get( 'private_mode_val', '' ) ) {
102 $sdk->setMeMode();
103 }
104
105 // Exclude-path patterns from settings (one per line).
106 $excludes_raw = (string) RL5_Settings::get( 'exclude_patterns', '' );
107 if ( '' !== trim( $excludes_raw ) ) {
108 $sdk->skipForPaths( array_filter( array_map( 'trim', explode( "\n", $excludes_raw ) ) ) );
109 }
110
111 // Skip cache for cart/session cookies (native names).
112 $sdk->skipForCookies( self::bypass_cookies() );
113
114 // The bypass decision (native WP). If skipping, tell the SDK.
115 $reason = self::skip_reason( $mode );
116 if ( '' !== $reason ) {
117 $sdk->ignoreRequest( $reason );
118 }
119
120 // When the SDK optimizes/purges a URL, also purge our CDN.
121 $sdk->registerPurgeCallback( function ( $url ) {
122 if ( class_exists( 'RL5_API' ) ) {
123 // Fire a targeted purge for the changed URL (best-effort).
124 try {
125 $api = new RL5_API();
126 if ( method_exists( $api, 'purge_url' ) ) {
127 $api->purge_url( $url );
128 }
129 } catch ( \Throwable $e ) {
130 // swallow — purge callback must never break page serving
131 }
132 }
133 } );
134
135 $sdk->process();
136 self::$processed = true;
137 } catch ( \Throwable $e ) {
138 // Runtime must never fatal a page.
139 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
140 error_log( 'RL5 runtime: ' . $e->getMessage() );
141 }
142 }
143 }
144
145 /**
146 * Cookie names that mean "don't serve cache" (logged-in, Woo cart/session).
147 */
148 private static function bypass_cookies() {
149 $cookies = array(
150 'wordpress_logged_in_', // any logged-in user
151 'comment_author_', // commenters
152 'woocommerce_items_in_cart', // Woo cart not empty
153 'woocommerce_cart_hash',
154 'wp_woocommerce_session_', // Woo session
155 'edd_items_in_cart', // Easy Digital Downloads
156 );
157 return $cookies;
158 }
159
160 /**
161 * Native-WP bypass logic. Mirrors old can_cache_request() exactly, but
162 * calls WordPress/Woo functions directly. Returns '' if cacheable,
163 * else a short skip reason string.
164 *
165 * NOTE: in 'ac' mode (advanced-cache, pre-WP) most conditional tags are
166 * unavailable — the SDK's own cookie/path checks + the HIT-only serve
167 * handle that safely. The full checks run in 'wp' mode (after init).
168 */
169 private static function skip_reason( $mode ) {
170 if ( '' !== self::$skip_reason ) {
171 return self::$skip_reason;
172 }
173
174 // Method must be GET.
175 $method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : 'GET';
176 if ( 'GET' !== $method ) {
177 return self::$skip_reason = 'method-' . strtolower( $method );
178 }
179
180 // No HTTP host / CLI.
181 if ( empty( $_SERVER['HTTP_HOST'] ) || ( defined( 'WP_CLI' ) && WP_CLI ) || php_sapi_name() === 'cli' ) {
182 return self::$skip_reason = 'cli';
183 }
184
185 // In 'ac' (pre-WordPress) we can't call conditional tags. The SDK
186 // only SERVES on a valid HIT and only for anonymous requests
187 // (cookie bypass already applied), so it's safe to proceed.
188 if ( 'ac' === $mode ) {
189 return '';
190 }
191
192 // ---- 'wp' mode: full WordPress checks (functions now available) ----
193
194 if ( function_exists( 'is_user_logged_in' ) && is_user_logged_in() ) {
195 return self::$skip_reason = 'user-session';
196 }
197
198 if ( ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
199 return self::$skip_reason = 'ajax';
200 }
201 if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
202 return self::$skip_reason = 'rest';
203 }
204 if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
205 return self::$skip_reason = 'cron';
206 }
207
208 if ( function_exists( 'is_admin' ) && is_admin() ) {
209 return self::$skip_reason = 'admin';
210 }
211 if ( function_exists( 'is_search' ) && is_search() ) {
212 return self::$skip_reason = 'search';
213 }
214 if ( function_exists( 'is_404' ) && is_404() ) {
215 return self::$skip_reason = '404';
216 }
217 if ( function_exists( 'is_feed' ) && is_feed() ) {
218 return self::$skip_reason = 'feed';
219 }
220 if ( function_exists( 'is_preview' ) && is_preview() ) {
221 return self::$skip_reason = 'preview';
222 }
223
224 // WooCommerce cart/checkout/account pages.
225 if ( function_exists( 'is_cart' ) && is_cart() ) {
226 return self::$skip_reason = 'cart';
227 }
228 if ( function_exists( 'is_checkout' ) && is_checkout() ) {
229 return self::$skip_reason = 'checkout';
230 }
231 if ( function_exists( 'is_account_page' ) && is_account_page() ) {
232 return self::$skip_reason = 'account';
233 }
234 // Woo cart with items (cookie is the fast signal; this is the WP-side check).
235 if ( function_exists( 'WC' ) && WC() && isset( WC()->cart ) && WC()->cart && ! WC()->cart->is_empty() ) {
236 return self::$skip_reason = 'cart-items';
237 }
238
239 return '';
240 }
241
242 /**
243 * Register WordPress hooks for the miss/capture path (called from plugin boot).
244 */
245 public static function hooks() {
246 if ( ! RL5_Settings::is_connected() ) {
247 return;
248 }
249 // Run the full-check process after WP is initialized (miss path capture).
250 add_action( 'template_redirect', array( __CLASS__, 'on_template_redirect' ), 0 );
251
252 // Avoid caching redirects.
253 add_filter( 'wp_redirect', array( __CLASS__, 'note_redirect' ), 10, 1 );
254 }
255
256 public static function on_template_redirect() {
257 self::process( 'wp' );
258 }
259
260 public static function note_redirect( $location ) {
261 self::$skip_reason = 'redirect';
262 if ( self::$sdk ) {
263 self::$sdk->ignoreRequest( 'redirect' );
264 }
265 return $location;
266 }
267 }
268