array('Start with today\'s work', array( 'Open the waiting work first: the To prepare or Awaiting payment tab, or a card in the list of what needs your attention.', 'Choose an order and open its next step; payment, preparation and delivery stay on the same order.', 'Before you create a record, search for it under Find anything in your store: order, customer, product, SKU, barcode or tracking number. Owners find the search under Store setup and business insights.', 'Finish the setup steps marked Continue; each one opens the screen where it is done.' ), array( array('Do I need to import my WooCommerce orders?', 'No. StoreRuler reads the existing store records.'), array('Does an order count prove payment arrived?', 'No. Check the payment status and remaining balance of each order.'), array('Which day is “today”?', 'The store\'s own day, in the time zone set in WordPress settings. The work queues count orders of every date and currency.'), array('Why don\'t I see money or settings?', 'Your role decides it: cashiers and store staff see a shorter list without financial figures.') )), 'storesuit-marketing' => array('Plan marketing from one place', array( 'Open Live carts to see carts being filled now and carts that stopped changing.', 'Open Waiting customers to see who waits for an out-of-stock product and what shoppers searched for without finding it.', 'Open Redirects and 404s to send old or broken addresses to the right page.', 'Read SEO health: open an example product under an issue to fix it, then choose Check again.' ), array( array('Do live carts send messages to shoppers?', 'No. Live carts is an internal view; no email or message is sent.'), array('Are back-in-stock requests reservations?', 'No. Each is a request for one email when the product is back, not an order and not reserved stock.'), array('Does SEO health change my products?', 'No. It only reads your published products and a few site settings. The result is kept for six hours; Check again counts again now.'), array('Where are the searches that found nothing?', 'On the Waiting customers page, under Searched but not found (30 days).') )), 'storesuit-orders' => array('Manage the order from one record', array( 'Find the order by number, customer, phone or email, or open Awaiting payment under Also track.', 'Open the order and follow its next step: payment, preparation and delivery are separate steps on the same order.', 'Prepare with Prepare / hand over next to each product, or tick several orders for a Pick list or Packing slips.', 'When the full amount is in your account, use Mark as paid on the order.' ), array( array('The customer says they paid. Is the order paid?', 'Only when the money has arrived. Check the bank or payment provider, then use Mark as paid: it records the full amount, moves the order to Processing (Completed if everything was shipped) with WooCommerce\'s usual email, and charges nothing.'), array('Can I ship an order that is not paid yet?', 'Yes, after you confirm that you ship (or hand it over) without waiting for payment. The same question comes when you ship or hand over a single product of that order. Cash on delivery orders are not asked.'), array('Why can\'t I pick or ship the full quantity?', 'Refunded units are taken off: picking, packing and shipping stop at the quantity left after refunds.'), array('Why is a control missing?', 'Your role may not have that StoreRuler capability.') )), 'storesuit-stock-alerts' => array('Use waiting demand without promising a sale', array( 'Choose Turn on requests, tick the box and choose Turn on; out-of-stock product pages then show an email box with a consent tick.', 'Read the list: Waiting shows how many shoppers wait for each product or option, with Waiting since and Stock.', 'Restock the product. With Email waiting shoppers automatically when a product is back in stock ticked, each waiting shopper gets one email.', 'If that box is not ticked, choose Email them now on a product that is in stock again; for one still out of stock, choose Open product.', 'Under Searched but not found (30 days), choose Check products to add the product or the missing word, or Dismiss to hide it until someone searches for it again.' ), array( array('Does a waiting request reserve stock?', 'No. It is a request for one email when the product is back, not an order or a sale.'), array('Why was nothing emailed?', 'Emails go out only while requests are on, the product is published and in stock, and the back-in-stock email is switched on in WooCommerce; use Edit the email.'), array('What happens when I turn requests off?', 'The email box leaves product pages and queued emails are cancelled. Waiting requests are kept; turning on again sends nothing until a product comes back in stock or you choose Email them now.'), array('Does Emails sent (30 days) prove delivery?', 'No. It counts emails handed to your mail system, not emails that reached an inbox or were opened.'), array('How long are requests kept?', 'A waiting request up to a year; sent and cancelled ones for 90 days.') )), 'storesuit-guide' => array('Start with the job you need', array( 'Store managers: read Your next setup step and press its Continue button; the checklist follows your real store records.', 'Choose a job card, such as Add a new product or Deliver an order, and press Start here →.', 'In a new, empty store, press Try with sample data to explore marked sample products, customers and orders.', 'When you are done, press Remove sample data, tick the confirmation and remove them for good.', 'Open Explain the terms simply when a word on a screen is unclear.' ), array( array('Will sample data email anyone or change stock?', 'No. Sample records send no email or webhook and move no stock, and shoppers cannot buy sample products.'), array('Why don\'t I see Try with sample data?', 'It appears only to store managers, in a store with no products, orders or customer accounts of its own; items in the bin count too.'), array('What if a real order uses a sample product?', 'Remove sample data is not offered until that order is gone, and the screen says why. Your own records are never changed.'), array('Does the checklist update by itself?', 'Yes. Each step is ticked from your real store records, such as a published product or an active payment method.') )), 'storesuit-products' => array('Keep one accurate product', array( 'Search by name, SKU or barcode, or press a check such as Out of stock or Missing price, before you add a product.', 'Open the product to see its prices, options, stock and product groups in one window.', 'For a simple product, change the name, SKU, barcode or prices and press Save.', 'For a product with options, press Review prices, read the preview and confirm before every option changes.', 'Use Quick add product for a new simple product; it is published at once with the shelf stock you enter.' ), array( array('Do I need to copy my products into StoreRuler?', 'No. StoreRuler uses the same WooCommerce product.'), array('Where do I change stock?', 'In the product\'s WooCommerce editor (Advanced editing), on its Inventory tab. This list shows stock but does not change it.'), array('Does Make inactive delete the product?', 'No. It turns the product into a draft, so shoppers cannot buy it. Publish now puts it back on sale.'), array('Where do I organise products into categories?', 'In WooCommerce, under Products → Categories.'), array('Why does Quick add product ask about shelf stock?', 'Above 0, you tick “I physically checked the shelf stock.” and your role needs the approval permission; if you have not counted yet, leave it at 0.') )), 'storesuit-customers' => array('Find a customer and keep the record right', array( 'Search by name, company, email or phone, or narrow the list with Account type and Sort by.', 'Press the customer\'s name or Open profile to see orders, balance, notes and contact preferences.', 'Use Edit customer details for name, company, phone and billing address, and Tags and contact for what the customer told you.', 'Press New customer to open an account; tick the email box only if the customer should get a link to set a password.' ), array( array('Why is a buyer missing from the list?', 'Only registered accounts are listed. A guest sale has no account: find its order in Sales.'), array('Does editing the details change past orders?', 'No. The new details are used for future sales; past orders and the account\'s sign-in email stay as they were.'), array('Does a contact preference send messages?', 'No. It is an internal record of what the customer said, not proof of marketing consent, and nothing is sent automatically.'), array('Which orders count as valid orders?', 'All orders except cancelled, failed and deleted ones, and web checkouts left unpaid at the payment step.') )), 'storesuit-carts' => array('See web carts before they become orders', array( 'Choose Turn on web-cart view, tick the confirmation and choose Turn on view.', 'Use Waiting over one hour for carts that stopped changing, and Active now for carts changed in the last hour.', 'On a registered customer\'s cart, choose Open customer profile to see their orders and contact details.', 'Check products, prices and stock again before you act; an unchanged cart is not a confirmed lost sale.' ), array( array('Does this page message shoppers?', 'No. No email, text, export, payment or stock change is triggered.'), array('Which carts appear?', 'Only carts changed after you turned the view on. A cart counts as waiting after one hour without a change and disappears after three days, or as soon as it is emptied or ordered.'), array('Can I see who an anonymous visitor is?', 'No. Guest names, email addresses, phone numbers and session tokens are not stored.'), array('What does Turn off remove?', 'Only StoreRuler\'s cart summaries; customers\' WooCommerce carts and orders stay as they are.') )), 'storesuit-redirects' => array('Send old addresses to the right page', array( 'Open Not found (404) to see the addresses visitors asked for in the last 30 days, most asked first.', 'Press Redirect next to an address, enter the New address, choose the Type and save.', 'For any other old address press New redirect; tick Also every address under it to move a whole folder.', 'Moving from another platform? Press Import CSV (old address, new address, code), check the import, then press Import … redirects.', 'Watch the Used column to see which redirects visitors still reach.' ), array( array('A redirect points to an address that is itself redirected. Must I fix it?', 'No. Visitors are sent straight to the end in one step, and the list shows Now goes to … under the new address. If the rules go round in a circle, the list says so and visitors see not found.'), array('Which type should I choose?', '301 when the page moved for good, 302 when it moved for now, 410 when it is gone for good and nothing replaces it (no new address needed).'), array('Can a redirect hide a page that still exists?', 'No. It is used only when the store finds no page at that address. Admin, login and REST API addresses cannot be redirected.'), array('I changed a category\'s address (slug). Do I need a redirect?', 'No. When a category, tag or brand address changes, the store adds one itself, marked Added when a category, tag or brand address changed.'), array('Does the not-found list keep visitor data?', 'No IP address or browser details are kept: only the address, the counts and the referring site\'s name. Entries unseen for 30 days are removed.') )), 'storesuit-order-tracking-settings' => array('Publish customer order tracking', array( 'Add the Order tracking block to a page, or paste [storesuit_order_tracking]; [storesuit_order_tracking title="no"] leaves out the heading.', 'To offer a chat link, tick Show a WhatsApp contact button and enter the Store WhatsApp number with country code.', 'Choose Save settings.', 'Publish the page and test it with a real order number and the email address used on that order.' ), array( array('What does a customer need?', 'The order number and the order\'s billing email together. After 5 wrong tries for one order number or one email, lookups stop for up to an hour.'), array('What does the customer see?', 'The order status and its steps, the tracking numbers your team recorded, and a partly hidden amount and city. Never names, street addresses, phone numbers or products.'), array('Why is my test order not found?', 'Orders placed with the store\'s own email address, anonymised orders, drafts and sample orders are never shown.'), array('When does it say Delivered?', 'Only when every product has shipped and delivery was confirmed or the order was handed over in person. An order that is only Completed in WooCommerce shows Completed.'), array('Does the WhatsApp button send order details?', 'No. It only opens a chat with your number when the customer chooses it.') )), 'storesuit-reports' => array('Read sales for a period', array( 'Choose Last 7 days, Last 30 days or Last 90 days; every card compares the period with the one before it.', 'If your orders are in more than one currency, pick the currency; amounts in different currencies are never added together.', 'Read Sales after refunds, Orders, Average order and Refunds, then the daily trend and the best-selling products.', 'Press Detailed report for your own dates, a channel, open balances and current inventory.' ), array( array('Which orders count as sales?', 'Orders placed in the period, with refunds taken off to date. Pending payment, failed and cancelled orders are left out; unpaid bank transfers and cash on delivery count.'), array('Is this my profit or my bank balance?', 'No. Amounts include tax and shipping and show what the orders were worth, not what they cost you or what reached your bank.'), array('Why did a past period\'s figure go down?', 'A refund is taken off the day its order was placed, even when it is made later.'), array('What is Cash on delivery to collect?', 'Cash-on-delivery orders of any date whose money has not been collected yet. It shows when the store offers cash on delivery or has such orders.') )), 'storesuit-reports:details' => array('Read sales for your own dates', array( 'Press Today, Last 7 days, Last 30 days or This month, or open Custom date and channel, set Start, End and a channel, and press Show report.', 'In Orders opened in the selected period, read each channel and currency: order value, refunded to date, after refunds and the current open balance.', 'Check Current inventory for the stocked products, the total quantity and any stock below zero.', 'To reuse the same dates and channel, open Saved report views and press Save this report.' ), array(array('Why does an old refund change this period?', 'Refunds and open balances show each order\'s state today, not only what happened in the period.'), array('Which orders are left out?', 'Pending payment, failed and cancelled orders. Amounts include tax and shipping; this is not a profit calculation.'), array('Does a saved report keep the figures?', 'No. It keeps the dates and the channel; the figures are read again each time you open it.'))), 'storesuit-settings' => array('Set up the store once', array( 'On General, check Store details; each card opens the screen where that detail is kept, such as the store address in WooCommerce.', 'On Shipping, write the carrier names you use most, one per line (up to 8), and press Save carrier names.', 'On Email, a WordPress administrator saves the store\'s email account, sends one test email, enters the six-digit code and enables the connection.', 'On System, read Store health: fix the Fix first items, then the Review ones, and press Check again.', 'On License, see which version you use; the free version has no time limit.' ), array( array('Where do I change the store address or bank account?', 'In WooCommerce. Store details only shows them; Open in WooCommerce goes to the right screen.'), array('Does saving a carrier name connect the carrier?', 'No. The names only appear first when you record a shipment; no carrier account is connected.'), array('Do I have to connect an email account?', 'No. WordPress keeps sending with its current settings. Connect an account only if emails do not arrive; Email history shows what was sent.'), array('Does Store health change my settings?', 'No. It only checks. The support file it downloads contains no passwords, customer or order details.'), array('Where are the activity log and email history?', 'In the Settings menu at the top of StoreRuler; Email history also opens from the Email tab.') )), 'storesuit-audit' => array('Find who changed a record', array( 'Leave All record types to see the newest activity first, or choose a Record type.', 'To follow one record, choose its type and enter its Record number, the number shown after # on that record.', 'Press Show activity: each line says what happened, when, and who did it.', 'Press Open record → to go to the order, product or customer; Load earlier activity shows older lines.' ), array( array('Can I change or delete a line?', 'No. The activity log only reads; nothing on this screen changes a record.'), array('What do System and Former team member mean?', 'System is a step StoreRuler or WordPress took by itself, such as an automation or a shopper\'s order. Former team member is an account that was deleted.'), array('Does it list changes made in WooCommerce\'s own screens?', 'Not all of them. It lists what StoreRuler records: actions on its screens, automatic steps and stock movements. For a change made only in WooCommerce, check that order\'s notes in WooCommerce.'), array('How long is activity kept?', 'There is no automatic time limit.') )), 'storesuit-mail-log' => array('Check which emails went out', array( 'Read the counts on All and Not sent for the last 30 days; press Not sent to see only the failures.', 'Type an email address or subject in the search box to find one message.', 'Read the Result column: Sent, or Not sent with the reason under it.', 'If emails are not sent, press Store email connection and send a test email, or ask your host about its mail settings.' ), array( array('Does Sent mean the customer received it?', 'No. It means your mail server accepted the message. Ask the customer to check their spam folder; delivery to the inbox is not guaranteed.'), array('Can I send an email again from here?', 'No. This list only shows the history. Send it again from where it started, for example the order\'s tracking message.'), array('What is kept, and for how long?', 'The date, recipient, subject, result and reason, never the message text. Entries are deleted after 30 days, and at most 5,000 are kept.'), array('Why is an email I expected not listed?', 'Only emails handed to the mail server are listed. An email that is turned off in WooCommerce → Settings → Emails is never sent, so it does not appear.') )), 'storesuit-modules' => array('Find any StoreRuler tool', array('Pick a journey card, such as Prepare a product for sale, and follow its numbered steps.', 'Under Looking for a special action?, type a job in the search box, for example redirect or awaiting payment.', 'Open a work area and choose a tool; its tools are grouped as Advanced actions, Reports and history, Exceptions and legacy records, and Settings and help.'), array(array('Why is a tool I know missing?', 'You see only the tools your account may open. Ask the store owner if you need one.'), array('Does this page change anything?', 'No. It only lists tools; each link opens that tool\'s own screen.'), array('My search found nothing. What now?', 'Type the job instead of a plugin name, for example redirect, live carts or awaiting payment.'))), ); public static function key( string $url ) : string { parse_str( (string) wp_parse_url( $url, PHP_URL_QUERY ), $q ); $key = ($q['page'] ?? '') . (( empty( $q['view'] ) ? '' : ':' . $q['view'] )) . (( empty( $q['tab'] ) ? '' : ':' . $q['tab'] )); // A single report of the library has its own help (storesuit-reports:library:); lookup() falls back to the library's. if ( $key === 'storesuit-reports:library' && !empty( $q['report'] ) && is_string( $q['report'] ) ) { $key .= ':' . sanitize_key( $q['report'] ); } return $key; } /** A table's row for a screen key: the key itself, then its first two parts (page:view), then the page. */ private static function lookup( array $table, string $key ) { $parts = explode( ':', $key ); return $table[$key] ?? $table[implode( ':', array_slice( $parts, 0, 2 ) )] ?? $table[$parts[0]] ?? null; } /** Every screen's task sentence in the user's language; $translate false keeps the English sources (describe() needs one). */ public static function tasks( bool $translate = true ) : array { // The free build's sentences; ProScreens::tasks() puts StoreRuler Pro's own wording of the shared pages back. $tasks = array( 'storesuit' => array('Start with today\'s work', 'Check the next action, then use one search for an order, customer, or product. Setup steps reflect your real store records.'), 'storesuit-orders' => array('Manage a sale from one record', 'Find the order and complete its next step. Payment, preparation, and delivery are separate steps on the same order.'), 'storesuit-products' => array('Keep one accurate product record', 'Check the name, sale price, stock, and options. StoreRuler edits the same product your WooCommerce store sells.'), 'storesuit-customers' => array('See the complete customer picture', 'Find a customer to review orders, unpaid balances, and contact history. Keep customer notes factual and necessary.'), 'storesuit-marketing' => array('Plan marketing from one place', 'Open live carts, waiting customers, searches without results, and URL redirects without leaving StoreRuler.'), 'storesuit-reports' => array('Compare the same period and currency', 'Use reports to see sales, refunds, sales channels, and best sellers. These are business views, not legal invoices.'), 'storesuit-carts' => array('See web carts before they become orders', 'Turn on the web-cart view to see carts being filled now and carts left unchanged. Nothing is sent to shoppers.'), 'storesuit-stock-alerts' => array('Turn waiting demand into restock decisions', 'See who waits for out-of-stock products and what shoppers could not find. Each waiting shopper can get one email when stock returns.'), 'storesuit-redirects' => array('Send old addresses to the right page', 'See the addresses visitors could not find, redirect them, or import a list from your old platform. A chain of redirects is followed in one step.'), 'storesuit-order-tracking-settings' => array('Let customers check their order', 'Add the order tracking block or shortcode to a page. Customers enter the order number and email to see its status.'), 'storesuit-settings' => array('Set up the store\'s defaults', 'Each tab holds one part of the setup: store details, shipping, email, store health and your license.'), 'storesuit-audit' => array('See who changed what and when', 'Filter by record type and number to see the StoreRuler activity of one order, product or customer. Nothing here changes a record.'), 'storesuit-mail-log' => array('Check which emails went out', 'See the emails your site handed to its mail server in the last 30 days, and why one was not sent.'), 'storesuit-modules' => array('Find any StoreRuler tool', 'Follow a work journey step by step, or search every tool by the job it does.'), 'storesuit-guide' => array('Start with the job you need', 'Choose a common job to open the right screen. Store managers also see the next setup step.'), 'storesuit-reports:details' => array('Read sales for your own dates', 'Choose dates and a channel to see orders, refunds, open balances and current inventory.'), ); if ( !$translate ) { return $tasks; } foreach ( $tasks as &$task ) { $task = array(I18n::t( $task[0] ), I18n::t( $task[1] )); } unset($task); return $tasks; } /** Short menu names (0.62.6, owner: "the tools show with the old long names"); the task sentence stays the description. */ private const SHORT = array(); // Every short menu name is a Pro tool's: ProScreens::short(). /** A tool's short menu name in the user's language, or an empty string. */ public static function short( string $url ) : string { $short = self::SHORT; $name = (string) (self::lookup( $short, self::key( $url ) ) ?? ''); return ( $name !== '' ? I18n::t( $name ) : '' ); } public static function describe( string $url ) : array { // Only the task asked for is translated: the menus describe each of their entries, and translating every task for // each of them read the language about 10,000 times on a StoreRuler screen. $tasks = self::tasks( false ); $key = self::key( $url ); $task = self::lookup( $tasks, $key ); return ( $task === null ? array() : array(I18n::t( $task[0] ), I18n::t( $task[1] )) ); } /** The help for a screen (title, steps, answers) in the user's language, or an empty array. */ public static function help( string $url ) : array { $table = self::HELP; $help = self::lookup( $table, self::key( $url ) ); if ( !$help ) { return array(); } return array( 'title' => I18n::t( $help[0] ), 'steps' => array_map( array(I18n::class, 't'), $help[1] ), 'faq' => array_map( static fn( $f ) => array(I18n::t( $f[0] ), I18n::t( $f[1] )), $help[2] ?? array() ), ); } public static function daily( string $area ) : array { // The free build's descriptions; ProScreens::daily() puts StoreRuler Pro's own wording of the shared rows back. $all = array( 'sales' => array(array( 'Orders', 'storesuit-orders', 'storesuit_orders', 'Prepare, deliver, and resolve orders.' ), array( 'Shipping', 'storesuit-orders&queue=fulfil', 'storesuit_fulfil', 'Pick and pack orders and record packages handed over.' ), array( 'Orders awaiting payment', 'storesuit-orders&queue=payment', 'storesuit_finance', 'Follow orders with money still due.' )), 'products' => array(array( 'Products', 'storesuit-products', 'manage_woocommerce', 'Find products and review price and stock.' ), array( 'Add Product', 'storesuit-products&new=1', 'manage_woocommerce', 'Create a product with or without options.' )), 'customers' => array(array( 'Customer Centre', 'storesuit-customers', 'manage_woocommerce', 'See orders, balances, and customer history.' )), 'marketing' => array(array( 'Live carts', 'storesuit-carts', 'manage_woocommerce', 'See carts shoppers are filling now and carts left unchanged.' ), array( 'Waiting customers', 'storesuit-stock-alerts', 'manage_woocommerce', 'See who waits for out-of-stock products and email them once when stock is back.' ), array( 'Redirects and 404s', 'storesuit-redirects', 'manage_woocommerce', 'See the pages visitors could not find (404) and send old addresses to the right page.' )), 'reports' => array(array( 'Reports', 'storesuit-reports', 'manage_woocommerce', 'Compare sales, refunds, sales channels, and best sellers.' )), 'help' => array(array( 'Quick actions', 'storesuit-guide', 'storesuit_orders', 'Start a common task in one step.' ), array( 'All features', 'storesuit-modules', 'manage_woocommerce', 'See every StoreRuler tool in one place.' )), 'settings' => array(array( 'Settings', 'storesuit-settings', 'manage_woocommerce', 'Set up the store and its daily defaults.' ), array( 'Activity log', 'storesuit-audit', 'manage_woocommerce', 'See who changed what and when.' ), array( 'Email history', 'storesuit-mail-log', 'manage_woocommerce', 'See which emails the store sent and why one was not sent.' )), ); // StoreRuler Pro's page, last in Help, while it is offered (ProPage). if ( ProPage::offered() ) { $all['help'][] = ProPage::help_row(); } $rows = array_values( array_filter( $all[$area] ?? array(), static function ( $r ) { $allowed = current_user_can( $r[2] ); return $allowed; } ) ); foreach ( $rows as &$row ) { $row[0] = I18n::t( $row[0] ); if ( isset( $row[3] ) ) { $row[3] = I18n::t( $row[3] ); } } unset($row); return $rows; } public static function intro() : void { if ( !Design::context() ) { return; } // Read-only: the page being shown chooses its help text; each of its three parameters is read and sanitized alone. $args = array(); foreach ( array( 'page', 'view', 'tab', 'report' ) as $key ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only: chooses the help text of the page being shown. if ( isset( $_GET[$key] ) && is_string( $_GET[$key] ) ) { $args[$key] = sanitize_key( wp_unslash( $_GET[$key] ) ); } // phpcs:enable WordPress.Security.NonceVerification.Recommended } $url = add_query_arg( $args, admin_url( 'admin.php' ) ); $task = self::describe( $url ); $help = self::help( $url ); if ( !$task && !$help ) { return; } // On a line of its own: Freemius's reprint moves a note at the end of a line down, where it no longer covers this one. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped by section(). echo self::section( $url, $task, $help ); // Tabs and views that change the address without a new page (B2B tabs, report library, settings…) take their own help // along: every other help of this page is sent once, and the script below puts the one for the new address in place. $page = $args['page'] ?? ''; $variants = array(); if ( $page !== '' && ($GLOBALS['pagenow'] ?? '') === 'admin.php' ) { $table = self::HELP; foreach ( array_unique( array_merge( array_keys( $table ), array_keys( self::tasks( false ) ) ) ) as $key ) { if ( $key !== $page && strpos( $key, $page . ':' ) !== 0 ) { continue; } // The third part is a tab, or on the report library the report (Guide::key()). $parts = explode( ':', $key ); $third = ( $page === 'storesuit-reports' && ($parts[1] ?? '') === 'library' ? 'report' : 'tab' ); $variant = add_query_arg( array( 'page' => $page, 'view' => $parts[1] ?? false, $third => $parts[2] ?? false, ), admin_url( 'admin.php' ) ); if ( self::key( $variant ) !== $key ) { continue; } // A key whose middle part is a tab, not a view: no address of this page gives it. $variants[$key] = self::section( $variant, self::describe( $variant ), self::help( $variant ) ); } } if ( count( $variants ) > 1 ) { wp_print_inline_script_tag( wp_json_encode( $variants, JSON_HEX_TAG | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE ), array( 'type' => 'application/json', 'id' => 'ss-job-help-variants', ) ); wp_print_inline_script_tag( "(function(){var d=document.getElementById('ss-job-help-variants');if(!d)return;var v;try{v=JSON.parse(d.textContent)}catch(e){return}function key(){var q=new URLSearchParams(location.search),k=(q.get('page')||'').toLowerCase();if(q.get('view'))k+=':'+q.get('view').toLowerCase();if(q.get('tab'))k+=':'+q.get('tab').toLowerCase();else if(k==='storesuit-reports:library'&&q.get('report'))k+=':'+q.get('report').toLowerCase();return k}function show(){var k=key(),p=k.split(':'),c=[k,p.slice(0,2).join(':'),p[0]].filter(function(x){return v[x]})[0],el=document.querySelector('.ss-job-help');if(!c||!el||el.getAttribute('data-sr-help-key')===c)return;var t=document.createElement('div');t.innerHTML=v[c];if(t.firstElementChild)el.replaceWith(t.firstElementChild)}['pushState','replaceState'].forEach(function(m){var o=history[m];history[m]=function(){var r=o.apply(this,arguments);show();return r}});addEventListener('popstate',show)})();" ); } } /** The help box of one address: its task sentence, steps and answers (escaped here). */ private static function section( string $url, array $task, array $help ) : string { if ( !$task && !$help ) { return ''; } $steps = $help['steps'] ?? array(I18n::t( 'Choose the product or record you will work on.' ), I18n::t( 'Check the quantity, price, and scope. Review the preview when available.' ), I18n::t( 'Finish with Save or Approve, then wait for the success message.' )); $html = '
' . esc_html( $task[0] ?? $help['title'] ) . '' . (( ($task[1] ?? '') !== '' ? '

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

' : '' )) . '
' . esc_html( I18n::t( 'Help with steps and terms' ) ) . '
    '; foreach ( $steps as $step ) { $html .= '
  1. ' . esc_html( $step ) . '
  2. '; } $html .= '
'; if ( !empty( $help['faq'] ) ) { $html .= '
'; foreach ( $help['faq'] as $answer ) { $html .= '
' . esc_html( $answer[0] ) . '
' . esc_html( $answer[1] ) . '
'; } $html .= '
'; } else { $html .= '

' . esc_html( I18n::t( 'Stock: the quantity you have. SKU: the code you use to find a product. Product option: a colour or size. Child category: a smaller product group such as T-shirts inside Clothing.' ) ) . '

'; } $html .= '
'; if ( strpos( self::key( $url ), 'storesuit-guide' ) !== 0 ) { $html .= '' . esc_html( I18n::t( 'Choose what to do →' ) ) . ''; } return $html . '
'; } }