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

equalizedigital / accessibility-checker / 24673701338

20 Apr 2026 02:59PM UTC coverage: 60.51% (-0.008%) from 60.518%
24673701338

push

github

web-flow
Merge pull request #1580 from equalizedigital/mainwilliam/pro-670-make-the-cleanup-routine-for-orphaned-issues-run-twice-a-day--

Make the cleanup routine for orphaned issues run twice a day

0 of 6 new or added lines in 1 file covered. (0.0%)

4980 of 8230 relevant lines covered (60.51%)

4.81 hits per line

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

45.24
/admin/class-orphaned-issues-cleanup.php
1
<?php
2
/**
3
 * Scheduled cleanup of orphaned issues.
4
 *
5
 * @package Accessibility_Checker
6
 */
7

8
namespace EDAC\Admin;
9

10
if ( ! defined( 'ABSPATH' ) ) {
11
        exit;
12
}
13

14
/**
15
 * Class Orphaned_Issues_Cleanup
16
 *
17
 * @since 1.29.0
18
 */
19
class Orphaned_Issues_Cleanup {
20

21
        /**
22
         * Cron event name.
23
         *
24
         * @var string
25
         */
26
        const EVENT = 'edac_cleanup_orphaned_issues';
27

28
        /**
29
         * Max number of posts to cleanup per run.
30
         *
31
         * @var int
32
         */
33
        const BATCH_LIMIT = 50;
34

35
        /**
36
         * Batch size for cleanup (overrides BATCH_LIMIT if set).
37
         *
38
         * @var int|null
39
         */
40
        private ?int $batch_size = null;
41

42
        /**
43
         * Register hooks.
44
         *
45
         * @since 1.29.0
46
         *
47
         * @return void
48
         */
49
        public function init_hooks() {
50
                self::schedule_event();
×
51
                add_action( self::EVENT, [ $this, 'run_cleanup' ] );
×
52
        }
53

54
        /**
55
         * The recurrence interval for the cleanup event.
56
         *
57
         * @var string
58
         */
59
        const RECURRENCE = 'twicedaily';
60

61
        /**
62
         * Schedule the cleanup event.
63
         *
64
         * If the event is already scheduled with a different recurrence,
65
         * it will be rescheduled with the current recurrence.
66
         *
67
         * @since 1.29.0
68
         *
69
         * @return void
70
         */
71
        public static function schedule_event() {
NEW
72
                $event = wp_get_scheduled_event( self::EVENT );
×
73

NEW
74
                if ( $event && self::RECURRENCE === $event->schedule ) {
×
NEW
75
                        return;
×
76
                }
77

NEW
78
                wp_clear_scheduled_hook( self::EVENT );
×
NEW
79
                wp_schedule_event( time(), self::RECURRENCE, self::EVENT );
×
80
        }
81

82
        /**
83
         * Unschedule the cleanup event.
84
         *
85
         * @since 1.29.0
86
         *
87
         * @return void
88
         */
89
        public static function unschedule_event() {
NEW
90
                wp_clear_scheduled_hook( self::EVENT );
×
91
        }
92

93
        /**
94
         * Set a custom batch size for this cleanup instance.
95
         *
96
         * @since 1.29.0
97
         *
98
         * @param int $batch_size A custom size of batch to process.
99
         * @return void
100
         */
101
        public function set_batch_size( int $batch_size ): void {
102
                $this->batch_size = $batch_size;
2✔
103
        }
104

105
        /**
106
         * Get the batch size to use.
107
         *
108
         * @return int
109
         */
110
        protected function get_batch_size(): int {
111
                return $this->batch_size ?? self::BATCH_LIMIT;
14✔
112
        }
113

114
        /**
115
         * Get orphaned post IDs (posts in issues table but not in posts table).
116
         *
117
         * @since 1.29.0
118
         *
119
         * @return int[] Array of orphaned post IDs.
120
         */
121
        public function get_orphaned_post_ids(): array {
122
                global $wpdb;
14✔
123
                $table_name = edac_get_valid_table_name( $wpdb->prefix . 'accessibility_checker' );
14✔
124
                if ( ! $table_name ) {
14✔
125
                        return [];
×
126
                }
127

128
                $scannable_post_types = Settings::get_scannable_post_types();
14✔
129
                if ( empty( $scannable_post_types ) ) {
14✔
130
                        // No scannable post types: treat all issues as orphaned for this site.
131
                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
132
                        return $wpdb->get_col(
×
133
                                $wpdb->prepare(
×
134
                                        'SELECT DISTINCT postid FROM %i WHERE siteid = %d LIMIT %d',
×
135
                                        $table_name,
×
136
                                        get_current_blog_id(),
×
137
                                        $this->get_batch_size()
×
138
                                )
×
139
                        );
×
140
                }
141

142
                // Build placeholders for each post type.
143
                $placeholders = implode( ',', array_fill( 0, count( $scannable_post_types ), '%s' ) );
14✔
144
                $sql          = "SELECT DISTINCT t.postid
14✔
145
                                FROM %i AS t
146
                                LEFT JOIN {$wpdb->posts} AS p ON t.postid = p.ID
14✔
147
                                WHERE t.siteid = %d
148
                                AND (p.ID IS NULL OR p.post_type NOT IN ($placeholders))
14✔
149
                                LIMIT %d";
14✔
150

151
                // Build arguments for prepare: table name, site id, ...post types, batch size.
152
                $args = array_merge(
14✔
153
                        [ $sql, $table_name, get_current_blog_id() ],
14✔
154
                        $scannable_post_types,
14✔
155
                        [ $this->get_batch_size() ]
14✔
156
                );
14✔
157

158
                // 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.
159
                return $wpdb->get_col( call_user_func_array( [ $wpdb, 'prepare' ], $args ) );
14✔
160
        }
161

162
        /**
163
         * Delete all issues for a given orphaned post ID.
164
         *
165
         * @since 1.29.0
166
         *
167
         * @param int $post_id The orphaned post ID.
168
         */
169
        public function delete_orphaned_post( int $post_id ) {
170
                Purge_Post_Data::delete_post( $post_id );
8✔
171
        }
172

173
        /**
174
         * Cleanup orphaned issues (batch process).
175
         *
176
         * @since 1.29.0
177
         *
178
         * @return int[] Array of deleted orphaned post IDs.
179
         */
180
        public function run_cleanup(): array {
181
                $orphaned = $this->get_orphaned_post_ids();
×
182
                if ( empty( $orphaned ) ) {
×
183
                        return [];
×
184
                }
185
                foreach ( $orphaned as $post_id ) {
×
186
                        $this->delete_orphaned_post( (int) $post_id );
×
187
                }
188
                return $orphaned;
×
189
        }
190
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc