• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / wordpress-seo / 121d6055c956360fb867d8ed620c843ef16874b1

09 Dec 2025 02:53PM UTC coverage: 51.87%. Remained the same
121d6055c956360fb867d8ed620c843ef16874b1

push

github

web-flow
Merge pull request #22786 from Yoast/schema_aggregator/fix-cache

Add post type to cache layer.

8327 of 15641 branches covered (53.24%)

Branch coverage included in aggregate %.

0 of 15 new or added lines in 5 files covered. (0.0%)

1 existing line in 1 file now uncovered.

31846 of 61809 relevant lines covered (51.52%)

46650.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/schema-aggregator/application/cache/manager.php
1
<?php
2

3
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
4
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
5
namespace Yoast\WP\SEO\Schema_Aggregator\Application\Cache;
6

7
use Exception;
8
use Yoast\WP\SEO\Schema_Aggregator\Infrastructure\Config;
9

10
/**
11
 * Cache manager
12
 *
13
 * Manages cache storage/retrieval/invalidation using WordPress Transients API.
14
 */
15
class Manager {
16
        /**
17
         * Cache key prefix
18
         *
19
         * @var string
20
         */
21
        private const CACHE_PREFIX = 'yoast_schema_aggregator';
22

23
        /**
24
         * Cache version for invalidation
25
         *
26
         * @var int
27
         */
28
        private const CACHE_VERSION = 1;
29

30
        /**
31
         * Configuration provider
32
         *
33
         * @var Config
34
         */
35
        private $config;
36

37
        /**
38
         * Constructor
39
         *
40
         * @param Config $config Configuration provider.
41
         */
42
        public function __construct( Config $config ) {
×
43
                $this->config = $config;
×
44
        }
45

46
        /**
47
         * Get cached data for a page
48
         *
49
         * @param string $post_type The post type that the cache is for.
50
         * @param int    $page      Page number.
51
         * @param int    $per_page  Items per page.
52
         *
53
         * @return array<string>|null Cached data or null.
54
         */
NEW
55
        public function get( string $post_type, int $page, int $per_page ): ?array {
×
56
                try {
57
                        if ( ! $this->config->cache_enabled() ) {
×
58
                                return null;
×
59
                        }
60
                        if ( $page < 1 || $per_page < 1 ) {
×
61
                                return null;
×
62
                        }
63

NEW
64
                        $key = $this->get_cache_key( $post_type, $page, $per_page );
×
65

66
                        $data = \get_transient( $key );
×
67

68
                        if ( $data === false ) {
×
69
                                return null;
×
70
                        }
71
                        if ( ! \is_array( $data ) ) {
×
72
                                \delete_transient( $key );
×
73

74
                                return null;
×
75
                        }
76

77
                        return $data;
×
78

79
                } catch ( Exception $e ) {
×
80
                        return null;
×
81
                }
82
        }
83

84
        /**
85
         * Set cache data for a page
86
         *
87
         * @param string        $post_type The post type that the cache is for.
88
         * @param int           $page      Page number.
89
         * @param int           $per_page  Items per page.
90
         * @param array<string> $data      Data to cache.
91
         *
92
         * @return bool Success.
93
         */
NEW
94
        public function set( string $post_type, int $page, int $per_page, array $data ): bool {
×
95
                try {
96
                        if ( $page < 1 || $per_page < 1 || ! \is_array( $data ) ) {
×
97
                                return false;
×
98
                        }
99

NEW
100
                        $key        = $this->get_cache_key( $post_type, $page, $per_page );
×
101
                        $expiration = $this->config->get_expiration( $data );
×
102

103
                        return \set_transient( $key, $data, $expiration );
×
104

105
                } catch ( Exception $e ) {
×
106
                        return false;
×
107
                }
108
        }
109

110
        /**
111
         * Invalidate cache for specific page/per_page combination or all pages
112
         *
113
         * Note: When invalidating a specific page without per_page, this clears
114
         * ALL per_page variations for that page using a wildcard pattern.
115
         *
116
         * @param string   $post_type The post type that the cache is for.
117
         * @param int|null $page      Page number or null for all.
118
         * @param int|null $per_page  Items per page or null to clear all per_page variations.
119
         *
120
         * @return bool Success.
121
         */
NEW
122
        public function invalidate( string $post_type, ?int $page = null, ?int $per_page = null ): bool {
×
123
                if ( $page !== null && $per_page !== null ) {
×
124
                        // Clear specific page/per_page combination.
NEW
125
                        return \delete_transient( $this->get_cache_key( $post_type, $page, $per_page ) );
×
126
                }
127

128
                if ( $page !== null && $per_page === null ) {
×
129
                        // Clear all per_page variations for this page.
130
                        global $wpdb;
×
131

132
                        if ( ! isset( $wpdb ) || ! \is_object( $wpdb ) ) {
×
133
                                return false;
×
134
                        }
135

136
                        $pattern         = '_transient_' . self::CACHE_PREFIX . '_page_' . $page . '_per_%';
×
137
                        $timeout_pattern = '_transient_timeout_' . self::CACHE_PREFIX . '_page_' . $page . '_per_%';
×
138
                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
139
                        $deleted = $wpdb->query(
×
140
                                $wpdb->prepare(
×
141
                                        "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
×
142
                                        $pattern,
×
143
                                        $timeout_pattern
×
144
                                )
×
145
                        );
×
146

147
                        return $deleted !== false;
×
148
                }
149
                return $this->invalidate_all();
×
150
        }
151

152
        /**
153
         * Invalidate all cache pages
154
         *
155
         * @return bool Success.
156
         */
157
        public function invalidate_all(): bool {
×
158
                try {
159
                        global $wpdb;
×
160

161
                        if ( ! isset( $wpdb ) || ! \is_object( $wpdb ) ) {
×
162
                                return false;
×
163
                        }
164

165
                        // Pattern matches: yoast_schema__aggregator_page_{n}_per_{m}_v{version}.
166
                        $pattern         = '_transient_' . self::CACHE_PREFIX . '_page_%';
×
167
                        $timeout_pattern = '_transient_timeout_' . self::CACHE_PREFIX . '_page_%';
×
168
                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
169
                        $deleted = $wpdb->query(
×
170
                                $wpdb->prepare(
×
171
                                        "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
×
172
                                        $pattern,
×
173
                                        $timeout_pattern
×
174
                                )
×
175
                        );
×
176

177
                        if ( $deleted === false ) {
×
178
                                return false;
×
179
                        }
180

181
                        return true;
×
182

183
                } catch ( Exception $e ) {
×
184

185
                        return false;
×
186
                }
187
        }
188

189
        /**
190
         * Generate cache key for page.
191
         *
192
         * @param string $post_type The post type that the cache is for.
193
         * @param int    $page      Page number.
194
         * @param int    $per_page  Items per page.
195
         *
196
         * @return string Cache key.
197
         */
NEW
198
        private function get_cache_key( string $post_type, int $page, int $per_page ): string {
×
199
                return \sprintf(
×
NEW
200
                        '%s_page_%d_per_%d_type_%s_v%d',
×
201
                        self::CACHE_PREFIX,
×
202
                        $page,
×
203
                        $per_page,
×
NEW
204
                        $post_type,
×
205
                        self::CACHE_VERSION
×
206
                );
×
207
        }
208
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc