|null */ private ?array $data = null; public function registerHooks(): void { add_action('admin_post_' . self::ACTION, [$this, 'handleDismiss']); } /** @return array */ private function data(): array { if ($this->data === null) { $file = TABBY_DIR . 'config/pro-upsell.php'; $this->data = is_readable($file) ? (array) require $file : []; } return $this->data; } /** Whether the PRO edition can actually be bought yet. */ private function sellable(): bool { return (bool) ($this->data()['sellable'] ?? false); } /** Whether to render the promo at all (filterable for white-label builds). */ public function enabled(): bool { // Somebody running the paid edition has already bought what this sells. // Only the banner was ever dismissible, so without this the sidebar promo // and the locked cards followed a paying customer around for ever. if (defined('Tabby\\Pro\\VERSION')) { return false; } /** * Filters whether the Tabby PRO promo is shown on the settings screen. * * @param bool $show Default true. */ return (bool) apply_filters('tabby/show_pro_cta', true) && $this->features() !== []; } /** * The pricing page in the admin's language when plogins.com publishes one, * tagged so the store can tell a visit came from this settings screen. */ private function trackedUrl(string $url): string { $d = $this->data(); $lang = substr((string) get_user_locale(), 0, 2); if (in_array($lang, (array) ($d['locales'] ?? []), true)) { $url = (string) preg_replace('#^https://plogins\.com/#', 'https://plogins.com/' . $lang . '/', $url); } if (empty($d['campaign'])) { return $url; } return add_query_arg( [ 'utm_source' => 'wp-admin', 'utm_medium' => 'plugin', 'utm_campaign' => (string) $d['campaign'], ], $url ); } private function url(): string { $default = (string) ($this->data()['url'] ?? 'https://plogins.com/tabvera-pro/'); $default = $this->trackedUrl($default); /** * Filters the URL the PRO call-to-action buttons point at. * * @param string $url Default the Tabby PRO page. */ return (string) apply_filters('tabby/pro_url', $default); } private function isPolish(): bool { return str_starts_with((string) get_user_locale(), 'pl'); } private function priceLabel(): string { if (! $this->sellable()) { return __('Coming soon', 'tabvera'); } $d = $this->data(); if (! empty($d['price_from'])) { $cur = ($d['currency'] ?? 'EUR') === 'EUR' ? '€' : (string) $d['currency'] . ' '; /* translators: 1: currency symbol, 2: yearly price */ return sprintf(__('from %1$s%2$d/yr', 'tabvera'), $cur, (int) $d['price_from']); } return ''; } /** The call-to-action label: buy when sellable, otherwise a soft notify. */ private function ctaLabel(): string { return $this->sellable() ? __('Upgrade to PRO', 'tabvera') : __('Get notified', 'tabvera'); } /** @return array */ private function features(): array { $lang = $this->isPolish() ? 'pl' : 'en'; $out = []; foreach ((array) ($this->data()['features'] ?? []) as $f) { $x = is_array($f) ? ($f[$lang] ?? $f['en'] ?? null) : null; if (is_array($x) && ! empty($x['title'])) { $out[] = ['title' => (string) $x['title'], 'desc' => (string) ($x['desc'] ?? '')]; } } return $out; } public function bannerDismissed(): bool { return (bool) get_user_meta(get_current_user_id(), self::META, true); } private function dismissUrl(): string { return wp_nonce_url(admin_url('admin-post.php?action=' . self::ACTION), self::ACTION); } public function handleDismiss(): void { if (! current_user_can('manage_woocommerce')) { wp_die(esc_html__('Permission denied.', 'tabvera')); } check_admin_referer(self::ACTION); update_user_meta(get_current_user_id(), self::META, 1); wp_safe_redirect(wp_get_referer() ?: admin_url('admin.php?page=tabby-settings')); exit; } /* ------------------------------------------------------------------ */ /* Render pieces */ /* ------------------------------------------------------------------ */ /** Dismissible strip at the top of the settings screen. */ public function banner(): void { if (! $this->enabled() || $this->bannerDismissed()) { return; } $name = (string) ($this->data()['name'] ?? 'Tabvera Pro'); $price = $this->priceLabel(); $subtitle = implode(', ', array_slice(array_map( static fn (array $f): string => $f['title'], $this->features(), ), 0, 3)); ?> enabled() || $this->bannerDismissed()) { return; } $name = (string) ($this->data()['name'] ?? 'Tabvera Pro'); $price = $this->priceLabel(); $features = $this->features(); ?> enabled()) { return; } $features = $this->features(); $name = (string) ($this->data()['name'] ?? 'Tabvera Pro'); ?>

PRO