Refund Policy). 'grace_days' * is how long everything keeps working after a paid license ends (StoreRuler Pro's Licensing::grace_days(); the free build * has no license code, so the page keeps the number itself). 'sites' maps each site count a license is sold for to the * yearly prices it costs (Freemius's pricing rows of every plan; more than one site has no monthly price). The buyer picks * the count in Freemius's checkout, whose license selector the page leaves on. */ public const OFFER = array( 'prices' => array( 'USD' => array( 'monthly' => 39, 'yearly' => 390, ), ), 'plans' => array( 'pro' => array( 'monthly' => 39, 'yearly' => 390, 'id' => '69659', ), 'plus' => array( 'monthly' => 59, 'yearly' => 590, 'id' => '73443', ), 'business' => array( 'monthly' => 79, 'yearly' => 790, 'id' => '73444', ), ), 'refund_days' => 14, 'sites' => array( 1 => 1, 3 => 2, 5 => 3, ), 'grace_days' => 14, ); /** The currency shown when neither the store's currency nor StoreRuler's language picks one. */ public const DEFAULT_CURRENCY = 'USD'; /** The plans' names, who each is for, the plan it builds on and what it adds (the card's list). */ private const PLANS = array( 'pro' => array( 'name' => 'Pro', 'for' => 'For stores that sell online', 'base' => 'Everything in Free, plus:', 'adds' => array( 'Manual orders, returns and order editing', 'Campaigns, cart reminders and gift cards', 'SEO Center and AI Studio', 'Shipping labels and live parcel tracking', 'Report library and automations', 'The StoreRuler theme and design studio' ), ), 'plus' => array( 'name' => 'Plus', 'for' => 'For shops with a till, stock and suppliers', 'base' => 'Everything in Pro, plus:', 'adds' => array( 'Till with barcodes, split payments and day close', 'Purchase orders, suppliers and goods in', 'Stock counts with barcodes and approval', 'Landed cost, costs and profit', 'Accounting exports' ), ), 'business' => array( 'name' => 'Business', 'for' => 'For stores that also sell to dealers', 'base' => 'Everything in Plus, plus:', 'adds' => array( 'Dealer groups and wholesale price lists', 'Quotes, and prices on request only', 'Customer accounts, credit limits and statements', 'Receivables and overdue balances' ), ), ); /** The work of separate single-purpose plugins that Pro does, with their typical yearly price for one site (October 2026). */ private const SEPARATE = array( array('SEO toolkit', 99), array('Cart reminder emails', 99), array('Gift cards', 99), array('Google and Meta product feeds', 119), array('Reviews and product questions', 60), array('Bulk product editor', 199) ); /** What every paid plan brings to the panel beyond the screenshots: a dashicon, a title and a sentence (TABLE's Pro rows). */ private const HIGHLIGHTS = array( array('superhero-alt', 'AI Studio', 'Product descriptions, alt texts and campaign copy in your store\'s language, from your own OpenAI account. Every change can be undone.'), array('search', 'SEO Center', 'Audit your store, fix titles and descriptions in bulk, add structured data and sitemaps, and follow Search Console and rankings.'), array('edit-page', 'Bulk editing', 'Change many products at once, with a preview of every change before it is saved.'), array('category', 'Smart categories and bundles', 'Categories that fill themselves by your rules, and sets of products sold together at a set price.'), array('rss', 'Product feeds', 'Your products in Google Merchant Center and Meta catalogues, kept up to date by themselves.'), array('groups', 'Team roles', 'Store owner, staff and cashier roles, with separate permissions for money and approvals.'), array('update', 'Automations', 'Move paid orders to preparation, remind you of low stock and email tracking numbers to customers.'), array('bell', 'Store notifications', 'New orders and low stock by email or on your phone, for each person who turns them on.'), array('backup', 'Settings backup', 'Save StoreRuler\'s settings to a file and bring them back on this site or another one.') ); /** What only the carrier integrations give: a package without them (Edition) does not offer it. */ private const CARRIER_ONLY = array( 'Shipping labels and live parcel tracking', 'Supported Geliver, Sendcloud, EasyPost and Parcel2Go connections', 'Live parcel status on the order tracking page', 'Can StoreRuler buy shipping labels for me?' ); /** What only the theme gives: a package without it (Edition) neither shows nor offers it. */ private const THEME_ONLY = array( 'The StoreRuler theme and design studio', 'The StoreRuler theme, with ready-made designs for every kind of store', 'Design studio with a live preview, sections and menus', 'Do I have to use the StoreRuler theme?' ); /** A package without the theme has these services but not all of what the row says, so the row says what it has. */ private const THEME_WORDS = array( 'AI images for your store and AI help with customer replies' => 'AI help with customer replies', 'Gift wrap, pre-orders, price alerts and a free-shipping bar' => 'Gift wrap and pre-orders', 'Product readiness and "Complete the look"' => 'Product readiness', ); /** The work areas with a real screenshot: the area, the plan it needs, a title, a sentence, four points and the picture (file, size, alt text). */ private const FEATURES = array( array( 'area' => 'Sales and orders', 'plan' => 'pro', 'title' => 'Manage the work around each order', 'text' => 'Your order desk, preparation stages and printouts stay in Free. Pro adds manual orders, previewed editing, collection follow-up and returns on the same WooCommerce records.', 'points' => array( 'Manual orders from the phone, WhatsApp and social media', 'Order editing with a preview', 'Payments, collections and payment notices from customers', 'Returns and exchanges from the original order' ), 'shot' => array( 'pro-shot-orders.png', 1200, 750, 'Order desk with channels, stages and payment badges; example data' ), ), array( 'area' => 'Marketing', 'plan' => 'pro', 'title' => 'Bring shoppers back', 'text' => 'Run web campaigns, remind shoppers who agreed to it about their cart, sell gift cards and send your products to Google and Meta.', 'points' => array( 'Web campaigns with rules, dates and a test cart', 'Cart reminder emails, only for shoppers who ticked the box', 'Gift cards and store credit', 'Reviews, product questions and bulk SEO' ), 'shot' => array( 'pro-shot-marketing.png', 1200, 750, 'Web campaigns with their offer, products, dates and status: paused, scheduled or active' ), ), array( 'area' => 'Reports', 'plan' => 'pro', 'title' => 'See sales, money and stock in detail', 'text' => 'A report library by period, channel, product, customer and stock, next to the money that came in and what is still due.', 'points' => array( 'Sales by channel, payment method, place, product, brand and customer group', 'Stock levels, stock movements and low stock', 'Payments, collections and the till report for a day or a period', 'Accounting exports: sales, refunds, tax by rate and payments' ), 'shot' => array( 'pro-shot-reports.png', 1200, 750, 'The report library with sales, product, customer and stock reports' ), ), array( 'area' => 'Shipping', 'plan' => 'pro', 'title' => 'Work with your carrier\'s official integration', 'text' => 'Connect a supported shipping account. View existing labels and tracking in the order. Paid label purchases with Sendcloud or Parcel2Go require the owner\'s permission for that account; it is off by default. Other providers may require purchase in their own portal. Shipping charges are separate.', 'points' => array( 'Print an existing label from the order', 'Follow the parcel\'s tracking on the order', 'Printable address labels for any carrier', 'Live parcel status on your "Where is my order?" page' ), 'shot' => array( 'pro-shot-shipping.png', 1200, 750, 'The Integrations page with a tile for each shipping provider and its status' ), ), array( 'area' => 'Stock & Purchasing', 'plan' => 'plus', 'title' => 'Know what to buy and what is on the shelf', 'text' => 'See what is running low, order from your suppliers and add the goods to stock only when they arrive.', 'points' => array( 'A buying list from stock on hand and what is already on order', 'Purchase orders by WhatsApp or email, with partial deliveries', 'An average cost that includes shipping and other purchase costs', 'Stock counts with barcode scanning, blind counts and approval' ), 'shot' => array( 'pro-shot-purchasing.png', 1200, 750, 'The buying list by supplier, with each product\'s stock, incoming quantity and sales in the last 30 days' ), ), array( 'area' => 'Physical Store', 'plan' => 'plus', 'title' => 'Sell at the counter and close the day', 'text' => 'Quick Sale takes the barcode, the payment and the receipt on one screen. Your shop and your website sell from the same stock.', 'points' => array( 'Scan barcodes and take cash, card or both in one sale', 'Count the drawer and close the register day', 'Returns and exchanges from the receipt or the original order', 'In-store prices, and price and barcode labels' ), 'shot' => array( 'pro-shot-till.png', 1200, 750, 'The cash register for two weeks: net sales, receipts, register count differences, net sales by day and how customers paid' ), ), array( 'area' => 'B2B', 'plan' => 'business', 'title' => 'Sell to trade customers on their own terms', 'text' => 'Give your dealers their own prices, payment terms and credit limits, and send quotes that become orders.', 'points' => array( 'Dealer groups with wholesale and quantity prices', 'Payment terms, credit limits and account statements', 'Quotes, and products that show their price on request only', 'Receivables and overdue balances in one place' ), 'shot' => array( 'pro-shot-b2b.png', 1200, 750, 'The B2B overview with dealers, open quotes, receivables and overdue balances' ), ) ); /** * StoreRuler's languages whose screenshots were taken with StoreRuler in that language (the owner's request of 5 Oct 2026; * German and Spanish with the demo store's data in that language and in euros, 9 Oct 2026): pro-shot--.png. * Every other language shows the English set. */ public const SHOT_LANGUAGES = array('tr', 'de', 'es'); /** * The StoreRuler theme (StoreRuler Pro). Owner, 26 Oct 2026: the panel stays the page's first subject, and the theme shows * itself clearly after it, with its features in full. The facts are the theme's (theme/storeruler: inc/kits.php, the * changelog): 13 style kits, "ready-made designs" on this page, with three colour presets each; Plaza (meydan, 24 Oct 2026) * is the newest. 'kits' is a selection, named as the theme names them; 'features' and 'studio' say what every design does. * The pictures are the live demo stores (the official website's demos-7/ and kits-12/ shots) as palette PNGs, because the free zip * takes PNG and SVG only (tests/wporg-compliance.php): file, width, height and the English alt text ('' for a picture whose * name is printed under it). They show the same Turkish demo stores in every language. */ private const THEME = array( 'count' => 13, 'phone' => array( 'pro-theme-plaza-phone.png', 300, 649, 'The Plaza design on a phone, with category chips and a tab bar at the bottom' ), 'plaza' => array( 'title' => 'Plaza: the big-shop look, under your own name', 'text' => 'For stores with many categories. Shoppers find their way as they do in the big online shops, with your logo, colours and products.', 'points' => array( 'A search bar in the middle, with account, favourites and cart beside it', 'Round category stories and product rails on soft panels', 'Product cards with a heart, stars, a discount box and a free-shipping badge', 'Filters beside the products, and a tab bar at the bottom of phones' ), 'colors' => array( 'Coral' => 'coral', 'Pomegranate' => 'pomegranate', 'Ocean' => 'ocean', ), 'shots' => array(array( 'pro-theme-plaza.png', 960, 600, 'The Plaza design on a computer: a demo store\'s home page with a search bar, round category stories and banners' ), array( 'pro-theme-plaza-rails.png', 960, 600, 'The Plaza design: best sellers on a soft panel, with hearts, stars and discounts on the cards' )), ), 'features' => array( array('format-image', 'Product pages that sell', array( 'A photo gallery that follows the finger on phones', 'Colour swatches, a size guide and a "Buy now" button', 'Add to cart without loading the page again', '"Complete the look" sets, product videos and unit prices' )), array('grid-view', 'Category pages', array( 'Filters beside the products, in a bar or in a drawer', 'More products load by themselves as shoppers scroll', 'Quick view and quick add on product cards', 'Sale, best-seller and free-shipping badges from real data' )), array('cart', 'Cart', array( 'A cart drawer that shows what is left for free shipping', 'The order summary stays in view beside the products', 'Suggestions under the cart lines', 'Instant product search from every page' )), array('layout', 'Home page sections', array( 'Banner grids, slideshows and shoppable images', 'Category rails, product rails and collection showcases', 'Reviews, FAQs, coupons and a store highlights band', 'Product comparison, a product finder and recipes' )), array('smartphone', 'Made for phones', array( 'A tab bar at the bottom: home, categories, favourites, cart and account', 'Favourites that shoppers keep while they browse', 'Photos to swipe, and no buttons a thumb taps by mistake', 'Sign-in and account pages designed in every style' )), array('performance', 'Speed', array( 'The first screen draws from inline styles', 'Only the design in use loads its styles and fonts', 'Lighter fonts, with Turkish letters in a small file of their own', 'The page\'s main photo loads first; WebP copies on request' )), array('search', 'Search engines', array('A meta description on every page when no SEO plugin writes one', 'One clean breadcrumb trail with its structured data', 'An active SEO plugin keeps its own tags and trail')), array('welcome-write-blog', 'Blog and pages', array( 'Product cards between the paragraphs of an article', 'A sidebar with the article\'s products and the most read posts', 'Tidy comments under each article', 'Information pages in the same design' )), array('money-alt', 'Selling rules built in', array( 'The lowest price of the last 30 days, for the EU and Turkey rules', 'Instalment messages, only what your payment provider offers', 'A same-day dispatch cut-off time', 'Gift wrap, pre-orders and price alerts' )) ), 'kits' => array( 'podyum' => array('Runway', 'Fashion, edge to edge'), 'butik' => array('Boutique', 'Calm and elegant, for jewellery'), 'isik' => array('Glow', 'Light and fresh, for beauty'), 'yasam' => array('Living', 'Large photos, for furniture and home'), 'kiler' => array('Pantry', 'Warm and appetising, for food'), 'devre' => array('Circuit', 'Key specs first, for electronics'), 'sahne' => array('Stage', 'Cinematic and image-first'), 'atolye' => array('Workshop', 'Soft colour fields and rounded panels'), ), 'studio' => array( array('visibility', 'Live preview', 'See every change on your own pages before you publish it.'), array('excerpt-view', 'Sections you arrange', 'Banners, product rails, category stories, reviews and more, in the order you choose.'), array('admin-appearance', 'Your colours and fonts', 'Start from a colour set, then change any colour or font.'), array('columns', 'Layout variants', 'Choose how the header, the search box, the product cards and the gallery look.'), array('tablet', 'Every device', 'Show or hide each section on phones, tablets or computers.'), array('backup', 'Drafts and history', 'Work in a draft, see a summary before you publish, and go back to an earlier version.') ), ); /** Every plan side by side: per work area, a feature and the first plan that has it (free, pro, plus or business). */ private const TABLE = array( 'Sales and orders' => array( array('Order desk with stages, filters and saved views', 'free'), array('Pick lists, packing slips, order summaries and CSV export', 'free'), array('Tracking messages by email or as a WhatsApp draft', 'free'), array('Manual orders from the phone, WhatsApp and social media', 'pro'), array('Returns and exchanges from the original order', 'pro'), array('Order editing with a preview', 'pro'), array('Payments, collections and payment notices from customers', 'pro'), array('Cash-on-delivery guard and risky-customer flags', 'pro'), array('Sales quotas and purchase limits', 'pro') ), 'Products' => array( array('Product list with quick edit and variation prices', 'free'), array('A simple product in one form', 'free'), array('Back-in-stock requests', 'free'), array('Products with options, shared stock and templates', 'pro'), array('Bulk editing with a preview', 'pro'), array('Smart categories, bundles, brands and catalog sorting', 'pro'), array('Product readiness and "Complete the look"', 'pro'), array('Catalog export', 'pro') ), 'Marketing' => array( array('Live carts and searches with no results', 'free'), array('Web campaigns with rules, dates and a test cart', 'pro'), array('Cart reminder emails, only for shoppers who ticked the box', 'pro'), array('Gift cards and store credit', 'pro'), array('Google and Meta product feeds', 'pro'), array('Reviews, product questions and review-request emails', 'pro'), array('Google Analytics, Tag Manager and the Meta Pixel', 'pro') ), 'SEO and AI' => array( array('URL redirects and the 404 report', 'free'), array('SEO health summary: your store\'s score and its top issues', 'free'), array('AI description and photo alt text for one product, with your own OpenAI key', 'free'), array('SEO Center: audit, fixes, structured data and sitemaps', 'pro'), array('Bulk SEO and search matches', 'pro'), array('Search Console and rank tracking', 'pro'), array('AI Studio: product texts and alt texts in bulk', 'pro'), array('AI images for your store and AI help with customer replies', 'pro') ), 'Store design' => array(array('The StoreRuler theme, with ready-made designs for every kind of store', 'pro'), array('Design studio with a live preview, sections and menus', 'pro'), array('Gift wrap, pre-orders, price alerts and a free-shipping bar', 'pro')), 'Shipping' => array( array('Carrier list, shipments and tracking numbers on the order', 'free'), array('Supported Geliver, Sendcloud, EasyPost and Parcel2Go connections', 'pro'), array('Printable address labels', 'pro'), array('An order tracking page for customers', 'free'), array('Live parcel status on the order tracking page', 'pro') ), 'Physical store and till' => array( array('Quick Sale with barcode scanning and split payments', 'plus'), array('Receipts, returns and exchanges at the counter', 'plus'), array('Cash register: opening float, drawer count and day close', 'plus'), array('In-store prices, and price and barcode labels', 'plus') ), 'Stock and purchasing' => array( array('Low-stock and slow-seller signals', 'pro'), array('A buying list from stock on hand and what is already on order', 'plus'), array('Suppliers and purchase orders by WhatsApp or email', 'plus'), array('Partial deliveries and an average cost with purchase costs', 'plus'), array('Stock counts with barcode scanning, blind counts and approval', 'plus'), array('Stock movements, costs and profit', 'plus'), array('Accounting exports', 'plus') ), 'B2B' => array( array('Dealer groups with wholesale and quantity prices', 'business'), array('Quotes, and products that show their price on request only', 'business'), array('Payment terms, credit limits and account statements', 'business'), array('Sales on account for regular customers', 'business'), array('Receivables and dealer reports', 'business') ), 'Reports, team and settings' => array( array('Home, Today and the sales report', 'free'), array('Activity log, email history and store health', 'free'), array('English, Turkish, German and Spanish', 'free'), array('Add StoreRuler to your phone\'s home screen', 'free'), array('Report library: sales, products, customers and stock', 'pro'), array('Team roles: store owner, staff and cashier', 'pro'), array('Automations and store notifications on your phone', 'pro'), array('Settings backup', 'pro') ), ); /** The free version's card beside the plans (the readme's free features, in three lines). */ private const FREE_FACTS = array('Order desk, printouts and shipments', 'Products, customers and live carts', 'No account needed'); /** What keeps working when a license ends (Lite.php: the free features, what the store owes its customers, the records). */ private const KEEPS = array( 'Every free feature, in full', 'Gift cards, store credit and returns for your customers', 'Your records: view and export them at any time', 'Your customers see no difference in your shop' ); /** The questions owners ask, answered from the code and the plans (the refund days come from OFFER). */ private const FAQ = array( array('Which plan is right for my store?', 'Pro if you sell online. Plus if you also sell at a counter, buy from suppliers or count stock. Business if you also sell to dealers on their own prices and payment terms.'), array('Do I have to use the StoreRuler theme?', 'No. The theme is optional: StoreRuler works with any WooCommerce theme. Switch to the StoreRuler theme when you want one of its designs; your products and pages stay as they are.'), // Freemius prorates a plan change made on the site that has the license (its proration docs, checked 20 Oct 2026): the // unused share of the last payment comes off the new plan's price and a new billing period starts that day. array('Can I change my plan later?', 'Yes. Upgrade here at any time and choose "Upgrade my current license" in the checkout: the unused part of your current period comes off the price, and the new plan\'s billing period starts that day. Your records and settings stay as they are.'), array('Does the free version stop working after a while?', 'No. The free version has no time limit and needs no account. What it does today stays free.'), array('Can I get my money back?', 'Yes. Within %s days of your purchase you can ask for a full refund. Your license then ends; your records stay.'), array('Do I have to move my data?', 'No. The paid plans are one plugin that runs instead of the free one, on the same StoreRuler data and settings and the same WooCommerce orders, products and customers.'), array('Does AI cost extra?', 'Not from StoreRuler. The AI features use your own OpenAI account, so you pay OpenAI only for what you use, and you set a daily limit.'), array('How many sites does a license cover?', 'A license covers 1, 3 or 5 sites; you choose in the checkout. 3 sites cost the same as 2 and 5 sites the same as 3, billed yearly. Local installs on your own computer do not count.'), array('Can StoreRuler buy shipping labels for me?', 'Available label actions depend on the provider, account and country. Sendcloud and Parcel2Go purchases require the owner\'s permission for the account and a current connection check; permission is off by default. Other providers may require purchase in their own portal. Carrier charges are not part of the license.'), array('Who sells StoreRuler, and how do updates arrive?', 'StoreRuler is made by Eren Labs. Freemius runs the checkout and the licenses, and updates arrive on WordPress\'s Plugins screen like any other update.'), array('Which languages does StoreRuler speak?', 'English, Turkish, German and Spanish, like the free version. StoreRuler follows your WordPress language.'), ); public static function hooks() : void { // Freemius's own Upgrade entry leaves the menu while this page is offered (its page stays registered: the checkout // runs on it), and the Plugins screen's upgrade link points here instead of to it. storeruler_fs()->add_filter( 'is_submenu_visible', array(__CLASS__, 'freemius_menu'), 10, 2 ); if ( Licensing::configured() ) { add_filter( 'plugin_action_links_' . plugin_basename( STORESUIT_FILE ), array(__CLASS__, 'action_link') ); } } /** The store's plan: 'free' in the free build; in StoreRuler Pro its plan (Plans::tier(): 'lite', 'pro', 'plus' or 'business'). */ public static function current() : string { $tier = 'free'; return $tier; } /** * Whether the page is offered: always in the free build; in StoreRuler Pro while no license is active (it then explains * what the plans bring back and how to renew) and while a higher plan exists (Pro, Plus). */ public static function offered() : bool { $offered = true; return $offered; } /** 'renew' once a paid license ended, 'upgrade' with a plan below Business (StoreRuler Pro), else 'buy' (no trial: a money-back guarantee). */ public static function state() : string { $state = 'buy'; return $state; } public static function url() : string { return admin_url( 'admin.php?page=' . self::SLUG ); } /** Admin::menu(): the last entry of StoreRuler's submenu while offered; otherwise a hidden page whose address leads away. */ public static function menu() : void { $offered = self::offered(); $hook = add_submenu_page( ( $offered ? 'storesuit' : '' ), I18n::t( 'StoreRuler Pro' ), ( $offered ? self::label() : I18n::t( 'StoreRuler Pro' ) ), self::CAP, self::SLUG, array(__CLASS__, 'render') ); if ( $hook ) { add_action( 'load-' . $hook, array(__CLASS__, 'load') ); } } /** load-{page}: a store with Business is sent to its license. */ public static function load() : void { if ( !self::offered() ) { wp_safe_redirect( self::away() ); exit; } $GLOBALS['title'] = I18n::t( 'StoreRuler Pro' ); } /** Where a licensed store goes instead: Settings → License (LicensePanel), which shows the plan and every license task. */ public static function away() : string { return admin_url( 'admin.php?page=storesuit-settings#tab-license' ); } /** Guide::daily('help'): the page's row in the Help menu while it is offered. */ public static function help_row() : array { return array( 'StoreRuler Pro', self::SLUG, self::CAP, 'Compare the free version with StoreRuler\'s plans and see the prices.' ); } /** Freemius's is_submenu_visible filter: its Upgrade (pricing) entry leaves the menu while this page is offered. */ public static function freemius_menu( $visible, $id = '' ) { return ( $id === 'pricing' && self::offered() ? false : $visible ); } /** The Plugins screen's row: the upgrade link Freemius would add, to this page (only while Freemius runs, as its link did). */ public static function action_link( $links ) { if ( !is_array( $links ) || !self::offered() || !current_user_can( self::CAP ) ) { return $links; } $links['storesuit-pro'] = '' . esc_html( self::label() ) . ''; return $links; } /** The menu entry's and the Plugins screen link's words. */ public static function label() : string { $state = self::state(); if ( $state === 'renew' ) { return I18n::t( 'Renew StoreRuler Pro' ); } if ( $state === 'upgrade' ) { return I18n::t( 'Upgrade your plan' ); } return I18n::t( 'Upgrade to Pro' ); } /** * Where a plan's button goes, for someone who may open Freemius's pages (they ask manage_options): a billing cycle straight to * Freemius's checkout for that plan (its id in OFFER); a plan Freemius has no id for here opens Freemius's pricing page, never * another plan's checkout. Otherwise (Freemius off, or a shop manager) StoreRuler's page on the official website, the plugin * header's Plugin URI, where the plans are described. * The checkout gets the page's currency as Freemius's 'currency' parameter, in lower case, and the plan as 'plan_id': the * SDK's checkout_url( $billing_cycle, $is_trial, $extra ) merges $extra into the checkout's query, and its checkout frame passes * the query on to Freemius (vendor/freemius: class-freemius.php, class-fs-checkout-manager.php). An SDK whose checkout_url() * takes no extra parameters, or only get_upgrade_url(), opens the checkout as before, in Freemius's default currency. */ public static function checkout( string $cycle = 'annual', ?string $currency = null, string $plan = 'pro' ) : string { $fs = Licensing::fs(); if ( $fs && current_user_can( 'manage_options' ) ) { $cycle = ( $cycle === 'monthly' ? ( defined( 'WP_FS__PERIOD_MONTHLY' ) ? (string) constant( 'WP_FS__PERIOD_MONTHLY' ) : 'monthly' ) : (( defined( 'WP_FS__PERIOD_ANNUALLY' ) ? (string) constant( 'WP_FS__PERIOD_ANNUALLY' ) : 'annual' )) ); $id = (string) (self::OFFER['plans'][$plan]['id'] ?? ''); $extra = array( 'currency' => strtolower( self::currency_code( $currency ) ), ); if ( $id !== '' ) { $extra['plan_id'] = $id; } if ( $id === '' && $plan !== 'pro' ) { // No id for this plan yet: Freemius's own pricing page lists the plans it sells, at its prices. if ( method_exists( $fs, 'get_upgrade_url' ) ) { return add_query_arg( 'currency', 'usd', (string) $fs->get_upgrade_url( $cycle ) ); } return self::site(); } if ( method_exists( $fs, 'checkout_url' ) ) { if ( ( new \ReflectionMethod($fs, 'checkout_url') )->getNumberOfParameters() >= 3 ) { return (string) $fs->checkout_url( $cycle, false, $extra ); } return add_query_arg( $extra, (string) $fs->checkout_url( $cycle ) ); } if ( method_exists( $fs, 'get_upgrade_url' ) ) { return add_query_arg( 'currency', 'usd', (string) $fs->get_upgrade_url( $cycle ) ); } } return self::site(); } /** The official website's page of StoreRuler: the plugin header's Plugin URI (one address for the readme, the header and this page). */ public static function site() : string { $data = get_file_data( STORESUIT_FILE, array( 'uri' => 'Plugin URI', ) ); $uri = esc_url_raw( (string) ($data['uri'] ?? '') ); return ( $uri !== '' ? $uri : admin_url( 'admin.php?page=storesuit' ) ); } /** Admin::assets() for this page: its own stylesheet over StoreRuler's shell (no admin.js: the page needs no store data). */ public static function assets() : void { wp_enqueue_style( 'storesuit-pro-page', plugins_url( 'assets/pro-page.css', STORESUIT_FILE ), array('storesuit-premium'), STORESUIT_VERSION . '.' . filemtime( STORESUIT_DIR . 'assets/pro-page.css' ) ); } /** The confirmed offer is USD only. Arguments remain compatible with existing callers. */ public static function currency( ?string $store = null, ?string $locale = null ) : string { return self::DEFAULT_CURRENCY; } /** An offered currency: the one given when the plans are sold in it, else the page's (currency()). */ private static function currency_code( ?string $currency ) : string { return self::DEFAULT_CURRENCY; } /** A plan's monthly and yearly price (Pro's when none is named): array( 'monthly' => …, 'yearly' => … ). */ public static function prices( ?string $currency = null, string $plan = 'pro' ) : array { $prices = ( $plan === 'pro' ? self::OFFER['prices'][self::currency_code( $currency )] : self::OFFER['plans'][$plan] ?? self::OFFER['plans']['pro'] ); return array( 'monthly' => (float) $prices['monthly'], 'yearly' => (float) $prices['yearly'], ); } /** * An amount of the offer in a currency as StoreRuler's language writes it ("$32.50", "32,50 $"): the number by * I18n::number() (not WordPress's language files), the sign and its place by the catalog's "$%s", on one line (a no-break * space between them). */ public static function money( float $amount, string $currency ) : string { $number = I18n::number( $amount, ( floor( $amount ) === $amount ? 0 : 2 ) ); $text = I18n::format( '$%s', $number ); return str_replace( ' ', " ", $text ); } /** Months a yearly license saves against twelve monthly payments, in a plan's prices (0 when it saves none or not a whole month). */ public static function months_free( ?string $currency = null, string $plan = 'pro' ) : int { $prices = self::prices( $currency, $plan ); $monthly = $prices['monthly']; $saved = $monthly * 12 - $prices['yearly']; return ( $monthly > 0 && $saved > 0 && fmod( $saved, $monthly ) === 0.0 ? (int) ($saved / $monthly) : 0 ); } /** The fine print under the plans: the currency the prices are in. */ private static function currency_note( string $currency ) : string { return I18n::t( 'Prices are in US dollars. Freemius runs the checkout and the licenses.' ); } /** Every English text the page can show, for the catalog checks (tests/upgrade-page.php); the literals of render() are read from the source. */ public static function texts() : array { $texts = array(); foreach ( self::PLANS as $plan ) { array_push( $texts, $plan['for'], $plan['base'], ...$plan['adds'] ); } // The plans' names are names, never translated. foreach ( self::SEPARATE as $row ) { $texts[] = $row[0]; } foreach ( self::HIGHLIGHTS as $h ) { array_push( $texts, $h[1], $h[2] ); } foreach ( self::FEATURES as $f ) { array_push( $texts, $f['area'], $f['title'], $f['text'], $f['shot'][3], ...$f['points'] ); } $plaza = self::THEME['plaza']; array_push( $texts, $plaza['title'], $plaza['text'], self::THEME['phone'][3], ...$plaza['points'] ); array_push( $texts, ...array_keys( $plaza['colors'] ), ...array_column( $plaza['shots'], 3 ) ); foreach ( self::THEME['kits'] as $kit ) { array_push( $texts, $kit[0], $kit[1] ); } foreach ( self::THEME['features'] as $f ) { array_push( $texts, $f[1], ...$f[2] ); } foreach ( self::THEME['studio'] as $s ) { array_push( $texts, $s[1], $s[2] ); } foreach ( self::TABLE as $group => $rows ) { $texts[] = $group; foreach ( $rows as $row ) { $texts[] = $row[0]; } } array_push( $texts, 'Store features', ...array_values( self::THEME_WORDS ) ); foreach ( self::FAQ as $qa ) { array_push( $texts, $qa[0], $qa[1] ); } array_push( $texts, ...self::KEEPS, ...self::FREE_FACTS ); $row = self::help_row(); array_push( $texts, $row[0], $row[3] ); return array_values( array_unique( $texts ) ); } /** * The bundled screenshots in StoreRuler's language: its own set when SHOT_LANGUAGES has one (pro-shot--.png), * else the English set. Each: file name under assets/, width, height and the English alt text (the catalog translates it). */ public static function shots( ?string $locale = null ) : array { $language = strtolower( substr( (string) (( $locale ?: I18n::locale() )), 0, 2 ) ); $suffix = ( in_array( $language, self::SHOT_LANGUAGES, true ) ? '-' . $language : '' ); return array_map( static fn( $f ) => array_replace( $f['shot'], array( 0 => substr( $f['shot'][0], 0, -4 ) . $suffix . '.png', ) ), self::features() ); } /** The work areas this package has: the shipping area and its picture go with the carrier integrations. */ private static function features() : array { return ( Edition::has( Edition::CARRIERS ) ? self::FEATURES : array_values( array_filter( self::FEATURES, static fn( $f ) => $f['area'] !== 'Shipping' ) ) ); } /** Rows (a text or a row whose first cell is the text) without what this package does not carry. */ private static function carried( array $rows ) : array { $gone = array_merge( ( Edition::has( Edition::CARRIERS ) ? array() : self::CARRIER_ONLY ), ( Edition::has( Edition::THEME ) ? array() : self::THEME_ONLY ) ); $rows = ( $gone ? array_values( array_filter( $rows, static fn( $row ) => !in_array( ( is_array( $row ) ? $row[0] : $row ), $gone, true ) ) ) : $rows ); if ( Edition::has( Edition::THEME ) ) { return $rows; } return array_map( static function ( $row ) { if ( is_array( $row ) ) { $row[0] = self::THEME_WORDS[$row[0]] ?? $row[0]; return $row; } return self::THEME_WORDS[$row] ?? $row; }, $rows ); } /** A plan's name as the page writes it: "StoreRuler Pro", "StoreRuler Plus", "StoreRuler Business". */ private static function plan_name( string $plan ) : string { return 'StoreRuler ' . (self::PLANS[$plan]['name'] ?? 'Pro'); } /** The plan order: 'free' (or 'lite') first. */ private static function rank( string $plan ) : int { return array( 'free' => 0, 'lite' => 0, 'pro' => 1, 'plus' => 2, 'business' => 3, )[$plan] ?? 0; } public static function render() : void { if ( !current_user_can( self::CAP ) ) { return; } $state = self::state(); $current = self::current(); // The shell sets every page's .wrap full width; the page keeps its own measure inside it. echo '
'; $currency = self::currency(); // one currency for every amount on the page and for the checkout // A long page (owner, 26 Oct 2026): the panel first and in full, then the theme in full, then the plans. self::hero( $state, $current, $currency ); self::jump(); self::value( $currency ); self::adds(); self::highlights(); if ( Edition::has( Edition::THEME ) ) { self::theme(); } self::pricing( $state, $current, $currency ); self::compare( $current ); self::ends(); self::faq(); self::closing( $state, $currency ); echo '
'; } /** A button link; one to another site (the website, while Freemius is off) opens in a new tab and says so. */ private static function render_button( string $url, string $class, string $text ) : void { $host = (string) wp_parse_url( $url, PHP_URL_HOST ); $away = $host !== '' && $host !== (string) wp_parse_url( admin_url(), PHP_URL_HOST ); echo '' . esc_html( $text ) . (( $away ? ' ' . esc_html( I18n::t( '(opens in a new tab)' ) ) . '' : '' )) . ''; } /** A check mark; with a label, the label is what a screen reader says instead (the table's "Included"). */ private static function render_check( string $label = '' ) : void { echo '' . (( $label !== '' ? '' . esc_html( $label ) . '' : '' )); } /** One list item with a check mark and the text beside it. */ private static function render_check_item( string $text ) : void { echo '
  • '; self::render_check(); echo '' . esc_html( $text ) . '
  • '; } /** A plan's tag: "Pro", "Plus" or "Business" in its own colour. */ private static function render_tag( string $plan ) : void { echo '' . esc_html( self::PLANS[$plan]['name'] ?? 'Pro' ) . ''; } private static function admin_note() : void { if ( !current_user_can( 'manage_options' ) ) { echo '

    ' . esc_html( I18n::t( 'Only a site administrator can buy StoreRuler Pro.' ) ) . '

    '; } } /** A "New" badge. */ private static function render_new() : void { echo '' . esc_html( I18n::t( 'New' ) ) . ''; } /** A bundled picture of the theme (THEME): file under assets/, width, height and the English alt text. */ private static function render_picture( array $picture, bool $eager = false ) : void { list( $file, $width, $height, $alt ) = $picture; echo '' . esc_attr( ( $alt !== '' ? I18n::t( $alt ) : '' ) ) . ''; } /** The live demo stores: the official website's demos page (Turkish or English), where every design opens as a real store. */ private static function demos() : string { $site = self::site(); if ( !str_starts_with( $site, 'https://' ) ) { return $site; } return trailingslashit( $site ) . (( str_starts_with( I18n::locale(), 'tr' ) ? 'sektor-demolari/' : 'en/industry-demos/' )); } /** The first screen: the panel (the real order desk), with the StoreRuler theme on a phone beside it and a way down to it in a package that has the theme. */ private static function hero( string $state, string $current, string $currency ) : void { $from = self::prices( $currency ); $count = (string) self::THEME['count']; $theme = Edition::has( Edition::THEME ); echo '
    '; echo '

    ' . esc_html( ( $state === 'upgrade' ? I18n::format( 'You have %s', self::plan_name( $current ) ) : I18n::t( 'StoreRuler Pro · Plus · Business' ) ) ) . '

    '; echo '

    ' . esc_html( I18n::t( 'Run your whole store from one plugin' ) ) . '

    '; echo '

    ' . esc_html( ( Edition::has( Edition::CARRIERS ) ? I18n::t( 'Orders, products, marketing, SEO, AI and shipping in one place, and the till, stock and dealers when your business needs them. One plugin instead of ten, one bill instead of ten.' ) : I18n::t( 'Orders, products, marketing, SEO and AI in one place, and the till, stock and dealers when your business needs them. One plugin instead of ten, one bill instead of ten.' ) ) ) . '

    '; echo '
    '; self::render_button( '#ss-pro-pricing', 'ss-button primary ss-pro-cta ss-pro-cta-lg', ( $state === 'renew' ? I18n::t( 'Renew StoreRuler Pro' ) : I18n::t( 'See plans and prices' ) ) ); echo '' . esc_html( I18n::t( 'Compare every feature' ) ) . '
    '; echo '
      '; self::render_check_item( I18n::format( '%s-day money-back guarantee', (string) self::OFFER['refund_days'] ) ); self::render_check_item( I18n::t( 'Renewals cost the same as your first year' ) ); self::render_check_item( I18n::t( 'Your records stay if you stop' ) ); echo '
    '; if ( $theme ) { echo ''; self::render_new(); echo '' . esc_html( I18n::format( 'The StoreRuler theme, with %s ready-made designs', $count ) ) . ''; } self::admin_note(); echo '

    ' . esc_html( I18n::format( 'From %1$s a month, or %2$s a year', self::money( $from['monthly'], $currency ), self::money( $from['yearly'], $currency ) ) ) . '

    '; echo '
    '; self::screenshot( 0, true ); if ( $theme ) { echo '
    '; self::render_picture( self::THEME['phone'], true ); echo '
    '; } echo '
    '; } /** The page's parts, for a long page: the panel, the theme, the plans, every feature and the questions. */ private static function jump() : void { $parts = array( 'ss-pro-panel' => I18n::t( 'The panel' ), 'ss-pro-theme' => I18n::t( 'The theme' ), 'ss-pro-pricing' => I18n::t( 'Plans and prices' ), 'ss-pro-compare' => I18n::t( 'Every feature' ), 'ss-pro-faq' => I18n::t( 'Questions' ), ); if ( !Edition::has( Edition::THEME ) ) { unset($parts['ss-pro-theme']); } echo ''; } /** * The StoreRuler theme (StoreRuler Pro), after the panel and in full: its newest design, Plaza, a selection of the others, * what every design does for shoppers and the design studio that sets it up. */ private static function theme() : void { $plaza = self::THEME['plaza']; $count = (string) self::THEME['count']; echo '

    ' . esc_html( I18n::t( 'Store design' ) ) . ''; self::render_tag( 'pro' ); echo '

    ' . esc_html( I18n::format( 'The StoreRuler theme: %s ready-made designs', $count ) ) . '

    ' . esc_html( I18n::t( 'The panel runs your store; the StoreRuler theme shows it to your shoppers. Pick a design and a colour set, arrange the sections and publish. Your products, pages and menus stay when you switch designs.' ) ) . '

    '; echo '

    '; self::render_new(); echo esc_html( I18n::t( 'Our newest design' ) ) . '

    ' . esc_html( I18n::t( $plaza['title'] ) ) . '

    ' . esc_html( I18n::t( $plaza['text'] ) ) . '

      '; foreach ( $plaza['points'] as $point ) { self::render_check_item( I18n::t( $point ) ); } echo '
    ' . esc_html( I18n::t( 'Three colour sets' ) ) . '
      '; foreach ( $plaza['colors'] as $name => $key ) { echo '
    • ' . esc_html( I18n::t( $name ) ) . '
    • '; } echo '
    '; self::render_button( self::demos(), 'ss-button ss-pro-demo-link', I18n::t( 'Visit the live demo stores' ) ); echo '
    '; foreach ( $plaza['shots'] as $shot ) { echo '
    '; self::render_picture( $shot ); echo '
    '; } echo '
    '; echo '

    ' . esc_html( I18n::t( 'A design for every kind of store' ) ) . '

    ' . esc_html( I18n::format( '%s designs in all, each with three colour sets. A few of them:', $count ) ) . '

      '; foreach ( self::THEME['kits'] as $key => $kit ) { echo '
    • '; self::render_picture( array( 'pro-kit-' . $key . '.png', 480, 360, '' ) ); // Named by the heading under it. echo '

      ' . esc_html( I18n::t( $kit[0] ) ) . '

      ' . esc_html( I18n::t( $kit[1] ) ) . '

    • '; } echo '

    ' . esc_html( I18n::t( 'What every design does for your shoppers' ) ) . '

    ' . esc_html( I18n::t( 'The same features in all of them, whichever design you choose.' ) ) . '

      '; foreach ( self::THEME['features'] as $f ) { echo '
    • ' . esc_html( I18n::t( $f[1] ) ) . '

        '; foreach ( $f[2] as $point ) { self::render_check_item( I18n::t( $point ) ); } echo '
    • '; } echo '

    ' . esc_html( I18n::t( 'The design studio, in your panel' ) ) . '

    ' . esc_html( I18n::t( 'You set the theme up from StoreRuler, without code.' ) ) . '

      '; foreach ( self::THEME['studio'] as $s ) { echo '
    • ' . esc_html( I18n::t( $s[1] ) ) . '

      ' . esc_html( I18n::t( $s[2] ) ) . '

    • '; } echo '

    ' . esc_html( I18n::t( 'The theme is optional: StoreRuler works with any WooCommerce theme. The StoreRuler theme comes with every paid plan.' ) ) . ' ' . esc_html( I18n::t( 'Pictures from the demo stores, with example products.' ) ) . '

    '; } /** One plugin instead of ten: the separate plugins' typical yearly prices beside Pro's. */ private static function value( string $currency ) : void { $total = array_sum( array_column( self::SEPARATE, 1 ) ); $pro = self::prices( $currency ); echo '

    ' . esc_html( I18n::t( 'One plugin instead of ten' ) ) . '

    ' . esc_html( I18n::t( 'What the same work costs with separate single-purpose plugins, at typical yearly prices for one site.' ) ) . '

    '; echo '
      '; foreach ( self::SEPARATE as $row ) { echo '
    • ' . esc_html( I18n::t( $row[0] ) ) . '' . esc_html( I18n::format( 'about %s', self::money( (float) $row[1], $currency ) ) ) . '
    • '; } echo '

    ' . esc_html( I18n::t( 'Separately, every year' ) ) . '' . esc_html( I18n::format( 'about %s', self::money( (float) $total, $currency ) ) ) . '

    '; echo '

    ' . esc_html( I18n::t( 'StoreRuler Pro, every year' ) ) . '' . esc_html( self::money( $pro['yearly'], $currency ) ) . '

    '; echo '

    ' . esc_html( ( Edition::has( Edition::CARRIERS ) && Edition::has( Edition::THEME ) ? I18n::t( 'Plus AI Studio, the store design studio, shipping labels and reports. Typical prices of popular plugins seen on their own pages in October 2026; they may change.' ) : I18n::t( 'Plus AI Studio and reports. Typical prices of popular plugins seen on their own pages in October 2026; they may change.' ) ) ) . '

    '; } /** The plans: a monthly/yearly switch (two radio buttons, CSS shows the chosen prices and buttons) and the four cards. */ private static function pricing( string $state, string $current, string $currency ) : void { $pro_build = $current !== 'free'; $rank = self::rank( $current ); $licensed = $pro_build && $current !== 'lite'; $free = self::months_free( $currency ); echo '

    ' . esc_html( I18n::t( 'Pick the plan for how you sell' ) ) . '

    ' . esc_html( I18n::t( 'Each plan has everything of the plan before it. Move up whenever your business grows.' ) ) . '

    '; // The switch: the inputs come first so the plans that follow can be styled by the chosen one (no script). echo ''; echo '
    '; echo '
    '; // The free version first, at its price: what the store keeps whatever it chooses (the free build says it is the one in // use). A store with a paid plan is choosing between plans, so it is left out there. if ( !$licensed ) { echo '

    ' . esc_html( I18n::t( 'Free' ) ) . '

    ' . (( !$pro_build ? '' . esc_html( I18n::t( 'Your current version' ) ) . '' : '' )) . '
    '; echo '

    ' . esc_html( I18n::t( 'To get started' ) ) . '

    ' . esc_html( self::money( 0, $currency ) ) . ' ' . esc_html( I18n::t( 'no time limit' ) ) . '

    ' . esc_html( I18n::t( 'StoreRuler without a plan' ) ) . '

      '; foreach ( self::FREE_FACTS as $fact ) { self::render_check_item( I18n::t( $fact ) ); } echo '
    '; } foreach ( self::PLANS as $key => $plan ) { $prices = self::prices( $currency, $key ); $mine = $licensed && $current === $key; $below = $licensed && self::rank( $key ) < $rank; $class = 'ss-pro-plan is-' . $key . (( $key === 'plus' ? ' is-featured' : '' )) . (( $mine ? ' is-current' : '' )) . (( $below ? ' is-below' : '' )); echo '

    ' . esc_html( $plan['name'] ) . '

    '; if ( $mine ) { echo '' . esc_html( I18n::t( 'Your plan' ) ) . ''; } elseif ( $key === 'plus' ) { echo '' . esc_html( I18n::t( 'Most popular' ) ) . ''; } echo '

    ' . esc_html( I18n::t( $plan['for'] ) ) . '

    '; echo '

    ' . esc_html( self::money( $prices['yearly'], $currency ) ) . ' ' . esc_html( I18n::t( 'per year' ) ) . '

    '; echo '

    ' . esc_html( self::money( $prices['monthly'], $currency ) ) . ' ' . esc_html( I18n::t( 'per month' ) ) . '

    '; echo '

    ' . esc_html( I18n::format( '%s a month, billed once a year', self::money( round( $prices['yearly'] / 12, 2 ), $currency ) ) ) . '

    ' . esc_html( I18n::t( 'Billed every month' ) ) . '

    '; echo '

    ' . esc_html( I18n::t( $plan['base'] ) ) . '

      '; foreach ( self::carried( $plan['adds'] ) as $add ) { self::render_check_item( I18n::t( $add ) ); } echo '
    '; if ( $mine ) { echo '

    ' . esc_html( I18n::t( 'Your current plan' ) ) . '

    '; } elseif ( !$below ) { $words = ( $state === 'upgrade' ? I18n::format( 'Upgrade to %s', $plan['name'] ) : I18n::format( 'Choose %s', $plan['name'] ) ); $primary = ( $key === 'plus' ? ' primary' : '' ); self::render_button( self::checkout( 'annual', $currency, $key ), 'ss-button ss-pro-plan-cta is-yearly' . $primary, $words ); self::render_button( self::checkout( 'monthly', $currency, $key ), 'ss-button ss-pro-plan-cta is-monthly' . $primary, $words ); } echo '
    '; } echo '
    '; // Freemius prorates only a change to the license this site has activated: its checkout then asks whether to upgrade // that license or buy another one. A Pro copy with no active license may still own one, so it is told to activate it first. if ( $state === 'upgrade' ) { echo '

    ' . esc_html( I18n::t( 'In the checkout, choose "Upgrade my current license". The unused part of your current period comes off the price, and the new plan\'s billing period starts that day.' ) ) . '

    '; } elseif ( $pro_build ) { echo '

    ' . esc_html( I18n::t( 'Already bought a plan? Activate its license in Settings → License first; a higher plan bought after that upgrades the same license, and the unused part of its period comes off the price.' ) ) . '

    '; } if ( $state === 'buy' ) { // The money-back guarantee in place of a trial (Freemius's refund policy 'Flexible': any reason, within the days). echo '

    ' . esc_html( I18n::format( '%s-day money-back guarantee', (string) self::OFFER['refund_days'] ) ) . ' ' . esc_html( I18n::format( 'If StoreRuler is not right for your shop, ask for a full refund within %s days of your purchase.', (string) self::OFFER['refund_days'] ) ) . '

    '; } echo '

    ' . esc_html( self::currency_note( $currency ) ) . ' ' . esc_html( I18n::t( 'Choose 1, 3 or 5 sites in the checkout: 3 sites for the price of 2, 5 for the price of 3, billed yearly.' ) ) . '

    '; echo ''; self::admin_note(); echo '
    '; } /** What every paid plan brings without a screenshot: AI, SEO, the design studio, the team. */ private static function highlights() : void { echo '

    ' . esc_html( I18n::t( 'Built into every paid plan' ) ) . '

    ' . esc_html( I18n::t( 'The tools you would otherwise buy one by one, working on the same products, orders and customers.' ) ) . '

      '; foreach ( self::HIGHLIGHTS as $h ) { echo '
    • ' . esc_html( I18n::t( $h[1] ) ) . '

      ' . esc_html( I18n::t( $h[2] ) ) . '

    • '; } echo '
    '; } /** Local native image, with one eager first-screen image and lazy loading below the fold. */ private static function screenshot( int $index, bool $hero = false ) : void { list( $file, $width, $height, $alt ) = self::shots()[$index]; $image = plugins_url( 'assets/' . $file, STORESUIT_FILE ) . '?ver=' . STORESUIT_VERSION; // The picture is the link (its name says what it shows and that it opens in a new tab); the caption's "Enlarge the // screenshot" is the same link for the mouse, out of the tab order and hidden from screen readers so it is not named twice. // "Example data" is said once, under the last feature picture (adds()), not in every caption. echo '
    ' . esc_attr( I18n::t( $alt ) ) . '
    '; } private static function adds() : void { echo '

    ' . esc_html( I18n::t( 'The panel' ) ) . '

    ' . esc_html( I18n::t( 'Your whole store, screen by screen' ) ) . '

    ' . esc_html( I18n::t( 'Real StoreRuler screens, with the plan each one comes with.' ) ) . '

    '; foreach ( self::features() as $i => $f ) { if ( $i === 0 ) { continue; } // The order screenshot leads the hero. echo '

    ' . esc_html( I18n::t( $f['area'] ) ) . ''; self::render_tag( $f['plan'] ); echo '

    ' . esc_html( I18n::t( $f['title'] ) ) . '

    ' . esc_html( I18n::t( $f['text'] ) ) . '

      '; foreach ( $f['points'] as $point ) { self::render_check_item( I18n::t( $point ) ); } echo '
    '; self::screenshot( $i ); echo '
    '; } // Said once for all the pictures, not under each of them. echo '

    ' . esc_html( I18n::t( 'Example data; layout may vary by version.' ) ) . '

    '; } /** Every feature with the four plans side by side: a check from the first plan that has it. */ private static function compare( string $current ) : void { $columns = array( 'free' => I18n::t( 'Free' ), 'pro' => 'Pro', 'plus' => 'Plus', 'business' => 'Business', ); $yes = I18n::t( 'Included' ); $no = I18n::t( 'Not included' ); echo '

    ' . esc_html( I18n::t( 'Every feature, plan by plan' ) ) . '

    ' . esc_html( I18n::t( 'Everything in the free version stays free, with no time limit. Each plan adds to the one before it.' ) ) . '

    ' . esc_html( I18n::t( 'Choose a work area to compare every included feature.' ) ) . '

    '; $i = 0; foreach ( self::TABLE as $group => $rows ) { $rows = self::carried( $rows ); if ( $group === 'Store design' && !Edition::has( Edition::THEME ) ) { $group = 'Store features'; } $from = min( array_map( static fn( $row ) => self::rank( $row[1] ), $rows ) ); echo '
    ' . esc_html( I18n::t( $group ) ) . '' . esc_html( I18n::format( '%1$s features · from %2$s', I18n::number( count( $rows ) ), $columns[array_search( $from, array( 'free' => 0, 'pro' => 1, 'plus' => 2, 'business' => 3, ), true )] ) ) . '
    '; foreach ( $columns as $key => $name ) { echo ''; } echo ''; foreach ( $rows as $row ) { echo ''; foreach ( array_keys( $columns ) as $key ) { echo ''; } echo ''; } echo '
    ' . esc_html( I18n::format( 'The plans compared — %s', I18n::t( $group ) ) ) . '
    ' . esc_html( I18n::t( 'Feature' ) ) . '' . esc_html( $name ) . '
    ' . esc_html( I18n::t( $row[0] ) ) . ''; if ( self::rank( $key ) >= self::rank( $row[1] ) ) { self::render_check( $yes ); } else { echo '' . esc_html( $no ) . ''; } echo '
    '; ++$i; } echo '
    '; } private static function ends() : void { $grace = (string) self::OFFER['grace_days']; $steps = array(array(I18n::t( 'A paid license ends' ), I18n::format( 'Everything keeps working for %s more days, and StoreRuler shows the date this period ends.', $grace )), array(I18n::format( 'After %s days', $grace ), I18n::t( 'Pro tools become view-only: you can still view, export, finish and undo work. Starting new Pro work, such as a sale at the till or a new purchase order, needs Pro.' )), array(I18n::t( 'You renew' ), I18n::t( 'Everything is back at once, with your records as you left them.' ))); echo '

    ' . esc_html( I18n::t( 'If you stop, your records stay' ) ) . '

    ' . esc_html( I18n::t( 'StoreRuler never deletes your work when a license ends. This is what changes, step by step.' ) ) . '

      '; foreach ( $steps as $step ) { echo '
    1. ' . esc_html( $step[0] ) . '

      ' . esc_html( $step[1] ) . '

    2. '; } echo '

    ' . esc_html( I18n::t( 'What keeps working' ) ) . '

      '; foreach ( self::KEEPS as $keep ) { self::render_check_item( I18n::t( $keep ) ); } echo '

    ' . esc_html( I18n::t( 'Automations, cart reminders, review-request emails and product feed updates pause until Pro is back.' ) ) . '

    '; } private static function faq() : void { echo '

    ' . esc_html( I18n::t( 'Questions owners ask' ) ) . '

    '; foreach ( self::carried( self::FAQ ) as $qa ) { $answer = ( str_contains( $qa[1], '%s' ) ? I18n::format( $qa[1], (string) self::OFFER['refund_days'] ) : I18n::t( $qa[1] ) ); echo '
    ' . esc_html( I18n::t( $qa[0] ) ) . '

    ' . esc_html( $answer ) . '

    '; } echo '
    '; } private static function closing( string $state, string $currency ) : void { $prices = self::prices( $currency ); $monthly = self::money( $prices['monthly'], $currency ); $yearly = self::money( $prices['yearly'], $currency ); $copy = array( 'buy' => array(I18n::t( 'Start with the plan that fits today' ), I18n::format( 'From %1$s a month or %2$s a year', $monthly, $yearly ) . ' · ' . I18n::format( '%s-day money-back guarantee', (string) self::OFFER['refund_days'] )), 'upgrade' => array(I18n::t( 'Ready for the next step?' ), I18n::t( 'Upgrade now: the unused part of your current period comes off the price at checkout.' )), 'renew' => array(I18n::t( 'Pick up where you left off' ), I18n::t( 'Renew Pro and everything is back at once, with your records as you left them.' )), ); list( $title, $text ) = $copy[$state] ?? $copy['buy']; echo '

    ' . esc_html( $title ) . '

    ' . esc_html( $text ) . '

    '; self::render_button( '#ss-pro-pricing', 'ss-button primary ss-pro-cta ss-pro-cta-lg', ( $state === 'renew' ? I18n::t( 'Renew StoreRuler Pro' ) : I18n::t( 'See plans and prices' ) ) ); echo '
    '; } }