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

equalizedigital / accessibility-checker / 12817959377

16 Jan 2025 09:31PM UTC coverage: 24.742%. First build
12817959377

Pull #837

github

web-flow
Merge 460952ef4 into 03c52531a
Pull Request #837: Release v1.18.0

1 of 20 new or added lines in 3 files covered. (5.0%)

1750 of 7073 relevant lines covered (24.74%)

5.55 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'] ) ) {
×
NEW
216
                        return new \WP_REST_Response( [ 'message' => 'The ID is required to be passed.' ], 400 );
×
217
                }
218

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

NEW
227
                        $post_type  = get_post_type( $post );
×
NEW
228
                        $post_types = Helpers::get_option_as_array( 'edac_post_types' );
×
NEW
229
                        if ( empty( $post_types ) || ! in_array( $post_type, $post_types, true ) ) {
×
NEW
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.
NEW
235
                if ( isset( $json['flush'] ) ) {
×
236
                        // purge the issues for this post.
237
                        Purge_Post_Data::delete_post( $post_id );
×
238
                }
239

NEW
240
                return new \WP_REST_Response(
×
NEW
241
                        [
×
NEW
242
                                'success' => true,
×
NEW
243
                                'flushed' => isset( $json['flush'] ),
×
NEW
244
                                'id'      => $post_id,
×
NEW
245
                        ]
×
NEW
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
                ) {
290
                        return new \WP_REST_Response( [ 'message' => 'A required parameter is missing.' ], 400 );
×
291
                }
292

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

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

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

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

306
                }
307

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

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

326
                try {
327

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

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

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

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

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

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

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

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

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

360
                                                //phpcs:ignore Generic.Commenting.Todo.TaskFound
361
                                                // TODO: setup a rules class for loading/filtering rules.
362
                                                foreach ( $rules as $rule ) {
×
363
                                                        if ( $rule['slug'] === $actual_rule_id ) {
×
364
                                                                $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.
×
365
                                                        }
366
                                                }
367

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

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

384
                                                ( new Insert_Rule_Data() )->insert( $post, $actual_rule_id, $impact, $html );
×
385

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

399
                                        }
400
                                }
401
                        }
402

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

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

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

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

424
                        /**
425
                         * Fires before sending the REST response ending the validation process.
426
                         *
427
                         * @since 1.14.0
428
                         *
429
                         * @param int             $post_id The post ID.
430
                         * @param string          $type    The type of validation which is always 'js' in this path.
431
                         * @param WP_REST_Request $request The request passed from the REST call.
432
                         */
433
                        do_action( 'edac_validate_before_sending_rest_response', $post_id, 'js', $request );
×
434

435
                        return new \WP_REST_Response(
×
436
                                [
×
437
                                        'success'   => true,
×
438
                                        'id'        => $post_id,
×
439
                                        'timestamp' => time(),
×
440
                                ]
×
441
                        );
×
442

443
                } catch ( \Exception $ex ) {
×
444

445
                        return new \WP_REST_Response(
×
446
                                [
×
447
                                        'message' => $ex->getMessage(),
×
448
                                ],
×
449
                                500
×
450
                        );
×
451

452
                }
453
        }
454

455

456
        /**
457
         * REST handler that clears the cached stats about the scans
458
         *
459
         * @return \WP_REST_Response
460
         */
461
        public function clear_cached_scans_stats() {
462

463
                try {
464

465
                        // Clear the cache.
466
                        $scans_stats = new Scans_Stats();
×
467
                        $scans_stats->clear_cache();
×
468

469
                        // Prime the cache.
470
                        $scans_stats = new Scans_Stats();
×
471

472
                        return new \WP_REST_Response(
×
473
                                [
×
474
                                        'success' => true,
×
475
                                ]
×
476
                        );
×
477

478
                } catch ( \Exception $ex ) {
×
479

480
                        return new \WP_REST_Response(
×
481
                                [
×
482
                                        'message' => $ex->getMessage(),
×
483
                                ],
×
484
                                500
×
485
                        );
×
486

487
                }
488
        }
489

490
        /**
491
         * REST handler that gets stats about the scans
492
         *
493
         * @return \WP_REST_Response
494
         */
495
        public function get_scans_stats() {
496

497
                try {
498

499
                        $scans_stats = new Scans_Stats( 60 * 5 );
×
500
                        $stats       = $scans_stats->summary();
×
501

502
                        return new \WP_REST_Response(
×
503
                                [
×
504
                                        'success' => true,
×
505
                                        'stats'   => $stats,
×
506
                                ]
×
507
                        );
×
508

509
                } catch ( \Exception $ex ) {
×
510

511
                        return new \WP_REST_Response(
×
512
                                [
×
513
                                        'message' => $ex->getMessage(),
×
514
                                ],
×
515
                                500
×
516
                        );
×
517

518
                }
519
        }
520

521

522
        /**
523
         * REST handler that gets stats about the scans by post type
524
         *
525
         * @param WP_REST_Request $request The request passed from the REST call.
526
         *
527
         * @return \WP_REST_Response
528
         */
529
        public function get_scans_stats_by_post_type( $request ) {
530

531
                if ( ! isset( $request['slug'] ) ) {
×
532
                        return new \WP_REST_Response( [ 'message' => 'A required parameter is missing.' ], 400 );
×
533
                }
534

535
                try {
536

537
                        $post_type            = strval( $request['slug'] );
×
538
                        $scannable_post_types = Settings::get_scannable_post_types();
×
539

540
                        if ( in_array( $post_type, $scannable_post_types, true ) ) {
×
541

542
                                $scans_stats = new Scans_Stats( 60 * 5 );
×
543
                                $by_type     = $scans_stats->issues_summary_by_post_type( $post_type );
×
544

545
                                return new \WP_REST_Response(
×
546
                                        [
×
547
                                                'success' => true,
×
548
                                                'stats'   => $by_type,
×
549
                                        ]
×
550
                                );
×
551
                        }
552
                        return new \WP_REST_Response( [ 'message' => 'The post type is not set to be scanned.' ], 400 );
×
553
                } catch ( \Exception $ex ) {
×
554
                        return new \WP_REST_Response(
×
555
                                [
×
556
                                        'message' => $ex->getMessage(),
×
557
                                ],
×
558
                                500
×
559
                        );
×
560
                }
561
        }
562

563
        /**
564
         * REST handler that gets stats about the scans by post types
565
         *
566
         * @param WP_REST_Request $request The request passed from the REST call.
567
         *
568
         * @return \WP_REST_Response
569
         */
570
        public function get_scans_stats_by_post_types( $request ) { //phpcs:ignore
571

572
                try {
573

574
                        $scans_stats = new Scans_Stats( 60 * 5 );
×
575

576
                        $scannable_post_types = Settings::get_scannable_post_types();
×
577

578
                        $post_types = get_post_types(
×
579
                                [
×
580
                                        'public' => true,
×
581
                                ]
×
582
                        );
×
583
                        unset( $post_types['attachment'] );
×
584

585
                        $post_types_to_check = array_merge( [ 'post', 'page' ], $scannable_post_types );
×
586

587
                        $by_types = [];
×
588

589
                        foreach ( $post_types as $post_type ) {
×
590

591
                                $by_types[ $post_type ] = false;
×
592
                                if ( in_array( $post_type, $scannable_post_types, true ) && in_array( $post_type, $post_types_to_check, true ) ) {
×
593
                                        $by_types[ $post_type ] = $scans_stats->issues_summary_by_post_type( $post_type );
×
594
                                }
595
                        }
596

597
                        return new \WP_REST_Response(
×
598
                                [
×
599
                                        'success' => true,
×
600
                                        'stats'   => $by_types,
×
601
                                ]
×
602
                        );
×
603

604
                } catch ( \Exception $ex ) {
×
605

606
                        return new \WP_REST_Response(
×
607
                                [
×
608
                                        'message' => $ex->getMessage(),
×
609
                                ],
×
610
                                500
×
611
                        );
×
612

613
                }
614
        }
615

616
        /**
617
         * REST handler that gets stats about the scans
618
         *
619
         * @param \WP_REST_Request $request The request passed from the REST call.
620
         *
621
         * @return \WP_REST_Response
622
         */
623
        public function get_site_summary( \WP_REST_Request $request ) {
624

625
                try {
626
                        $scan_stats = new Scans_Stats();
×
627
                        if ( (bool) $request->get_param( 'clearCache' ) ) {
×
628
                                $scan_stats->clear_cache();
×
629
                        }
630

631
                        return new \WP_REST_Response(
×
632
                                [
×
633
                                        'success' => true,
×
634
                                        'stats'   => $scan_stats->summary(),
×
635
                                ]
×
636
                        );
×
637
                } catch ( \Exception $ex ) {
×
638
                        return new \WP_REST_Response(
×
639
                                [
×
640
                                        'message' => $ex->getMessage(),
×
641
                                ],
×
642
                                500
×
643
                        );
×
644
                }
645
        }
646
}
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