*/ private const STABLE_PARAMS = ['page', 'post_type', 'taxonomy']; /** * Option holding the list of transient keys created by the plugin. */ private const REGISTRY_OPTION = 'adsd_transient_registry'; /** * Upper bound for the registry, so it cannot grow without limit. */ private const REGISTRY_LIMIT = 200; /** * Get a cache key for the current request URI. * * @since 1.0.29 * @return string Hash of the normalized page URI. */ public static function get_page_cache_key(): string { $uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : ''; return md5(self::normalize_uri($uri)); } /** * Get the current URL without volatile query parameters. * * @since 1.0.29 * @return string Normalized absolute URL. */ public static function normalize_current_url(): string { $scheme = is_ssl() ? 'https' : 'http'; $host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : 'localhost'; $uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : ''; return $scheme . '://' . $host . self::normalize_uri($uri); } /** * Compare two URLs ignoring volatile query parameters. * * @since 1.0.29 * @param string $first First URL. * @param string $second Second URL. * @return bool True when both point to the same screen. */ public static function urls_match(string $first, string $second): bool { return self::normalize_uri($first) === self::normalize_uri($second); } /** * Check whether a rule may be applied on the current screen. * * Position based selectors are only valid on the page they were recorded * on, so such rules carry the `page` scope. Rules stored before 1.0.31 have * no scope and keep applying everywhere. * * @since 1.0.31 * @param array $rule Rule data. * @return bool True when the rule belongs to this screen. */ public static function rule_applies_here(array $rule): bool { if ((string) ($rule['scope'] ?? 'any') !== 'page') { return true; } $page_url = (string) ($rule['page_url'] ?? ''); if ($page_url === '') { return true; } return self::urls_match(self::normalize_current_url(), $page_url); } /** * Store a transient and remember its key for later invalidation. * * @since 1.0.29 * @param string $key Transient key. * @param mixed $value Value to store. * @param int $expiration Lifetime in seconds. */ public static function set_tracked_transient(string $key, $value, int $expiration): void { set_transient($key, $value, $expiration); $registry = get_option(self::REGISTRY_OPTION, []); if (!is_array($registry)) { $registry = []; } if (in_array($key, $registry, true)) { return; } $registry[] = $key; if (count($registry) > self::REGISTRY_LIMIT) { $dropped = array_splice($registry, 0, count($registry) - self::REGISTRY_LIMIT); foreach ($dropped as $stale_key) { delete_transient($stale_key); } } update_option(self::REGISTRY_OPTION, $registry, false); } /** * Delete every transient created through the registry. * * @since 1.0.29 */ public static function flush_tracked_transients(): void { $registry = get_option(self::REGISTRY_OPTION, []); if (is_array($registry)) { foreach ($registry as $key) { delete_transient((string) $key); } } delete_option(self::REGISTRY_OPTION); } /** * Reduce a URI to its path plus the parameters that identify a screen. * * @since 1.0.29 * @param string $uri URI or absolute URL. * @return string Normalized path with a stable query string. */ private static function normalize_uri(string $uri): string { $parsed = wp_parse_url($uri); $path = $parsed['path'] ?? ''; $query = []; if (!empty($parsed['query'])) { parse_str($parsed['query'], $params); foreach (self::STABLE_PARAMS as $key) { if (isset($params[$key]) && is_scalar($params[$key])) { $query[$key] = (string) $params[$key]; } } } if (empty($query)) { return $path; } ksort($query); return $path . '?' . http_build_query($query); } }