PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.0
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.0
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 1.2.0 All 28 releases
xspeed / includes / class-cache-rules.php

class-cache-rules.php in xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN 1.3.0, at includes/class-cache-rules.php

175 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Cache_Rules — per-post overrides to the global cache policy.
4 *
5 * Postmeta:
6 * _xspeed_no_cache (bool) — when true, the post / page is never
7 * cached even if the global cache is
8 * on. Honored by Cache::should_cache.
9 * _xspeed_expiry_hours (int) — when > 0, overrides the global
10 * cache_expiry for this post. Honored
11 * by Cache::is_expired.
12 *
13 * Both metas are registered with WordPress core's REST API so the
14 * block editor (and other automation) can edit them without going
15 * through the classic-editor meta box.
16 *
17 * The engine reads via this class — never via raw get_post_meta in
18 * Cache::should_cache — so future enhancements (caching the
19 * resolution, exposing it through a filter, applying parent-page
20 * inheritance) have one place to land.
21 *
22 * @package XSpeed
23 */
24
25 declare(strict_types=1);
26
27 namespace XSpeed;
28
29 defined( 'ABSPATH' ) || exit;
30
31 final class Cache_Rules {
32
33 public const META_NO_CACHE = '_xspeed_no_cache';
34 public const META_EXPIRY_HOURS = '_xspeed_expiry_hours';
35
36 /**
37 * Should the renderer skip caching this specific post?
38 *
39 * Returns false when there's no resolvable post (e.g. the current
40 * request is an archive, a 404, or fired before WP_Query has
41 * settled) — the global policy then applies as before.
42 */
43 public static function should_skip_for_post( ?int $post_id ): bool {
44 if ( ! $post_id || $post_id < 1 ) {
45 $skip = false;
46 } else {
47 $skip = (bool) get_post_meta( $post_id, self::META_NO_CACHE, true );
48 }
49 /**
50 * Pro extension point — Pro's custom-rules engine can short-
51 * circuit caching for a post that doesn't have postmeta set
52 * but matches a pattern-based rule (category, tag, post_type,
53 * URL glob, etc.).
54 *
55 * @since 1.5.0
56 * @param bool $skip Current decision.
57 * @param int|null $post_id Post being evaluated (null when not in a singular context).
58 */
59 return (bool) apply_filters( 'xspeed_cache_skip_for_post', $skip, $post_id );
60 }
61
62 /**
63 * Per-post expiry in seconds (already multiplied by HOUR_IN_SECONDS)
64 * or null when the global cache_expiry should apply.
65 */
66 public static function expiry_override_seconds_for_post( ?int $post_id ): ?int {
67 if ( ! $post_id || $post_id < 1 ) {
68 $seconds = null;
69 } else {
70 $hours = (int) get_post_meta( $post_id, self::META_EXPIRY_HOURS, true );
71 if ( $hours <= 0 ) {
72 $seconds = null;
73 } else {
74 // Defensive clamp — matches the global field's bounds so any
75 // hand-edited postmeta still falls in a sane range.
76 $hours = max( 1, min( 720, $hours ) );
77 $seconds = $hours * HOUR_IN_SECONDS;
78 }
79 }
80 /**
81 * Pro extension point — Pro's custom-rules engine can override the
82 * per-post expiry from a pattern rule. Return null to fall back to
83 * the global expiry, or an int of seconds.
84 *
85 * @since 1.5.0
86 * @param int|null $seconds Current override (null = no override).
87 * @param int|null $post_id Post being evaluated.
88 */
89 $filtered = apply_filters( 'xspeed_cache_expiry_for_post', $seconds, $post_id );
90 return is_int( $filtered ) ? $filtered : null;
91 }
92
93 /**
94 * Resolve the post ID for the current request — wp_query's queried
95 * object when it's a post, falling back to get_the_ID(). Returns
96 * null for non-post contexts (archives, search, 404, taxonomies).
97 */
98 public static function current_post_id(): ?int {
99 if ( ! function_exists( 'is_singular' ) ) {
100 return null;
101 }
102 if ( ! is_singular() ) {
103 return null;
104 }
105 $id = function_exists( 'get_queried_object_id' ) ? (int) get_queried_object_id() : 0;
106 if ( $id < 1 && function_exists( 'get_the_ID' ) ) {
107 $id = (int) get_the_ID();
108 }
109 return $id > 0 ? $id : null;
110 }
111
112 /**
113 * Register both postmetas with the REST API so external editors
114 * (Gutenberg sidebar, our future custom panel, third-party
115 * automation) can read + write them. Called once from
116 * Cache_Meta_Box::boot on `init`.
117 */
118 public static function register_post_meta(): void {
119 $post_types = self::supported_post_types();
120 foreach ( $post_types as $type ) {
121 register_post_meta(
122 $type,
123 self::META_NO_CACHE,
124 array(
125 'show_in_rest' => true,
126 'single' => true,
127 'type' => 'boolean',
128 'default' => false,
129 'auth_callback' => static function ( $allowed, $meta_key, $post_id ) {
130 return current_user_can( 'edit_post', $post_id );
131 },
132 )
133 );
134 register_post_meta(
135 $type,
136 self::META_EXPIRY_HOURS,
137 array(
138 'show_in_rest' => true,
139 'single' => true,
140 'type' => 'integer',
141 'default' => 0,
142 'auth_callback' => static function ( $allowed, $meta_key, $post_id ) {
143 return current_user_can( 'edit_post', $post_id );
144 },
145 )
146 );
147 }
148 }
149
150 /**
151 * Which post types get the per-page rules UI. Public post types
152 * only by default — private CPTs and revisions don't need it.
153 * Filterable so site code can opt a private CPT in.
154 *
155 * @return string[]
156 */
157 public static function supported_post_types(): array {
158 $types = function_exists( 'get_post_types' )
159 ? get_post_types( array( 'public' => true ), 'names' )
160 : array( 'post', 'page' );
161 // Strip 'attachment' — caching individual media items via this UI
162 // is rarely useful and confuses non-technical users.
163 unset( $types['attachment'] );
164
165 /**
166 * Filter: xspeed_per_post_rules_post_types
167 *
168 * @param string[] $types Slugs of post types that show the
169 * per-page rules UI + accept the
170 * postmeta-driven overrides.
171 */
172 return (array) apply_filters( 'xspeed_per_post_rules_post_types', array_values( $types ) );
173 }
174 }
175