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

equalizedigital / accessibility-checker / 17205722517

25 Aug 2025 10:05AM UTC coverage: 60.808% (+3.6%) from 57.172%
17205722517

push

github

web-flow
Merge pull request #1206 from equalizedigital/william/no-ticket/enhance-endpoint-checks

Backport v1.30.1

11 of 22 new or added lines in 3 files covered. (50.0%)

9 existing lines in 2 files now uncovered.

4110 of 6759 relevant lines covered (60.81%)

3.53 hits per line

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

46.34
/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; // Exit if accessed directly.
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
         * Schedule the cleanup event.
56
         *
57
         * @since 1.29.0
58
         *
59
         * @return void
60
         */
61
        public static function schedule_event() {
62
                if ( ! wp_next_scheduled( self::EVENT ) ) {
×
63
                        wp_schedule_event( time(), 'daily', self::EVENT );
×
64
                }
65
        }
66

67
        /**
68
         * Unschedule the cleanup event.
69
         *
70
         * @since 1.29.0
71
         *
72
         * @return void
73
         */
74
        public static function unschedule_event() {
75
                $timestamp = wp_next_scheduled( self::EVENT );
×
76
                if ( $timestamp ) {
×
77
                        wp_unschedule_event( $timestamp, self::EVENT );
×
78
                }
79
        }
80

81
        /**
82
         * Set a custom batch size for this cleanup instance.
83
         *
84
         * @since 1.29.0
85
         *
86
         * @param int $batch_size A custom size of batch to process.
87
         * @return void
88
         */
89
        public function set_batch_size( int $batch_size ): void {
90
                $this->batch_size = $batch_size;
2✔
91
        }
92

93
        /**
94
         * Get the batch size to use.
95
         *
96
         * @return int
97
         */
98
        protected function get_batch_size(): int {
99
                return $this->batch_size ?? self::BATCH_LIMIT;
14✔
100
        }
101

102
        /**
103
         * Get orphaned post IDs (posts in issues table but not in posts table).
104
         *
105
         * @since 1.29.0
106
         *
107
         * @return int[] Array of orphaned post IDs.
108
         */
109
        public function get_orphaned_post_ids(): array {
110
                global $wpdb;
14✔
111
                $table_name = edac_get_valid_table_name( $wpdb->prefix . 'accessibility_checker' );
14✔
112
                if ( ! $table_name ) {
14✔
113
                        return [];
×
114
                }
115
                
116
                $scannable_post_types = Settings::get_scannable_post_types();
14✔
117
                if ( empty( $scannable_post_types ) ) {
14✔
118
                        // No scannable post types: treat all issues as orphaned for this site.
119
                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
UNCOV
120
                        return $wpdb->get_col(
×
UNCOV
121
                                $wpdb->prepare(
×
UNCOV
122
                                        'SELECT DISTINCT postid FROM %i WHERE siteid = %d LIMIT %d',
×
UNCOV
123
                                        $table_name,
×
UNCOV
124
                                        get_current_blog_id(),
×
UNCOV
125
                                        $this->get_batch_size()
×
UNCOV
126
                                )
×
UNCOV
127
                        );
×
128
                }
129

130
                // Build placeholders for each post type.
131
                $placeholders = implode( ',', array_fill( 0, count( $scannable_post_types ), '%s' ) );
14✔
132
                $sql          = "SELECT DISTINCT t.postid
14✔
133
                                FROM %i AS t
134
                                LEFT JOIN {$wpdb->posts} AS p ON t.postid = p.ID
14✔
135
                                WHERE t.siteid = %d
136
                                AND (p.ID IS NULL OR p.post_type NOT IN ($placeholders))
14✔
137
                                LIMIT %d";
14✔
138

139
                // Build arguments for prepare: table name, site id, ...post types, batch size.
140
                $args = array_merge(
14✔
141
                        [ $sql, $table_name, get_current_blog_id() ],
14✔
142
                        $scannable_post_types,
14✔
143
                        [ $this->get_batch_size() ]
14✔
144
                );
14✔
145

146
                // 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.
147
                return $wpdb->get_col( call_user_func_array( [ $wpdb, 'prepare' ], $args ) );
14✔
148
        }
149

150
        /**
151
         * Delete all issues for a given orphaned post ID.
152
         *
153
         * @since 1.29.0
154
         *
155
         * @param int $post_id The orphaned post ID.
156
         */
157
        public function delete_orphaned_post( int $post_id ) {
158
                Purge_Post_Data::delete_post( $post_id );
8✔
159
        }
160

161
        /**
162
         * Cleanup orphaned issues (batch process).
163
         *
164
         * @since 1.29.0
165
         *
166
         * @return int[] Array of deleted orphaned post IDs.
167
         */
168
        public function run_cleanup(): array {
169
                $orphaned = $this->get_orphaned_post_ids();
×
170
                if ( empty( $orphaned ) ) {
×
171
                        return [];
×
172
                }
173
                foreach ( $orphaned as $post_id ) {
×
174
                        $this->delete_orphaned_post( (int) $post_id );
×
175
                }
176
                return $orphaned;
×
177
        }
178
}
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