PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-stats / src / class-transient-cleanup.php
jetpack / jetpack_vendor / automattic / jetpack-stats / src Last commit date
abilities 1 week ago class-main.php 2 days ago class-options.php 3 weeks ago class-package-version.php 2 days ago class-rest-provider.php 2 years ago class-tracking-pixel.php 3 weeks ago class-transient-cleanup.php 1 week ago class-wpcom-stats.php 2 days ago class-xmlrpc-provider.php 2 days ago
class-transient-cleanup.php
245 lines
1 <?php
2 /**
3 * Stats Transient Cleanup
4 *
5 * Handles cleanup of expired stats cache transients to prevent database bloat
6 * on sites without a persistent object cache.
7 *
8 * Adapted from Brute Force Protection transient cleanup.
9 *
10 * @package automattic/jetpack-stats
11 */
12
13 namespace Automattic\Jetpack\Stats;
14
15 /**
16 * Transient_Cleanup class.
17 *
18 * Provides a scheduled cron job to clean up expired stats transients.
19 * WordPress transient garbage collection is "lazy" - expired transients are only
20 * deleted when accessed via get_transient(). Stats transients use dynamic cache keys
21 * based on query parameters, so expired entries are rarely accessed again and
22 * accumulate indefinitely in wp_options on sites without an external object cache.
23 *
24 * @since 0.18.0
25 */
26 class Transient_Cleanup {
27
28 /**
29 * Cron hook name.
30 */
31 const CRON_HOOK = 'jetpack_stats_transient_cleanup';
32
33 /**
34 * Batch size for transient cleanup.
35 *
36 * Used as the SQL LIMIT per prefix and as the threshold for stopping iteration.
37 * Actual deletions per run may slightly exceed this when processing multiple prefixes.
38 */
39 const BATCH_SIZE = 5000;
40
41 /**
42 * Cron interval in seconds (8 hours).
43 */
44 const CRON_INTERVAL = 28800;
45
46 /**
47 * Initialize the transient cleanup.
48 *
49 * @return void
50 */
51 public static function init() {
52 // Register the cron hook.
53 add_action( self::CRON_HOOK, array( static::class, 'run_cleanup' ) );
54
55 // Register custom cron schedule.
56 // phpcs:ignore WordPress.WP.CronInterval.ChangeDetected -- 8 hours is intentional for batched transient cleanup.
57 add_filter( 'cron_schedules', array( static::class, 'add_cron_schedule' ) );
58
59 // Schedule the cron job if not already scheduled.
60 add_action( 'admin_init', array( static::class, 'schedule_cleanup' ) );
61 }
62
63 /**
64 * Add custom cron schedule for 8-hour intervals.
65 *
66 * @param array $schedules Existing cron schedules.
67 * @return array Modified cron schedules.
68 */
69 public static function add_cron_schedule( $schedules ) {
70 if ( ! isset( $schedules['jetpack_stats_eight_hours'] ) ) {
71 $schedules['jetpack_stats_eight_hours'] = array(
72 'interval' => self::CRON_INTERVAL,
73 'display' => __( 'Every Eight Hours', 'jetpack-stats-pkg' ),
74 );
75 }
76 return $schedules;
77 }
78
79 /**
80 * Schedule the cleanup cron job.
81 *
82 * Skips scheduling on sites with persistent object cache since transients
83 * auto-expire there and cleanup is unnecessary.
84 *
85 * @return void
86 */
87 public static function schedule_cleanup() {
88 // Skip scheduling on sites with persistent object cache - not needed there.
89 if ( wp_using_ext_object_cache() ) {
90 // Unschedule if it was previously scheduled (e.g., object cache was added later).
91 if ( wp_next_scheduled( self::CRON_HOOK ) ) {
92 wp_clear_scheduled_hook( self::CRON_HOOK );
93 }
94 return;
95 }
96
97 if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
98 wp_schedule_event( time() + self::CRON_INTERVAL, 'jetpack_stats_eight_hours', self::CRON_HOOK );
99 }
100 }
101
102 /**
103 * Unschedule the cleanup cron job.
104 * Hooked to 'jetpack_deactivate_module_stats' in Main::__construct().
105 *
106 * @return void
107 */
108 public static function unschedule_cleanup() {
109 wp_clear_scheduled_hook( self::CRON_HOOK );
110 }
111
112 /**
113 * Get the list of transient prefixes to clean up.
114 *
115 * @return array List of transient prefixes.
116 */
117 public static function get_transient_prefixes() {
118 $prefixes = array(
119 WPCOM_Stats::STATS_CACHE_TRANSIENT_PREFIX, // jetpack_restapi_stats_cache_
120 );
121
122 /**
123 * Filter the list of transient prefixes to clean up.
124 *
125 * @since 0.18.0
126 *
127 * @param array $prefixes List of transient prefixes.
128 */
129 $filtered = apply_filters( 'jetpack_stats_transient_cleanup_prefixes', $prefixes );
130
131 // Normalize filtered value: ensure array, filter to non-empty strings, dedupe.
132 if ( ! is_array( $filtered ) ) {
133 $filtered = $prefixes;
134 }
135
136 $filtered = array_filter(
137 $filtered,
138 function ( $prefix ) {
139 return is_string( $prefix ) && '' !== $prefix;
140 }
141 );
142
143 return array_unique( $filtered );
144 }
145
146 /**
147 * Run the transient cleanup.
148 *
149 * @return int|false Number of deleted transient options (two entries per transient), or false if skipped.
150 */
151 public static function run_cleanup() {
152 /**
153 * Filter to disable transient cleanup.
154 *
155 * @since 0.18.0
156 *
157 * @param bool $disabled Whether to disable transient cleanup. Default false.
158 */
159 if ( apply_filters( 'jetpack_stats_transient_cleanup_disabled', false ) ) {
160 return false;
161 }
162
163 // Skip if using external object cache - transients auto-expire there.
164 if ( wp_using_ext_object_cache() ) {
165 return false;
166 }
167
168 $total_deleted = 0;
169 $prefixes = self::get_transient_prefixes();
170
171 foreach ( $prefixes as $prefix ) {
172 $deleted = self::purge_expired_transients( $prefix );
173 $total_deleted += $deleted;
174
175 // Stop processing additional prefixes once we've reached the batch threshold.
176 // Note: total may exceed BATCH_SIZE since each prefix can delete up to BATCH_SIZE.
177 if ( $total_deleted >= self::BATCH_SIZE ) {
178 break;
179 }
180 }
181
182 return $total_deleted;
183 }
184
185 /**
186 * Purge expired transients for a specific prefix.
187 *
188 * @param string $prefix The transient prefix to clean up.
189 * @return int Number of deleted transient options, two entries for each transient.
190 */
191 private static function purge_expired_transients( $prefix ) {
192 global $wpdb;
193
194 $now = time();
195 $timeout_prefix = '_transient_timeout_' . $prefix;
196 $like_pattern = $wpdb->esc_like( $timeout_prefix ) . '%';
197
198 // Find expired transients by querying timeout entries.
199 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
200 $transients = $wpdb->get_col(
201 $wpdb->prepare(
202 "SELECT SUBSTRING(option_name, %d) AS transient_name FROM {$wpdb->options} WHERE option_name LIKE %s AND option_value < %d LIMIT %d",
203 strlen( $timeout_prefix ) + 1,
204 $like_pattern,
205 $now,
206 self::BATCH_SIZE
207 )
208 );
209
210 if ( empty( $transients ) ) {
211 return 0;
212 }
213
214 // Build list of option names to delete (both transient and timeout entries).
215 $option_names = array();
216 foreach ( $transients as $transient ) {
217 $option_names[] = '_transient_' . $prefix . $transient;
218 $option_names[] = '_transient_timeout_' . $prefix . $transient;
219 }
220
221 // Delete in chunks to avoid excessively long SQL queries.
222 // Each option name can be ~80 chars, so 50 items ≈ 4KB per query.
223 $chunks = array_chunk( $option_names, 50 );
224 $total_deleted = 0;
225
226 foreach ( $chunks as $chunk ) {
227 $placeholders = implode( ', ', array_fill( 0, count( $chunk ), '%s' ) );
228
229 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
230 $result = $wpdb->query(
231 $wpdb->prepare(
232 "DELETE FROM {$wpdb->options} WHERE option_name IN ($placeholders)", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $placeholders is a list of %s.
233 $chunk
234 )
235 );
236
237 if ( false !== $result ) {
238 $total_deleted += $result;
239 }
240 }
241
242 return $total_deleted;
243 }
244 }
245