HEX
Server: Apache/2.4.65 (Debian)
System: Linux 88f31f35b0b8 6.1.0-38-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.147-1 (2025-08-02) x86_64
User: www-data (33)
PHP: 8.2.29
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/optimization-detective/storage/class-od-storage-lock.php
<?php
/**
 * Optimization Detective: OD_Storage_Lock class
 *
 * @package optimization-detective
 * @since 0.1.0
 */

// @codeCoverageIgnoreStart
if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}
// @codeCoverageIgnoreEnd

/**
 * Class containing logic for locking storage for new URL Metrics.
 *
 * @since 0.1.0
 * @access private
 */
final class OD_Storage_Lock {

	/**
	 * Capability for being able to store a URL Metric now.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	const STORE_URL_METRIC_NOW_CAPABILITY = 'od_store_url_metric_now';

	/**
	 * Adds hooks.
	 *
	 * @since 1.0.0
	 */
	public static function add_hooks(): void {
		add_filter( 'user_has_cap', array( __CLASS__, 'filter_user_has_cap' ) );
	}

	/**
	 * Filters `user_has_cap` to grant the `od_store_url_metric_now` capability to users who can `manage_options` by default.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, bool>|mixed $allcaps Capability names mapped to boolean values for whether the user has that capability.
	 * @return array<string, bool> Capability names mapped to boolean values for whether the user has that capability.
	 */
	public static function filter_user_has_cap( $allcaps ): array {
		if ( ! is_array( $allcaps ) ) {
			$allcaps = array();
		}
		if ( isset( $allcaps['manage_options'] ) ) {
			$allcaps['od_store_url_metric_now'] = $allcaps['manage_options'];
		}
		return $allcaps;
	}

	/**
	 * Gets the TTL (in seconds) for the URL Metric storage lock.
	 *
	 * @since 0.1.0
	 *
	 * @return int<0, max> TTL in seconds, greater than or equal to zero. A value of zero means that the storage lock should be disabled and thus that transients must not be used.
	 */
	public static function get_ttl(): int {
		$ttl = current_user_can( self::STORE_URL_METRIC_NOW_CAPABILITY ) ? 0 : MINUTE_IN_SECONDS;

		/**
		 * Filters how long the current IP is locked from submitting another URL metric storage REST API request.
		 *
		 * @since 0.1.0
		 * @since 1.0.0 This now defaults to zero (0) for authorized users.
		 * @link https://github.com/WordPress/performance/blob/trunk/plugins/optimization-detective/docs/hooks.md#:~:text=Filter%3A%20od_url_metric_storage_lock_ttl
		 *
		 * @param int $ttl TTL. Defaults to 60, except zero (0) for authorized users.
		 */
		$ttl = (int) apply_filters( 'od_url_metric_storage_lock_ttl', $ttl );
		return max( 0, $ttl );
	}

	/**
	 * Gets the transient key for locking URL Metric storage (for the current IP).
	 *
	 * @since 0.1.0
	 *
	 * @return non-empty-string Transient key.
	 */
	public static function get_transient_key(): string {
		if ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
			$ip_address = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) );
		} elseif ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
			$ip_address = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
		} else {
			$ip_address = '';
		}
		return 'url_metrics_storage_lock_' . wp_hash( (string) rest_is_ip_address( $ip_address ) );
	}

	/**
	 * Sets URL Metric storage lock (for the current IP).
	 *
	 * If the storage lock TTL is greater than zero, then a transient is set with the current timestamp and expiring at TTL
	 * seconds. Otherwise, if the current TTL is zero, then any transient is deleted.
	 *
	 * @since 0.1.0
	 */
	public static function set_lock(): void {
		$ttl = self::get_ttl();
		$key = self::get_transient_key();
		if ( 0 === $ttl ) {
			delete_transient( $key );
		} else {
			set_transient( $key, microtime( true ), $ttl );
		}
	}

	/**
	 * Checks whether URL Metric storage is locked (for the current IP).
	 *
	 * @since 0.1.0
	 *
	 * @return bool Whether locked.
	 */
	public static function is_locked(): bool {
		$ttl = self::get_ttl();
		if ( 0 === $ttl ) {
			return false;
		}
		$locked_time = get_transient( self::get_transient_key() );
		if ( false === $locked_time ) {
			return false;
		}
		return microtime( true ) - floatval( $locked_time ) < $ttl;
	}
}