*/ private const LABELS = [ self::ADMIN => 'admin', self::AJAX => 'ajax', self::REST => 'rest', self::CRON => 'cron', self::CLI => 'cli', self::FRONTEND => 'frontend', self::LOGIN => 'login', self::WORKER => 'worker', ]; /** * Resolve the bitmask for this request. * * A request can carry several bits at once: an admin-ajax call is ADMIN|AJAX, * and the queue worker endpoint is WORKER|FRONTEND. Modules should gate on the * bit they actually care about rather than testing for equality. */ public static function current(): int { if ( null !== self::$current ) { return self::$current; } $mask = 0; if ( is_admin() ) { $mask |= self::ADMIN; } if ( wp_doing_ajax() ) { $mask |= self::AJAX; } if ( self::isRest() ) { $mask |= self::REST; } if ( wp_doing_cron() ) { $mask |= self::CRON; } if ( ( defined( 'WP_CLI' ) && WP_CLI ) ) { $mask |= self::CLI; } if ( self::isLoginScreen() ) { $mask |= self::LOGIN; } if ( self::isWorker() ) { $mask |= self::WORKER; } // Frontend is the residual: a normal page view is anything that is not an // admin screen, an API endpoint, or a background runner. if ( 0 === ( $mask & ( self::ADMIN | self::REST | self::CRON | self::CLI | self::LOGIN ) ) ) { $mask |= self::FRONTEND; } self::$current = $mask; return $mask; } /** * Does the current request match any of the given bits? * * @param int $bits Bitmask to test against. */ public static function is( int $bits ): bool { return 0 !== ( self::current() & $bits ); } /** * Describe the current context, for logs and the health screen. * * @return list */ public static function describe(): array { $mask = self::current(); $names = []; foreach ( self::LABELS as $bit => $label ) { if ( 0 !== ( $mask & $bit ) ) { $names[] = $label; } } return $names; } /** * REST detection that works before `rest_api_init` has fired. * * `REST_REQUEST` is only defined once WordPress has committed to serving a REST * response, which happens too late for boot-time gating — so fall back to * matching the request path against the REST prefix. */ private static function isRest(): bool { if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { return true; } if ( isset( $_GET['rest_route'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return true; } if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { return false; } $path = wp_parse_url( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ), PHP_URL_PATH ); $prefix = trailingslashit( rest_get_url_prefix() ); return is_string( $path ) && str_contains( $path, '/' . $prefix ); } /** * Are we on wp-login.php / wp-register.php? */ private static function isLoginScreen(): bool { if ( isset( $GLOBALS['pagenow'] ) && in_array( $GLOBALS['pagenow'], [ 'wp-login.php', 'wp-register.php' ], true ) ) { return true; } return defined( 'RAHKAR_WP_DOING_LOGIN' ) && RAHKAR_WP_DOING_LOGIN; } /** * Is this the queue worker endpoint? * * Only the *presence* of the parameter is checked here — the shared secret is * verified by the worker endpoint itself. Context detection must stay free of * anything that could throw or query. */ private static function isWorker(): bool { return isset( $_GET['rahkar_worker'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } /** * Reset memoisation. Test-suite affordance only. * * @internal */ public static function flush(): void { self::$current = null; } }