isPro = $isPro ?? (defined('ZBRIDGE_IS_PRO') && ZBRIDGE_IS_PRO); $this->now = $now ?? time(); } public function register(): void { if ($this->isPro) { return; } add_action('admin_init', [$this, 'recordFirstSeen']); add_action('admin_init', [$this, 'handleChoice']); add_action('admin_notices', [$this, 'render']); } public function recordFirstSeen(): void { if (!Options::get(self::FIRST_SEEN)) { Options::set(self::FIRST_SEEN, $this->now); } } public function handleChoice(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified below $choice = isset($_GET['zbridge_review']) ? sanitize_key(wp_unslash($_GET['zbridge_review'])) : ''; if ($choice === '' || !current_user_can('manage_options')) { return; } check_admin_referer(self::NONCE); $this->applyChoice($choice); } /** * @param string $choice 'later' or 'dismiss' */ public function applyChoice(string $choice): void { if ($choice === 'dismiss') { Options::set(self::STATE, 'dismissed'); } elseif ($choice === 'later') { Options::set(self::SNOOZE_UNTIL, $this->now + 30 * DAY_IN_SECONDS); } } public function shouldShow(): bool { if ($this->isPro || Options::get(self::STATE) === 'dismissed') { return false; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only page check $page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; if (!in_array($page, self::PLUGIN_PAGES, true)) { return false; } $firstSeen = (int) Options::get(self::FIRST_SEEN, 0); if ($firstSeen === 0 || $this->now - $firstSeen < 14 * DAY_IN_SECONDS) { return false; } if ($this->now < (int) Options::get(self::SNOOZE_UNTIL, 0)) { return false; } return (int) ((new TelemetryAggregator())->getSummary()['total_orders'] ?? 0) > 0; } public function render(): void { if (!current_user_can('manage_options') || !$this->shouldShow()) { return; } $later = wp_nonce_url(add_query_arg('zbridge_review', 'later'), self::NONCE); $dismiss = wp_nonce_url(add_query_arg('zbridge_review', 'dismiss'), self::NONCE); ?>