Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
6.25% covered (danger)
6.25%
2 / 32
0.00% covered (danger)
0.00%
0 / 4
CRAP
n/a
0 / 0
pcg_force_override_active
20.00% covered (danger)
20.00%
2 / 10
0.00% covered (danger)
0.00%
0 / 1
17.80
pcg_force_bypass_key
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
pcg_force_handle_set_bypass
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
pcg_force_render_bypass_form
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Force-override controls for the activation / update guards.
4 *
5 * @package automattic/jetpack-mu-wpcom
6 */
7
8add_action( 'admin_post_pcg_force_set_bypass', 'pcg_force_handle_set_bypass' );
9
10const PCG_FORCE_BYPASS_TTL = 600;
11
12/**
13 * True when the current request is allowed to skip PCG checks, either
14 * via an explicit `pcg_force=1` flag on the current action's nonce or
15 * a short-lived per-user bypass transient.
16 *
17 * @param string $cap Capability the caller requires (e.g. `activate_plugins`, `update_plugins`).
18 * @return bool
19 */
20function pcg_force_override_active( $cap ) {
21    if ( ! is_user_logged_in() || ! current_user_can( $cap ) ) {
22        return false;
23    }
24
25    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- the surrounding action's own nonce gates the request; pcg_force is just a hint.
26    $force = sanitize_text_field( wp_unslash( $_REQUEST['pcg_force'] ?? '' ) );
27    if ( '1' === $force ) {
28        pcg_log_event( 'Force override used', array( 'source' => 'pcg_force_flag' ) );
29        return true;
30    }
31
32    if ( get_transient( pcg_force_bypass_key() ) ) {
33        pcg_log_event( 'Force override used', array( 'source' => 'bypass_transient' ) );
34        return true;
35    }
36
37    return false;
38}
39
40/**
41 * Per-user transient key for the time-boxed bypass.
42 *
43 * @return string
44 */
45function pcg_force_bypass_key() {
46    return 'pcg_force_bypass_' . get_current_user_id();
47}
48
49/**
50 * Admin-post handler — sets the bypass transient and redirects back.
51 *
52 * @return never
53 */
54function pcg_force_handle_set_bypass(): never {
55    if (
56        ! current_user_can( 'activate_plugins' )
57        && ! current_user_can( 'update_plugins' )
58        && ! current_user_can( 'install_plugins' )
59        && ! current_user_can( 'upload_plugins' )
60    ) {
61        wp_die( esc_html__( 'You do not have permission to do that.', 'jetpack-mu-wpcom' ), 403 );
62    }
63    check_admin_referer( 'pcg_force_set_bypass' );
64
65    set_transient( pcg_force_bypass_key(), 1, PCG_FORCE_BYPASS_TTL );
66    pcg_log_event( 'Force bypass enabled', array( 'ttl' => PCG_FORCE_BYPASS_TTL ) );
67
68    $redirect = isset( $_REQUEST['_wp_http_referer'] )
69        ? wp_validate_redirect( esc_url_raw( wp_unslash( $_REQUEST['_wp_http_referer'] ) ), self_admin_url( 'plugins.php' ) )
70        : self_admin_url( 'plugins.php' );
71    wp_safe_redirect( add_query_arg( 'pcg_bypass_enabled', '1', $redirect ) );
72    exit;
73}
74
75/**
76 * Render the inline "Disable check for 10 minutes" form. Posts to
77 * admin-post.php so the handler can set the transient and redirect.
78 */
79function pcg_force_render_bypass_form() {
80    ?>
81    <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline;">
82        <input type="hidden" name="action" value="pcg_force_set_bypass" />
83        <?php wp_nonce_field( 'pcg_force_set_bypass' ); ?>
84        <button type="submit" class="button-link">
85            <?php esc_html_e( 'Disable checks for 10 minutes', 'jetpack-mu-wpcom' ); ?>
86        </button>
87    </form>
88    <?php
89}