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

equalizedigital / accessibility-checker / 16148592723

08 Jul 2025 04:11PM UTC coverage: 28.87% (-0.05%) from 28.915%
16148592723

push

github

web-flow
Merge pull request #1028 from equalizedigital/steve/try/landmark-location

Added Landmark Location

30 of 50 new or added lines in 3 files covered. (60.0%)

1 existing line in 1 file now uncovered.

1531 of 5303 relevant lines covered (28.87%)

1.65 hits per line

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

0.0
/includes/classes/class-rest-api.php
1
<?php
2
/**
3
 * Class file for REST api
4
 *
5
 * @package Accessibility_Checker
6
 */
7

8
namespace EDAC\Inc;
9

10
use EDAC\Admin\Helpers;
11
use EDAC\Admin\Insert_Rule_Data;
12
use EDAC\Admin\Scans_Stats;
13
use EDAC\Admin\Settings;
14
use EDAC\Admin\Purge_Post_Data;
15

16
/**
17
 * Class that initializes and handles the REST api
18
 */
19
class REST_Api {
20

21

22
        /**
23
         * Constructor
24
         */
25
        public function __construct() {
26
        }
×
27

28

29
        /**
30
         * Add the class the hooks.
31
         */
32
        public function init_hooks() {
33
                add_action( 'init', [ $this, 'init_rest_routes' ] );
×
34
                add_filter( 'edac_filter_js_violation_html', [ $this, 'filter_js_validation_html' ], 10, 3 );
×
35
        }
36

37

38
        /**
39
         * Add the rest routes.
40
         *
41
         * @return void
42
         */
43
        public function init_rest_routes() {
44

45
                $ns      = 'accessibility-checker/';
×
46
                $version = 'v1';
×
47

48
                add_action(
×
49
                        'rest_api_init',
×
50
                        function () use ( $ns, $version ) {
×
51
                                register_rest_route(
×
52
                                        $ns . $version,
×
53
                                        '/test',
×
54
                                        [
×
55
                                                'methods'             => [ 'GET', 'POST' ],
×
56
                                                'callback'            => function () {
×
57
                                                        $messages          = [];
×
58
                                                        $messages['time']  = time();
×
59
                                                        $messages['perms'] = current_user_can( 'edit_posts' );
×
60

61
                                                        return new \WP_REST_Response( [ 'messages' => $messages ], 200 );
×
62
                                                },
×
63
                                                'permission_callback' => function () {
×
64
                                                        return true;
×
65
                                                },
×
66
                                        ]
×
67
                                );
×
68
                        }
×
69
                );
×
70

71
                add_action(
×
72
                        'rest_api_init',
×
73
                        function () use ( $ns, $version ) {
×
74
                                register_rest_route(
×
75
                                        $ns . $version,
×
76
                                        '/post-scan-results/(?P<id>\d+)',
×
77
                                        [
×
78
                                                'methods'             => 'POST',
×
79
                                                'callback'            => [ $this, 'set_post_scan_results' ],
×
80
                                                'args'                => [
×
81
                                                        'id' => [
×
82
                                                                'validate_callback' => function ( $param ) {
×
83
                                                                        return is_numeric( $param );
×
84
                                                                },
×
85
                                                        ],
×
86
                                                ],
×
87
                                                'permission_callback' => function () {
×
88
                                                        return current_user_can( 'edit_posts' );
×
89
                                                },
×
90
                                        ]
×
91
                                );
×
92
                        }
×
93
                );
×
94

95
                add_action(
×
96
                        'rest_api_init',
×
97
                        function () use ( $ns, $version ) {
×
98
                                register_rest_route(
×
99
                                        $ns . $version,
×
100
                                        '/scans-stats',
×
101
                                        [
×
102
                                                'methods'             => 'GET',
×
103
                                                'callback'            => [ $this, 'get_scans_stats' ],
×
104
                                                'permission_callback' => function () {
×
105
                                                        return current_user_can( 'read' ); // able to access the admin dashboard.
×
106
                                                },
×
107
                                        ]
×
108
                                );
×
109
                        }
×
110
                );
×
111

112
                add_action(
×
113
                        'rest_api_init',
×
114
                        function () use ( $ns, $version ) {
×
115
                                register_rest_route(
×
116
                                        $ns . $version,
×
117
                                        '/clear-cached-scans-stats',
×
118
                                        [
×
119
                                                'methods'             => 'POST',
×
120
                                                'callback'            => [ $this, 'clear_cached_scans_stats' ],
×
121
                                                'permission_callback' => function () {
×
122
                                                        return current_user_can( 'read' ); // able to access the admin dashboard.
×
123
                                                },
×
124
                                        ]
×
125
                                );
×
126
                        }
×
127
                );
×
128

129
                add_action(
×
130
                        'rest_api_init',
×
131
                        function () use ( $ns, $version ) {
×
132
                                register_rest_route(
×
133
                                        $ns . $version,
×
134
                                        '/scans-stats-by-post-type/(?P<slug>[a-zA-Z0-9_-]+)',
×
135
                                        [
×
136
                                                'methods'             => 'GET',
×
137
                                                'callback'            => [ $this, 'get_scans_stats_by_post_type' ],
×
138
                                                'permission_callback' => function () {
×
139
                                                        return current_user_can( 'read' ); // able to access the admin dashboard.
×
140
                                                },
×
141
                                        ]
×
142
                                );
×
143
                        }
×
144
                );
×
145

146
                add_action(
×
147
                        'rest_api_init',
×
148
                        function () use ( $ns, $version ) {
×
149
                                register_rest_route(
×
150
                                        $ns . $version,
×
151
                                        '/scans-stats-by-post-types',
×
152
                                        [
×
153
                                                'methods'             => 'GET',
×
154
                                                'callback'            => [ $this, 'get_scans_stats_by_post_types' ],
×
155
                                                'permission_callback' => function () {
×
156
                                                        return current_user_can( 'read' ); // able to access the admin dashboard.
×
157
                                                },
×
158
                                        ]
×
159
                                );
×
160
                        }
×
161
                );
×
162

163
                add_action(
×
164
                        'rest_api_init',
×
165
                        function () use ( $ns, $version ) {
×
166
                                register_rest_route(
×
167
                                        $ns . $version,
×
168
                                        '/clear-issues/(?P<id>\d+)',
×
169
                                        [
×
170
                                                'methods'             => 'POST',
×
171
                                                'callback'            => [ $this, 'clear_issues_for_post' ],
×
172
                                                'args'                => [
×
173
                                                        'id' => [
×
174
                                                                'validate_callback' => function ( $param ) {
×
175
                                                                        return is_numeric( $param );
×
176
                                                                },
×
177
                                                        ],
×
178
                                                ],
×
179
                                                'permission_callback' => function () {
×
180
                                                        return current_user_can( 'edit_posts' );
×
181
                                                },
×
182
                                        ]
×
183
                                );
×
184
                        }
×
185
                );
×
186

187
                // Exposes the scan summary data.
188
                add_action(
×
189
                        'rest_api_init',
×
190
                        function () use ( $ns, $version ) {
×
191
                                register_rest_route(
×
192
                                        $ns . $version,
×
193
                                        '/site-summary',
×
194
                                        [
×
195
                                                'methods'             => 'GET',
×
196
                                                'callback'            => [ $this, 'get_site_summary' ],
×
197
                                                'permission_callback' => function () {
×
198
                                                        return current_user_can( 'edit_posts' );
×
199
                                                },
×
200
                                        ]
×
201
                                );
×
202
                        }
×
203
                );
×
204
        }
205

206
        /**
207
         * REST handler to clear issues results for a given post ID.
208
         *
209
         * @param WP_REST_Request $request  The request passed from the REST call.
210
         *
211
         * @return \WP_REST_Response
212
         */
213
        public function clear_issues_for_post( $request ) {
214

215
                if ( ! isset( $request['id'] ) ) {
×
216
                        return new \WP_REST_Response( [ 'message' => 'The ID is required to be passed.' ], 400 );
×
217
                }
218

219
                $json    = $request->get_json_params();
×
220
                $post_id = (int) $request['id'];
×
221
                if ( ! isset( $json['skip_post_exists_check'] ) ) {
×
222
                        $post = get_post( $post_id );
×
223
                        if ( ! is_object( $post ) ) {
×
224
                                return new \WP_REST_Response( [ 'message' => 'The post is not valid.' ], 400 );
×
225
                        }
226

227
                        $post_type  = get_post_type( $post );
×
228
                        $post_types = Helpers::get_option_as_array( 'edac_post_types' );
×
229
                        if ( empty( $post_types ) || ! in_array( $post_type, $post_types, true ) ) {
×
230
                                return new \WP_REST_Response( [ 'message' => 'The post type is not set to be scanned.' ], 400 );
×
231
                        }
232
                }
233

234
                // if flush is set then clear the issues for that ID.
235
                if ( isset( $json['flush'] ) ) {
×
236
                        // purge the issues for this post.
237
                        Purge_Post_Data::delete_post( $post_id );
×
238
                }
239

240
                return new \WP_REST_Response(
×
241
                        [
×
242
                                'success' => true,
×
243
                                'flushed' => isset( $json['flush'] ),
×
244
                                'id'      => $post_id,
×
245
                        ]
×
246
                );
×
247
        }
248

249

250
        /**
251
         * Filter the html of the js validation violation.
252
         *
253
         * This can be used to store additional data in the html of the violation.
254
         *
255
         * @since 1.13.0
256
         * @param string $html      The html of the violation.
257
         * @param string $rule_id   The id of the rule.
258
         * @param array  $violation The violation data.
259
         *
260
         * @return string
261
         */
262
        public function filter_js_validation_html( string $html, string $rule_id, array $violation ): string {
263
                // Add the selector to the violation message as empty paragraphs are almost always
264
                // duplicate html fragments. Adding the selector makes it unique, so it can be saved.
265
                if ( 'empty_paragraph_tag' === $rule_id ) {
×
266
                        $html .= $violation['selector'][0]
×
267
                                ? '// {{ ' . $violation['selector'][0] . ' }}'
×
268
                                : '';
×
269
                }
270

271
                // Use just the opening <html> and closing </html> tag, prevents storing entire page as the affected code.
272
                if ( 'html-has-lang' === $rule_id || 'document-title' === $rule_id ) {
×
273
                        $html = preg_replace( '/^.*(<html.*?>).*(<\/html>).*$/s', '$1...$2', $html );
×
274

275
                }
276
                return $html;
×
277
        }
278

279
        /**
280
         * REST handler that saves to the DB a list of js rule violations for a post.
281
         *
282
         * @param WP_REST_Request $request  The request passed from the REST call.
283
         *
284
         * @return \WP_REST_Response
285
         */
286
        public function set_post_scan_results( $request ) {
287

288
                if ( ! isset( $request['violations'] ) ) {
×
289
                        return new \WP_REST_Response( [ 'message' => 'A required parameter is missing.' ], 400 );
×
290
                }
291

292
                $post_id = (int) $request['id'];
×
293
                $post    = get_post( $post_id );
×
294
                if ( ! is_object( $post ) ) {
×
295

296
                        return new \WP_REST_Response( [ 'message' => 'The post is not valid.' ], 400 );
×
297
                }
298

299
                $post_type  = get_post_type( $post );
×
300
                $post_types = Helpers::get_option_as_array( 'edac_post_types' );
×
301
                if ( empty( $post_types ) || ! in_array( $post_type, $post_types, true ) ) {
×
302

303
                        return new \WP_REST_Response( [ 'message' => 'The post type is not set to be scanned.' ], 400 );
×
304

305
                }
306

307
                //phpcs:ignore Generic.Commenting.Todo.TaskFound
308
                // TODO: setup a rules class for loading/filtering rules.
309
                $rules             = edac_register_rules();
×
310
                $js_rule_ids       = [];
×
311
                $combined_rule_ids = [];
×
312
                foreach ( $rules as $rule ) {
×
313
                        if ( array_key_exists( 'ruleset', $rule ) && 'js' === $rule['ruleset'] ) {
×
314
                                $js_rule_ids[] = $rule['slug'];
×
315

316
                                // Some rules can be a grouping of other checks with different ids. This tracks those combined check IDs for later mapping.
317
                                if ( array_key_exists( 'combines', $rule ) && ! empty( $rule['combines'] ) ) {
×
318
                                        foreach ( $rule['combines'] as $combine_rule_id ) {
×
319
                                                $combined_rule_ids[ $combine_rule_id ] = $rule['slug'];
×
320
                                        }
321
                                }
322
                        }
323
                }
324

325
                try {
326

327
                        /**
328
                         * Fires before the validation process starts.
329
                         *
330
                         * This is only running in the JS check context.
331
                         *
332
                         * @since 1.5.0
333
                         *
334
                         * @param int    $post_id The post ID.
335
                         * @param string $type    The type of validation which is always 'js' in this path.
336
                         */
337
                        do_action( 'edac_before_validate', $post_id, 'js' );
×
338

339
                        $violations = $request['violations'];
×
340

341
                        // set record check flag on previous error records.
342
                        edac_remove_corrected_posts( $post_id, $post->post_type, $pre = 1, 'js' );
×
343

344
                        if ( is_array( $violations ) && count( $violations ) > 0 ) {
×
345

346
                                foreach ( $violations as $violation ) {
×
347
                                        $rule_id = $violation['ruleId'];
×
348

349
                                        // If this rule is a combined rule then map it to the actual reporting rule ID.
350
                                        $actual_rule_id = array_key_exists( $rule_id, $combined_rule_ids ) ? $combined_rule_ids[ $rule_id ] : $rule_id;
×
351

352
                                        if ( in_array( $actual_rule_id, $js_rule_ids, true ) ) {
×
353

354
                                                // This rule is one that we've included in our js ruleset.
355

356
                                                $html   = apply_filters( 'edac_filter_js_violation_html', $violation['html'], $rule_id, $violation );
×
357
                                                $impact = $violation['impact']; // by default, use the impact setting from the js rule.
×
358

359
                                                //phpcs:ignore Generic.Commenting.Todo.TaskFound
360
                                                // TODO: setup a rules class for loading/filtering rules.
361
                                                foreach ( $rules as $rule ) {
×
362
                                                        if ( $rule['slug'] === $actual_rule_id ) {
×
363
                                                                $impact = $rule['rule_type']; // if we are defining the rule_type in php rules config, use that instead of the js rule's impact setting.
×
364
                                                        }
365
                                                }
366

367
                                                //phpcs:ignore Generic.Commenting.Todo.TaskFound, Squiz.PHP.CommentedOutCode.Found
368
                                                // TODO: add support storing $violation['selector'], $violation['tags'].
369

370
                                                /**
371
                                                 * Fires before a rule is run against the content.
372
                                                 *
373
                                                 * This is only running in the JS check context.
374
                                                 *
375
                                                 * @since 1.5.0
376
                                                 *
377
                                                 * @param int    $post_id The post ID.
378
                                                 * @param string $rule_id The rule ID.
379
                                                 * @param string $type    The type of validation which is always 'js' in this path.
380
                                                 */
381
                                                do_action( 'edac_before_rule', $post_id, $actual_rule_id, 'js' );
×
382

NEW
383
                                                $landmark          = $violation['landmark'] ?? null;
×
NEW
384
                                                $landmark_selector = $violation['landmarkSelector'] ?? null;
×
385

NEW
386
                                                ( new Insert_Rule_Data() )->insert( $post, $actual_rule_id, $impact, $html, $landmark, $landmark_selector );
×
387

388
                                                /**
389
                                                 * Fires after a rule is run against the content.
390
                                                 *
391
                                                 * This is only running in the JS check context.
392
                                                 *
393
                                                 * @since 1.5.0
394
                                                 *
395
                                                 * @param int    $post_id The post ID.
396
                                                 * @param string $rule_id The rule ID.
397
                                                 * @param string $type    The type of validation which is always 'js' in this path.
398
                                                 */
399
                                                do_action( 'edac_after_rule', $post_id, $actual_rule_id, 'js' );
×
400

401
                                        }
402
                                }
403
                        }
404

405
                        /**
406
                         * Fires after the validation process is complete.
407
                         *
408
                         * This is only running in the JS check context.
409
                         *
410
                         * @since 1.5.0
411
                         *
412
                         * @param int    $post_id The post ID.
413
                         * @param string $type    The type of validation which is always 'js' in this path.
414
                         */
415
                        do_action( 'edac_after_validate', $post_id, 'js' );
×
416

417
                        // remove corrected records.
418
                        edac_remove_corrected_posts( $post_id, $post->post_type, $pre = 2, 'js' );
×
419

420
                        // Save the density metrics before the summary is generated.
421
                        $metrics = $request['densityMetrics'] ?? [ 0, 0 ];
×
422
                        if ( is_array( $metrics ) && count( $metrics ) > 0 ) {
×
423
                                update_post_meta(
×
424
                                        $post_id,
×
425
                                        '_edac_density_data',
×
426
                                        [
×
427
                                                $metrics['elementCount'] ?? 0,
×
428
                                                $metrics['contentLength'] ?? 0,
×
429
                                        ]
×
430
                                );
×
431
                        }
432

433
                        // Update the summary info that is stored in meta this post.
434
                        ( new Summary_Generator( $post_id ) )->generate_summary();
×
435

436
                        // store a record of this scan in the post's meta.
437
                        update_post_meta( $post_id, '_edac_post_checked_js', time() );
×
438

439
                        /**
440
                         * Fires before sending the REST response ending the validation process.
441
                         *
442
                         * @since 1.14.0
443
                         *
444
                         * @param int             $post_id The post ID.
445
                         * @param string          $type    The type of validation which is always 'js' in this path.
446
                         * @param WP_REST_Request $request The request passed from the REST call.
447
                         */
448
                        do_action( 'edac_validate_before_sending_rest_response', $post_id, 'js', $request );
×
449

450
                        return new \WP_REST_Response(
×
451
                                [
×
452
                                        'success'   => true,
×
453
                                        'id'        => $post_id,
×
454
                                        'timestamp' => time(),
×
455
                                ]
×
456
                        );
×
457

458
                } catch ( \Exception $ex ) {
×
459

460
                        return new \WP_REST_Response(
×
461
                                [
×
462
                                        'message' => $ex->getMessage(),
×
463
                                ],
×
464
                                500
×
465
                        );
×
466

467
                }
468
        }
469

470

471
        /**
472
         * REST handler that clears the cached stats about the scans
473
         *
474
         * @return \WP_REST_Response
475
         */
476
        public function clear_cached_scans_stats() {
477

478
                try {
479

480
                        // Clear the cache.
481
                        $scans_stats = new Scans_Stats();
×
482
                        $scans_stats->clear_cache();
×
483

484
                        // Prime the cache.
485
                        $scans_stats = new Scans_Stats();
×
486

487
                        return new \WP_REST_Response(
×
488
                                [
×
489
                                        'success' => true,
×
490
                                ]
×
491
                        );
×
492

493
                } catch ( \Exception $ex ) {
×
494

495
                        return new \WP_REST_Response(
×
496
                                [
×
497
                                        'message' => $ex->getMessage(),
×
498
                                ],
×
499
                                500
×
500
                        );
×
501

502
                }
503
        }
504

505
        /**
506
         * REST handler that gets stats about the scans
507
         *
508
         * @return \WP_REST_Response
509
         */
510
        public function get_scans_stats() {
511

512
                try {
513

514
                        $scans_stats = new Scans_Stats( 60 * 5 );
×
515
                        $stats       = $scans_stats->summary();
×
516

517
                        return new \WP_REST_Response(
×
518
                                [
×
519
                                        'success' => true,
×
520
                                        'stats'   => $stats,
×
521
                                ]
×
522
                        );
×
523

524
                } catch ( \Exception $ex ) {
×
525

526
                        return new \WP_REST_Response(
×
527
                                [
×
528
                                        'message' => $ex->getMessage(),
×
529
                                ],
×
530
                                500
×
531
                        );
×
532

533
                }
534
        }
535

536

537
        /**
538
         * REST handler that gets stats about the scans by post type
539
         *
540
         * @param WP_REST_Request $request The request passed from the REST call.
541
         *
542
         * @return \WP_REST_Response
543
         */
544
        public function get_scans_stats_by_post_type( $request ) {
545

546
                if ( ! isset( $request['slug'] ) ) {
×
547
                        return new \WP_REST_Response( [ 'message' => 'A required parameter is missing.' ], 400 );
×
548
                }
549

550
                try {
551

552
                        $post_type            = strval( $request['slug'] );
×
553
                        $scannable_post_types = Settings::get_scannable_post_types();
×
554

555
                        if ( in_array( $post_type, $scannable_post_types, true ) ) {
×
556

557
                                $scans_stats = new Scans_Stats( 60 * 5 );
×
558
                                $by_type     = $scans_stats->issues_summary_by_post_type( $post_type );
×
559

560
                                return new \WP_REST_Response(
×
561
                                        [
×
562
                                                'success' => true,
×
563
                                                'stats'   => $by_type,
×
564
                                        ]
×
565
                                );
×
566
                        }
567
                        return new \WP_REST_Response( [ 'message' => 'The post type is not set to be scanned.' ], 400 );
×
568
                } catch ( \Exception $ex ) {
×
569
                        return new \WP_REST_Response(
×
570
                                [
×
571
                                        'message' => $ex->getMessage(),
×
572
                                ],
×
573
                                500
×
574
                        );
×
575
                }
576
        }
577

578
        /**
579
         * REST handler that gets stats about the scans by post types
580
         *
581
         * @param WP_REST_Request $request The request passed from the REST call.
582
         *
583
         * @return \WP_REST_Response
584
         */
585
        public function get_scans_stats_by_post_types( $request ) { //phpcs:ignore
586

587
                try {
588

589
                        $scans_stats = new Scans_Stats( 60 * 5 );
×
590

591
                        $scannable_post_types = Settings::get_scannable_post_types();
×
592

593
                        $post_types = get_post_types(
×
594
                                [
×
595
                                        'public' => true,
×
596
                                ]
×
597
                        );
×
598
                        unset( $post_types['attachment'] );
×
599

600
                        $post_types_to_check = array_merge( [ 'post', 'page' ], $scannable_post_types );
×
601

602
                        $by_types = [];
×
603

604
                        foreach ( $post_types as $post_type ) {
×
605

606
                                $by_types[ $post_type ] = false;
×
607
                                if ( in_array( $post_type, $scannable_post_types, true ) && in_array( $post_type, $post_types_to_check, true ) ) {
×
608
                                        $by_types[ $post_type ] = $scans_stats->issues_summary_by_post_type( $post_type );
×
609
                                }
610
                        }
611

612
                        return new \WP_REST_Response(
×
613
                                [
×
614
                                        'success' => true,
×
615
                                        'stats'   => $by_types,
×
616
                                ]
×
617
                        );
×
618

619
                } catch ( \Exception $ex ) {
×
620

621
                        return new \WP_REST_Response(
×
622
                                [
×
623
                                        'message' => $ex->getMessage(),
×
624
                                ],
×
625
                                500
×
626
                        );
×
627

628
                }
629
        }
630

631
        /**
632
         * REST handler that gets stats about the scans
633
         *
634
         * @param \WP_REST_Request $request The request passed from the REST call.
635
         *
636
         * @return \WP_REST_Response
637
         */
638
        public function get_site_summary( \WP_REST_Request $request ) {
639

640
                try {
641
                        $scan_stats = new Scans_Stats();
×
642
                        if ( (bool) $request->get_param( 'clearCache' ) ) {
×
643
                                $scan_stats->clear_cache();
×
644
                        }
645

646
                        return new \WP_REST_Response(
×
647
                                [
×
648
                                        'success' => true,
×
649
                                        'stats'   => $scan_stats->summary(),
×
650
                                ]
×
651
                        );
×
652
                } catch ( \Exception $ex ) {
×
653
                        return new \WP_REST_Response(
×
654
                                [
×
655
                                        'message' => $ex->getMessage(),
×
656
                                ],
×
657
                                500
×
658
                        );
×
659
                }
660
        }
661
}
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

© 2025 Coveralls, Inc