Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.00% covered (success)
90.00%
18 / 20
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
Jetpack_Application_Password_Extras
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
4 / 4
14
100.00% covered (success)
100.00%
1 / 1
 init
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 application_password_extras
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
9
 is_videopress_ajax_action
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 get_abilities
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Jetpack Application Password Extras
4 *
5 * Extends WordPress Application Passwords to work with additional abilities
6 * beyond the REST API.
7 *
8 * @package jetpack
9 */
10
11if ( ! defined( 'ABSPATH' ) ) {
12    exit( 0 );
13}
14
15/**
16 * Extends Application Password functionality beyond the REST API.
17 */
18class Jetpack_Application_Password_Extras {
19
20    /**
21     * The AJAX action prefix for VideoPress actions.
22     *
23     * @var string
24     */
25    private const VIDEOPRESS_AJAX_PREFIX = 'videopress-';
26
27    /**
28     * Initialize the main hooks.
29     */
30    public static function init() {
31        add_filter( 'application_password_is_api_request', array( __CLASS__, 'application_password_extras' ) );
32    }
33
34    /**
35     * Allow Application Password access to additional abilities.
36     *
37     * NOTE: If expanding this to include more abilities, consider updating the
38     * `get_abilities` method to include new abilities.
39     *
40     * @param bool $original_value The original value of the filter.
41     * @return bool The new value of the filter.
42     */
43    public static function application_password_extras( $original_value ) {
44        if ( $original_value ) {
45            return true;
46        }
47
48        // Allow Application Password access to admin-ajax.php for VideoPress actions only
49        if ( is_admin() && wp_doing_ajax() && self::is_videopress_ajax_action() ) {
50            return true;
51        }
52
53        // Allow access to post/page previews
54        $is_preview_request = isset( $_GET['preview'] ) && 'true' === $_GET['preview']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
55        $has_post_id        = isset( $_GET['p'] ) || isset( $_GET['page_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
56        if ( $is_preview_request && $has_post_id ) {
57            return true;
58        }
59
60        return $original_value;
61    }
62
63    /**
64     * Check if the current AJAX request is for a VideoPress action.
65     *
66     * @return bool True if the action starts with 'videopress-', false otherwise.
67     */
68    private static function is_videopress_ajax_action() {
69        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We're only checking the action name, not processing the request.
70        $action = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : '';
71
72        if ( empty( $action ) ) {
73            return false;
74        }
75
76        return str_starts_with( $action, self::VIDEOPRESS_AJAX_PREFIX );
77    }
78
79    /**
80     * Get the abilities that this extension provides.
81     *
82     * @return array Array of abilities with their status.
83     */
84    public static function get_abilities() {
85        return array(
86            'admin-ajax-videopress' => true,
87            'post-previews'         => true,
88        );
89    }
90}