-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathclass-orphaned-issues-cleanup.php
More file actions
191 lines (170 loc) · 4.3 KB
/
Copy pathclass-orphaned-issues-cleanup.php
File metadata and controls
191 lines (170 loc) · 4.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
<?php
/**
* Scheduled cleanup of orphaned issues.
*
* @package Accessibility_Checker
*/
namespace EDAC\Admin;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Orphaned_Issues_Cleanup
*
* @since 1.29.0
*/
class Orphaned_Issues_Cleanup {
/**
* Cron event name.
*
* @var string
*/
const EVENT = 'edac_cleanup_orphaned_issues';
/**
* Max number of posts to cleanup per run.
*
* @var int
*/
const BATCH_LIMIT = 50;
/**
* Batch size for cleanup (overrides BATCH_LIMIT if set).
*
* @var int|null
*/
private ?int $batch_size = null;
/**
* Register hooks.
*
* @since 1.29.0
*
* @return void
*/
public function init_hooks() {
self::schedule_event();
add_action( self::EVENT, [ $this, 'run_cleanup' ] );
}
/**
* The recurrence interval for the cleanup event.
*
* @var string
*/
const RECURRENCE = 'twicedaily';
/**
* Schedule the cleanup event.
*
* If the event is already scheduled with a different recurrence,
* it will be rescheduled with the current recurrence.
*
* @since 1.29.0
*
* @return void
*/
public static function schedule_event() {
$event = wp_get_scheduled_event( self::EVENT );
if ( $event && self::RECURRENCE === $event->schedule ) {
return;
}
wp_clear_scheduled_hook( self::EVENT );
wp_schedule_event( time(), self::RECURRENCE, self::EVENT );
}
/**
* Unschedule the cleanup event.
*
* @since 1.29.0
*
* @return void
*/
public static function unschedule_event() {
wp_clear_scheduled_hook( self::EVENT );
}
/**
* Set a custom batch size for this cleanup instance.
*
* @since 1.29.0
*
* @param int $batch_size A custom size of batch to process.
* @return void
*/
public function set_batch_size( int $batch_size ): void {
$this->batch_size = $batch_size;
}
/**
* Get the batch size to use.
*
* @return int
*/
protected function get_batch_size(): int {
return $this->batch_size ?? self::BATCH_LIMIT;
}
/**
* Get orphaned post IDs (posts in issues table but not in posts table).
*
* @since 1.29.0
*
* @return int[] Array of orphaned post IDs.
*/
public function get_orphaned_post_ids(): array {
global $wpdb;
$table_name = edac_get_valid_table_name( $wpdb->prefix . 'accessibility_checker' );
if ( ! $table_name ) {
return [];
}
$scannable_post_types = Settings::get_scannable_post_types();
if ( empty( $scannable_post_types ) ) {
// No scannable post types: treat all issues as orphaned for this site.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
return $wpdb->get_col(
$wpdb->prepare(
'SELECT DISTINCT postid FROM %i WHERE siteid = %d LIMIT %d',
$table_name,
get_current_blog_id(),
$this->get_batch_size()
)
);
}
// Build placeholders for each post type.
$placeholders = implode( ',', array_fill( 0, count( $scannable_post_types ), '%s' ) );
$sql = "SELECT DISTINCT t.postid
FROM %i AS t
LEFT JOIN {$wpdb->posts} AS p ON t.postid = p.ID
WHERE t.siteid = %d
AND (p.ID IS NULL OR p.post_type NOT IN ($placeholders))
LIMIT %d";
// Build arguments for prepare: table name, site id, ...post types, batch size.
$args = array_merge(
[ $sql, $table_name, get_current_blog_id() ],
$scannable_post_types,
[ $this->get_batch_size() ]
);
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching -- Dynamic placeholders for IN() are required for security and cannot be avoided in this context.
return $wpdb->get_col( call_user_func_array( [ $wpdb, 'prepare' ], $args ) );
}
/**
* Delete all issues for a given orphaned post ID.
*
* @since 1.29.0
*
* @param int $post_id The orphaned post ID.
* @return void
*/
public function delete_orphaned_post( int $post_id ) {
Purge_Post_Data::delete_post( $post_id );
}
/**
* Cleanup orphaned issues (batch process).
*
* @since 1.29.0
*
* @return int[] Array of deleted orphaned post IDs.
*/
public function run_cleanup(): array {
$orphaned = $this->get_orphaned_post_ids();
if ( empty( $orphaned ) ) {
return [];
}
foreach ( $orphaned as $post_id ) {
$this->delete_orphaned_post( (int) $post_id );
}
return $orphaned;
}
}