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

equalizedigital / accessibility-checker / 14624478010

23 Apr 2025 05:36PM UTC coverage: 25.609% (-0.5%) from 26.144%
14624478010

push

github

web-flow
Merge pull request #942 from equalizedigital/william/pro-33-convert-broken-aria-reference-rule-to-js

William/pro 33 convert broken aria reference rule to js

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

33 existing lines in 1 file now uncovered.

1681 of 6564 relevant lines covered (25.61%)

1.33 hits per line

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

52.63
/includes/validate.php
1
<?php
2
/**
3
 * Accessibility Checker pluign file.
4
 *
5
 * @package Accessibility_Checker
6
 */
7

8
use EDAC\Admin\Helpers;
9
use EDAC\Admin\Insert_Rule_Data;
10
use EDAC\Admin\Purge_Post_Data;
11

12
/**
13
 * Oxygen Builder on save
14
 *
15
 * @since 1.2.0
16
 *
17
 * @param int    $meta_id    The ID of the metadata entry in the database.
18
 * @param int    $post_id    The ID of the post being saved.
19
 * @param string $meta_key   The key of the metadata being saved.
20
 * @param mixed  $meta_value The value of the metadata being saved.
21
 *
22
 * @return void
23
 */
24
function edac_oxygen_builder_save_post( $meta_id, $post_id, $meta_key, $meta_value ) { // phpcs:ignore -- This function is a hook and the parameters are required.
25
        if ( 'ct_builder_shortcodes' === $meta_key ) {
×
26

27
                $post = get_post( $post_id, OBJECT );
×
28
                edac_validate( $post_id, $post, $action = 'save' );
×
29

30
        }
31
}
32

33
/**
34
 * Check if current post has been checked, if not check on page load
35
 *
36
 * @return void
37
 */
38
function edac_post_on_load() {
39
        global $pagenow;
×
40
        if ( 'post.php' === $pagenow ) {
×
41
                global $post;
×
42
                $checked = get_post_meta( $post->ID, '_edac_post_checked', true );
×
43
                if ( false === (bool) $checked ) {
×
44
                        edac_validate( $post->ID, $post, $action = 'load' );
×
45
                }
46
        }
47
}
48

49
/**
50
 * Post on save
51
 *
52
 * @param int    $post_ID The ID of the post being saved.
53
 * @param object $post    The post object being saved.
54
 * @param bool   $update  Whether this is an existing post being updated.
55
 *
56
 * @modified 1.10.0 to add a return when post_status is trash.
57
 *
58
 * @return void
59
 */
60
function edac_save_post( $post_ID, $post, $update ) {
61
        // check post type.
62
        $post_types = get_option( 'edac_post_types' );
34✔
63
        if ( is_array( $post_types ) && ! in_array( $post->post_type, $post_types, true ) ) {
34✔
64
                return;
×
65
        }
66

67
        // prevents first past of save_post due to meta boxes on post editor in gutenberg.
68
        if ( empty( $_POST ) ) {
34✔
69
                return;
26✔
70
        }
71

72
        // ignore revisions.
73
        if ( wp_is_post_revision( $post_ID ) ) {
8✔
74
                return;
2✔
75
        }
76

77
        // ignore autosaves.
78
        if ( wp_is_post_autosave( $post_ID ) ) {
8✔
79
                return;
×
80
        }
81

82
        // check if update.
83
        if ( ! $update ) {
8✔
84
                return;
8✔
85
        }
86

87
        // handle the case when the custom post is quick edited.
88
        if ( isset( $_POST['_inline_edit'] ) ) {
2✔
89
                $inline_edit = sanitize_text_field( $_POST['_inline_edit'] );
×
90
                if ( wp_verify_nonce( $inline_edit, 'inlineeditnonce' ) ) {
×
91
                        return;
×
92
                }
93
        }
94

95
        // Post in, or going to, trash.
96
        if ( 'trash' === $post->post_status ) {
2✔
97
                // Gutenberg does not fire the `wp_trash_post` action when moving posts to the
98
                // trash. Instead it uses `rest_delete_{$post_type}` which passes a different shape
99
                // so instead of hooking in there for every post type supported the data gets
100
                // purged here instead which produces the same result.
101
                Purge_Post_Data::delete_post( $post_ID );
2✔
102
                return;
2✔
103
        }
104

105
        edac_validate( $post_ID, $post, $action = 'save' );
×
106
}
107

108
/**
109
 * Post on save
110
 *
111
 * @param int    $post_ID The ID of the post being saved.
112
 * @param object $post    The post object being saved.
113
 * @param bool   $action  Whether this is an existing post being updated.
114
 *
115
 * @return void
116
 */
117
function edac_validate( $post_ID, $post, $action ) {
118
        // check post type.
119
        $post_types = get_option( 'edac_post_types' );
2✔
120
        if ( is_array( $post_types ) && ! in_array( $post->post_type, $post_types, true ) ) {
2✔
121
                return;
×
122
        }
123

124
        // Make a new post object to avoid changing the original (which could come from global $post).
125
        $block_parsed_post = new \WP_Post( (object) $post );
2✔
126

127
        /**
128
         * Allows to hook in before the validation process starts for a post.
129
         *
130
         * @since 1.4.0
131
         *
132
         * @param int    $post_ID The ID of the post being saved.
133
         * @param string $action  The action being performed.
134
         */
135
        do_action( 'edac_before_validate', $post_ID, $action );
2✔
136

137
        // Ensure dynamic blocks and oEmbeds are processed before validation.
138
        // Use the_content filter to ensure do_block allows wpautop to be handled correctly for custom blocks. See https://github.com/equalizedigital/accessibility-checker/pull/862.
139
        $block_parsed_post->post_content = apply_filters( 'the_content', $block_parsed_post->post_content );
2✔
140

141
        // apply filters to content.
142
        $content = edac_get_content( $block_parsed_post );
2✔
143

144
        /**
145
         * Allows to hook in after the content has been retrieved for a post.
146
         *
147
         * @since 1.4.0
148
         *
149
         * @param int    $post_ID The ID of the post being saved.
150
         * @param array  $content The content being retrieved.
151
         * @param string $action  The action being performed.
152
         */
153
        do_action( 'edac_after_get_content', $post_ID, $content, $action );
2✔
154

155
        if ( ! $content['html'] ) {
2✔
156
                // The woocommerce checkout page will always be a redirect when it has no items. The redirect
157
                // will cause the content to be empty.
158
                // TEMPORARY FIX: Just return without setting this as a password protected page. In future we
159
                // will need to fix this properly by adding a product to the cart before checking.
160
                if ( edac_check_if_post_id_is_woocommerce_checkout_page( $post_ID ) ) {
×
161
                        return;
×
162
                }
163

164
                update_option( 'edac_password_protected', true );
×
165
                return;
×
166
        } else {
167
                update_option( 'edac_password_protected', false );
2✔
168
        }
169
        delete_option( 'edac_password_protected' );
2✔
170

171
        // set record check flag on previous error records.
172
        edac_remove_corrected_posts( $post_ID, $block_parsed_post->post_type, $pre = 1, 'php' );
2✔
173

174
        // check and validate content.
175
        $rules = edac_register_rules();
2✔
176
        if ( EDAC_DEBUG === true ) {
2✔
177
                $rule_performance_results = [];
×
178
                $all_rules_process_time   = microtime( true );
×
179
        }
180
        if ( $rules ) {
2✔
181
                foreach ( $rules as $rule ) {
2✔
182

183
                        // Run php-base rules.
184
                        if ( ( array_key_exists( 'ruleset', $rule ) && 'php' === $rule['ruleset'] ) ||
2✔
185
                                ( ! array_key_exists( 'ruleset', $rule ) && $rule['slug'] )
2✔
186
                        ) {
187
                                /**
188
                                 * Allows to hook in before the rule has been run on the content.
189
                                 *
190
                                 * @since 1.4.0
191
                                 *
192
                                 * @param int    $post_ID The ID of the post being saved.
193
                                 * @param array  $rule    The rule being validated against the content.
194
                                 * @param string $action  The action being performed.
195
                                 */
UNCOV
196
                                do_action( 'edac_before_rule', $post_ID, $rule, $action );
×
UNCOV
197
                                if ( EDAC_DEBUG === true ) {
×
198
                                        $rule_process_time = microtime( true );
×
199
                                }
UNCOV
200
                                $errors = call_user_func( 'edac_rule_' . $rule['slug'], $content, $block_parsed_post );
×
201

UNCOV
202
                                if ( $errors && is_array( $errors ) ) {
×
203
                                        /**
204
                                         * Allows to hook in after the rule has been and get the errors list.
205
                                         *
206
                                         * @since 1.4.0
207
                                         *
208
                                         * @param int    $post_ID The ID of the post being saved.
209
                                         * @param array  $rule    The rule being validated against the content.
210
                                         * @param array  $errors  The errors list generated by this rule from the content.
211
                                         * @param string $action  The action being performed.
212
                                         */
213
                                        do_action( 'edac_rule_errors', $post_ID, $rule, $errors, $action );
×
214
                                        foreach ( $errors as $error ) {
×
215
                                                ( new Insert_Rule_Data() )->insert( $block_parsed_post, $rule['slug'], $rule['rule_type'], $object = $error );
×
216
                                        }
217
                                }
UNCOV
218
                                if ( EDAC_DEBUG === true ) {
×
219
                                        $time_elapsed_secs                         = microtime( true ) - $rule_process_time;
×
220
                                        $rule_performance_results[ $rule['slug'] ] = $time_elapsed_secs;
×
221
                                }
222

223
                                /**
224
                                 * Allows to hook in after the rule has been run on the content.
225
                                 *
226
                                 * @since 1.4.0
227
                                 *
228
                                 * @param int    $post_ID The ID of the post being saved.
229
                                 * @param array  $rule    The rule being validated against the content.
230
                                 * @param string $action  The action being performed.
231
                                 */
UNCOV
232
                                do_action( 'edac_after_rule', $post_ID, $rule, $action );
×
233
                        }
234
                }
235
                if ( EDAC_DEBUG === true ) {
2✔
236
                        edacp_log( $rule_performance_results );
×
237
                }
238
        }
239
        if ( EDAC_DEBUG === true ) {
2✔
240
                $time_elapsed_secs = microtime( true ) - $all_rules_process_time;
×
241
                edacp_log( 'rules validate time: ' . $time_elapsed_secs );
×
242
        }
243

244
        // remove corrected records.
245
        edac_remove_corrected_posts( $post_ID, $block_parsed_post->post_type, $pre = 2, 'php' );
2✔
246

247
        // set post meta checked.
248
        update_post_meta( $post_ID, '_edac_post_checked', true );
2✔
249

250
        /**
251
         * Allows to hook in after the validation process has completed for a post.
252
         *
253
         * @since 1.4.0
254
         *
255
         * @param int    $post_ID The ID of the post being saved.
256
         * @param string $action  The action being performed.
257
         */
258
        do_action( 'edac_after_validate', $post_ID, $action );
2✔
259
}
260

261
/**
262
 * Remove corrected posts
263
 *
264
 * @param int    $post_ID The ID of the post.
265
 * @param string $type    The type of the post.
266
 * @param int    $pre     The flag indicating the removal stage (1 for before validation php based rules, 2 for after validation).
267
 * @param string $ruleset    The type of the ruleset to correct (php or js).
268
 *
269
 * @return void
270
 */
271
function edac_remove_corrected_posts( $post_ID, $type, $pre = 1, $ruleset = 'php' ) {
272
        global $wpdb;
2✔
273

274
        $rules          = edac_register_rules();
2✔
275
        $js_rule_slugs  = [];
2✔
276
        $php_rule_slugs = [];
2✔
277
        // Separate the JS rules and the PHP rules.
278
        foreach ( $rules as $rule ) {
2✔
279
                if ( isset( $rule['ruleset'] ) && 'js' === $rule['ruleset'] ) {
2✔
280
                        $js_rule_slugs[] = $rule['slug'];
2✔
281
                } else {
UNCOV
282
                        $php_rule_slugs[] = $rule['slug'];
×
283
                }
284
        }
285
        // Operate only on the slugs for the ruleset we are checking in this call.
286
        $rule_slugs = 'js' === $ruleset ? $js_rule_slugs : $php_rule_slugs;
2✔
287
        if ( 0 === count( $rule_slugs ) ) {
2✔
288
                return;
2✔
289
        }
290

UNCOV
291
        if ( 1 === $pre ) {
×
292

293
                // Set record flag before validating content.
294
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Using direct query for adding data to database, caching not required for one time operation.
UNCOV
295
                $wpdb->query(
×
UNCOV
296
                        $wpdb->prepare(
×
UNCOV
297
                                sprintf(
×
UNCOV
298
                                        "UPDATE {$wpdb->prefix}accessibility_checker SET recordcheck = %%d WHERE siteid = %%d and postid = %%d and type = %%s AND rule IN (%s)",
×
UNCOV
299
                                        implode( ',', array_fill( 0, count( $rule_slugs ), '%s' ) )
×
UNCOV
300
                                ),
×
UNCOV
301
                                array_merge(
×
UNCOV
302
                                        [ 0, get_current_blog_id(), $post_ID, $type ],
×
UNCOV
303
                                        $rule_slugs
×
UNCOV
304
                                )
×
UNCOV
305
                        )
×
UNCOV
306
                );
×
307

UNCOV
308
        } elseif ( 2 === $pre ) {
×
309
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Using direct query for adding data to database, caching not required for one time operation.
UNCOV
310
                $wpdb->query(
×
UNCOV
311
                        $wpdb->prepare(
×
UNCOV
312
                                sprintf(
×
UNCOV
313
                                        "DELETE FROM {$wpdb->prefix}accessibility_checker WHERE siteid = %%d and postid = %%d and type = %%s and recordcheck = %%d AND rule IN (%s)",
×
UNCOV
314
                                        implode( ',', array_fill( 0, count( $rule_slugs ), '%s' ) )
×
UNCOV
315
                                ),
×
UNCOV
316
                                array_merge(
×
UNCOV
317
                                        [ get_current_blog_id(), $post_ID, $type, 0 ],
×
UNCOV
318
                                        $rule_slugs
×
UNCOV
319
                                )
×
UNCOV
320
                        )
×
UNCOV
321
                );
×
322
        }
323
}
324

325
/**
326
 * Get content
327
 *
328
 * @param WP_Post $post The post object.
329
 * @return simple_html_dom|bool Returns the parsed HTML content or false on failure.
330
 */
331
function edac_get_content( $post ) {
332
        $content         = [];
2✔
333
        $content['html'] = false;
2✔
334

335
        $context              = '';
2✔
336
        $context_opts         = [];
2✔
337
        $default_context_opts = [
2✔
338
                // See: https://www.php.net/manual/en/context.http.php.
339
                'http' => [
2✔
340
                        'user_agent'      => 'PHP Accessibility Checker',
2✔
341
                        'follow_location' => false,
2✔
342
                ],
2✔
343
        ];
2✔
344

345
        $username = get_option( 'edacp_authorization_username' );
2✔
346
        $password = get_option( 'edacp_authorization_password' );
2✔
347

348
        // Check if server returns that the domain IP is a local/loopback address.
349
        // If so then file_get_contents calls from this server to this domain will
350
        // likely not be able to verify ssl. So we need to use a context that
351
        // does not try to validate the ssl, otherwise file_get_contents will fail.
352
        // See: https://www.php.net/manual/en/context.ssl.php .
353

354
        $no_verify_ssl = false; // Verify by default.
2✔
355

356
        $is_local_loopback = get_option( 'edac_local_loopback', null );
2✔
357

358
        if ( null === $is_local_loopback ) {
2✔
359

360
                $parsed_url = wp_parse_url( home_url() );
2✔
361

362
                if ( isset( $parsed_url['host'] ) ) {
2✔
363
                        $is_local_loopback = Helpers::is_domain_loopback( $parsed_url['host'] );
2✔
364
                        // can only be bool.
365
                        update_option( 'edac_local_loopback', $is_local_loopback );
2✔
366
                }
367
        }
368

369
        /**
370
         * Indicates file_get_html should not verify SSL.
371
         *
372
         * For site security it is not recommended to use this filter in production.
373
         *
374
         * @since 1.4.0
375
         *
376
         * @param bool $no_verify_ssl True if verify SSL should be disabled (as it must be in loopback connections), false if not.
377
         */
378
        $no_verify_ssl = apply_filters( 'edac_no_verify_ssl', $is_local_loopback );
2✔
379

380
        if ( $no_verify_ssl ) {
2✔
381
                $context_opts['ssl'] = [
×
382
                        'verify_peer'      => false,
×
383
                        'verify_peer_name' => false,
×
384
                ];
×
385
        }
386

387
        // http authorization.
388
        if ( is_plugin_active( 'accessibility-checker-pro/accessibility-checker-pro.php' ) && EDAC_KEY_VALID === true && $username && $password ) {
2✔
389
                // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- This is a valid use case for base64_encode.
390
                $context_opts['http']['header'] = 'Authorization: Basic ' . base64_encode( "$username:$password" );
×
391
        }
392

393
        $parsed_url      = wp_parse_url( get_the_permalink( $post->ID ) );
2✔
394
        $parsed_site_url = wp_parse_url( get_site_url() );
2✔
395

396
        // sanity check: confirm the permalink url is on this site.
397
        if ( $parsed_url['host'] === $parsed_site_url['host'] ) {
2✔
398

399
                $url = ( array_key_exists( 'query', $parsed_url ) && $parsed_url['query'] )
2✔
400
                        ? get_the_permalink( $post->ID ) . '&edac_cache=' . time()  // Permalink structure using a querystring.
2✔
401
                        : get_the_permalink( $post->ID ) . '?edac_cache=' . time(); // Permalink structure not using a querystring.
2✔
402

403
                // set token if post status is 'draft' or 'pending'.
404
                if ( 'draft' === $post->post_status || 'pending' === $post->post_status ) {
2✔
405

406
                        // Generate a token that is valid for a short period of time.
407
                        $token = edac_generate_nonce( 'draft-or-pending-status', 120 );
×
408

409
                        // Add the token to the URL.
410
                        $url = add_query_arg( 'edac_token', $token, $url );
×
411

412
                }
413

414
                try {
415

416
                        // setup the context for the request.
417
                        // note - if follow_location => false, permalinks that redirect (both offsite and on).
418
                        // will not be followed, so $content['html] will be false.
419
                        $merged_context_opts = array_merge_recursive( $default_context_opts, $context_opts );
2✔
420
                        $context             = stream_context_create( $merged_context_opts );
2✔
421

422
                        $dom             = file_get_html( $url, false, $context );
2✔
423
                        $content['html'] = edac_remove_elements(
2✔
424
                                $dom,
2✔
425
                                [
2✔
426
                                        '#wpadminbar',            // wp admin bar.
2✔
427
                                        '.edac-highlight-panel',  // frontend highlighter.
2✔
428
                                        '#query-monitor-main',    // query-monitor.
2✔
429
                                        '#qm-icon-container',     // query-monitor.
2✔
430
                                ]
2✔
431
                        );
2✔
432

433
                        // Write density data to post meta.
434
                        if ( $content['html'] ) {
2✔
435

436
                                $page_html         = $content['html']->save();
2✔
437
                                $body_density_data = edac_get_body_density_data( $page_html );
2✔
438

439
                                if ( false !== $body_density_data ) {
2✔
440
                                        update_post_meta(
2✔
441
                                                $post->ID,
2✔
442
                                                '_edac_density_data',
2✔
443
                                                array_map( 'intval', $body_density_data )
2✔
444
                                        );
2✔
445
                                } else {
446
                                        delete_post_meta( $post->ID, '_edac_density_data' );
2✔
447
                                }
448
                        }
449
                } catch ( Exception $e ) {
×
450
                        update_post_meta( $post->ID, '_edac_density_data', [ 0, 0 ] );
×
451

452
                        $content['html'] = false;
2✔
453
                }
454
        } else {
455
                update_post_meta( $post->ID, '_edac_density_data', [ 0, 0 ] );
×
456

457
                $content['html'] = false;
×
458
        }
459

460
        // check for restricted access plugin.
461
        if ( ! is_plugin_active( 'accessibility-checker-pro/accessibility-checker-pro.php' ) && is_plugin_active( 'restricted-site-access/restricted_site_access.php' ) ) {
2✔
462
                $content['html'] = false;
×
463
        }
464

465
        // get styles and parse.
466
        if ( $content['html'] ) {
2✔
467

468
                $content['css'] = '';
2✔
469

470
                // css from style tags.
471
                $style_tag_styles = $content['html']->find( 'style' );
2✔
472
                if ( $style_tag_styles ) {
2✔
473
                        foreach ( $style_tag_styles as $style ) {
2✔
474
                                $content['css'] .= $style->innertext;
2✔
475
                        }
476
                }
477

478
                // css from files.
479
                $style_files = $content['html']->find( 'link[rel="stylesheet"]' );
2✔
480
                foreach ( $style_files as $stylesheet ) {
2✔
481
                        $stylesheet_url = $stylesheet->href;
×
482

483
                        $css_args['edac_cache'] = time();
×
484

485
                        if ( isset( $token ) ) {
×
486
                                $css_args['edac_token'] = $token;
×
487

488
                        }
489

490
                        // Add the query vars to the URL.
491
                        $stylesheet_url = add_query_arg(
×
492
                                $css_args,
×
493
                                $stylesheet_url
×
494
                        );
×
495

496
                        $response = wp_remote_get( $stylesheet_url ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get -- This is a valid use case for wp_remote_get as plugin can be used on environments other than WPVIP.
×
497

498
                        if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
×
499
                                $styles          = wp_remote_retrieve_body( $response );
×
500
                                $content['css'] .= $styles;
×
501
                        }
502
                }
503

504
                $content['css_parsed'] = edac_parse_css( $content['css'] );
2✔
505
        }
506

507
        return $content;
2✔
508
}
509

510
/**
511
 * Show draft posts.
512
 *
513
 * This function alters the main query on the front-end to show draft and pending posts when a specific
514
 * token is present in the URL. This token is stored as an option in the database and is regenerated every time
515
 * it's used, to prevent unauthorized access to drafts and pending posts.
516
 *
517
 * @param WP_Query $query The WP_Query instance (passed by reference).
518
 */
519
function edac_show_draft_posts( $query ) {
520

521
        // Do not run if it's not the main query.
522
        if ( ! $query->is_main_query() ) {
34✔
523
                return;
34✔
524
        }
525

526
        // Do not run on admin pages, feeds, REST API or AJAX calls.
527
        if ( is_admin() || is_feed() || wp_doing_ajax() || ( function_exists( 'rest_doing_request' ) && rest_doing_request() ) ) {
×
528
                return;
×
529
        }
530

531
        // Do not run if the query variable 'edac_cache' is not set.
532
        // phpcs:ignore WordPress.Security.NonceVerification
533
        $url_cache = isset( $_GET['edac_cache'] ) ? sanitize_text_field( $_GET['edac_cache'] ) : '';
×
534
        if ( ! $url_cache ) {
×
535
                return;
×
536
        }
537

538
        // Retrieve the token from the URL.
539
        // phpcs:ignore WordPress.Security.NonceVerification
540
        $url_token = isset( $_GET['edac_token'] ) ? sanitize_text_field( $_GET['edac_token'] ) : false;
×
541

542
        // If the token is not set we do nothing and return early.
543
        if ( false === $url_token ) {
×
544
                return;
×
545
        }
546

547
        // If the passed token is no longer valid, we do nothing and return early.
548
        if ( false === edac_is_valid_nonce( 'draft-or-pending-status', $url_token ) ) {
×
549
                return;
×
550
        }
551

552
        // If we've reached this point, alter the query to include 'publish', 'draft', and 'pending' posts.
553
        $query->set( 'post_status', [ 'publish', 'draft', 'pending' ] );
×
554
}
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