Changelog

  1. 1.4.0

    Stable

    A release about the hours spent inside the admin. The two Core 1.4 roadmap items land — an Email Queue screen so a failed send is no longer invisible, and a quick search box in the administrator header — and the product edit form is rebuilt around product types, so a product only ever shows the tabs and defaults that apply to what it is.

    It is also the core Downloadable Products 1.0.0 (free) pins as its minimum. The invoicing that ships in core gained an approval workflow that actually gates PDF generation, the customer email and customer visibility, plus a privacy fix — cached invoice PDFs are no longer reachable by guessing an integer.

    No schema changes.

    Added

    • Email queue admin UI + resend order email — failed sends are no longer invisible. A new Email Queue screen (Components → Solidshop → Email Queue, also in the Solidshop admin-menu preset under System) lists every queued email with recipient, subject, context, the related order (linked), delivery status badge, attempt count and the last error; filter by status/context, search by recipient/subject (id: and order: prefixes supported), and click a subject to preview the exact stored email body in a new tab. Failed rows can be selected and retried from the toolbar — they return to pending with a fresh attempt budget and go out on the next plg_task_solidshopemailqueue run — and rows can be deleted (core.delete). The screen is gated through EmailqueueModel::authorise() (core.admin / core.manage.order — the per-store seam). The order detail toolbar gains Resend Confirmation: re-renders the confirmation from the order's current state (so address fixes, order edits and template fixes are picked up) and queues the customer copy only, guarded by the queue's per-recipient idempotency check; the action is logged to the order timeline as a new email_requeued entry. Library additions: Solidshop\Lib\Email\QueueStatus enum (unit-tested) and EmailQueueService::find()/retry()/deleteRows().
    • Admin quick search (mod_sshop_quicksearch) — a universal search box in the administrator header: type an order id/reference, customer name or email, product name, SKU or barcode (variant SKUs/barcodes included), or a discount name/coupon code and jump straight to the matching record, grouped by entity with status badges and per-group "View all" deep links into the pre-filtered list views. Ctrl+K / ⌘K focuses it from anywhere in the admin, / too when not already typing; full ARIA combobox pattern (activedescendant navigation, live result announcements). Query-shape detection with staged fallback (numeric → id/reference first, email → email columns, else broad LIKE) lives in the new Solidshop\Lib\Search domain (QueryClassifier, SearchQuery, SearchResultItem, SearchProviderRegistry — unit-tested), providers are thin query adapters in the component, and QuicksearchRegisterEvent (onSolidshopQuicksearchRegister) lets feature plugins register more result groups (invoices, subscriptions, reviews — follow-up work in their own plugins). Endpoint is task=quicksearch.search (CSRF + core.manage gated; each provider additionally checks its own core.manage.* action, so a user with partial rights just gets fewer groups; every result carries store identity per the multi-store rule). First administrator module in the bundle: build/build.php gained an admin-module packaging pass, and the package script auto-publishes the module to Atum's status position on fresh installs only — updaters get a pointer to the module manager instead. Customers group is deliberately restricted to users with commerce activity (an order or a saved address).
    • The product edit form follows the product type. Creating a product now starts with a set of icon cards — Physical plus whatever types the installed plugins register (Downloadable, Service, Subscription); on a saved product the control is the familiar dropdown. Changing the type reloads the form and preserves unsaved edits, so the tabs on screen always match the type, and the Shipping tab is gated on the type's Shippable capability instead of always being present. Track quantity now defaults from the type's Stockable capability (on for Physical, off for Downloadable/Service/Subscription/Virtual) and stays switchable everywhere — a service with limited seats can still track stock. The media field accepts drag-and-drop uploads straight onto the media area rather than requiring a trip through the media manager. Tabs and sidebar are reordered: SEO is its own tab (meta description, page title and the search-snippet preview) instead of sharing one with publishing data, and the sidebar groups status, product type, category, brand, tags, access and language in a single fieldset. The summary editor is height-capped so a long description no longer pushes the rest of the form off screen.

    Fixed

    • Cached invoice PDFs were reachable by guessing an integer. Generated PDFs sat at files/shop/{store}/invoice/{id}.pdf inside the web root with no deny rules — walking the ids exposed other customers' invoices (name, address, order contents). InvoicePdf::cachedPath() now appends a 64-bit HMAC token keyed with the site secret, and secureDir() drops .htaccess (Apache 2.2/2.4/LiteSpeed), web.config (IIS) and a blank index.html into the cache directory — called from saveToFile() and, self-healing for existing installs, from cachedPath(). The first securing of a directory sweeps legacy token-less {id}.pdf files; they regenerate on demand at the tokened name, and pdf_path stays write-only so stale DB values are inert. On nginx, which honours neither guard file, the unguessable filename is the protection.
    • The invoice approval workflow did not gate what it claimed to. The PDF/email task now only processes ISSUED invoices; approve() re-queues the PDF so it carries the approval-stamped invoice date, and the customer email goes out strictly post-approval. The admin PDF download preserves a queued row instead of swallowing the pending email. Customers no longer see or download draft/pending invoices anywhere (account list, print, PDF, order-detail button). Approve is also offered on drafts so a rejected invoice is not a dead end, the approval message/toolbar language keys are fixed, and the admin list gained the missing Pending Approval filter option.
    • Invoice items now carry the per-line tax aggregated from the order's tax rows, so the Detailed layout's tax column shows real values instead of zeros. Credit notes store all money columns negated, matching the documented behaviour and what an accounting import expects. Layout resolution is unified in InvoiceLayoutResolver with an on-disk existence check, so the PDF task can no longer email a blank PDF for a stale layout key.
    • Emails rendered from a payment callback were incomplete. Email::dispatchBeforeRender() now imports the solidshop / solidshoppayment plugin groups itself: renders triggered from com_ajax payment flows (the PayPal capture, for one) never pass through the component dispatcher, so plugin listeners were silently absent and plugin-contributed blocks and the bank-transfer instructions never rendered.
    • Public order-tracking URLs are SEF-routed. The tracking view was never registered with the component router, so its links stayed in raw index.php?option=… form on sites with SEF URLs enabled.
    • A phantom empty media entry produced an empty storefront slideshow. MediaManagerField seeded its hidden input with [""] for a new product, so saving without ever opening the picker stored a blank entry in the media column; the site model hydrated it into a bogus slide and the downloadable/service templates gated their gallery on the raw entry count. The field no longer seeds blank entries, the admin model strips blank rows on save (healing existing records), the site model filters them when decoding, and both templates (component + Foundra) gate on the typed media counts like product/default.php does.
    • Cart page: the Estimated total row is laid out like the Subtotal row above it (space-between instead of a right-aligned pair with a margin), so label and amount line up down the summary column.
    • Product form: the brand field's placeholder typo is fixed, and the variants checkbox label is properly associated with its input.

    Changed

    • Plugin-owned account pages moved their queries out of the templates. The subscriptions, downloads and bookings account templates each carried their own SQL, copy-pasted verbatim into the Foundra override (and, for downloads, into the component stub as well). Each plugin now ships a site model (DownloadModel, SubscriptionModel, BookingModel) under site/com_solidshop/src/Model/, shadowed into the component namespace by the plugin autoloader and reached through the component's MVCFactory — the pattern the review plugin already used. All seven templates now ask a model and hold no query. The download paid/expired/limit predicate is extracted into the library as DownloadAvailability + DownloadState, replacing four independent copies (three templates and Download.file), so the account page and the controller agree on both verdict and reason; unit-tested. SubscriptionController::processPlanChange() and the plan picker now share one definition of a switchable plan, the picker is scoped to the subscription's own store, and the Foundra subscriptions/bookings overrides render the Plugin Required notice instead of querying tables that do not exist when the plugin is absent.
    • The Payment received email no longer promises a shipment on an order with nothing to ship: the intro and next-step notice gained NO_SHIPPING variants (23 locales), used when the order carries no shipping rate — the shape of a digital-only order.
    • Email seed versions bumped: order.order_completed → 1.0.12, order.payment_received → 1.0.2. Both defaults gained a {% include 'downloadable/downloads.twig' ignore missing %} line, so a store running the Downloadable plugin gets a "Your downloads" block and a store without it renders exactly as before.

    Upgrade notes

    • No schema changes1.4.0.sql is empty. Nothing to migrate, nothing to back out.
    • Publish the quick-search module. Joomla installs administrator modules unpublished and position-less. Fresh installs get it placed automatically; on update the package script enqueues a notice instead, because an existing module layout is the merchant's. Publish mod_sshop_quicksearch to the Atum status position under System → Administrator Modules.
    • Stores that customised the Order confirmation (seed 1.0.11 → 1.0.12) or Payment received (1.0.1 → 1.0.2) bodies will see a drift banner. Reset to the default, or add the {% include 'downloadable/downloads.twig' ignore missing %} line by hand where the download block should appear; the payment-received default also picked up the no-shipping wording.
    • Existing invoice PDF caches are secured lazily: the guard files are dropped and legacy token-less files swept the first time the directory is touched after the update. On nginx, add a deny rule for files/shop/*/invoice/ (or move the directory outside the web root) — nginx honours neither .htaccess nor web.config, so there the tokened filename is the only protection.
    • Downloadable Products 1.0.0 pins 1.4.0 as its minimum core and refuses to install against anything older; update core first.
  2. 1.3.2

    Stable

    The core the first commercial Solidshop extension needs. Discount & Coupons 1.0.1 pins 1.3.2 as its minimum: somewhere to store per-action settings, a discount that can reduce shipping and have that survive into the order and the invoice, a shopper-facing seam for offers that have not paid out yet, and per-line discount allocation so a free item carries no tax.

    That work exposed three tax and totals bugs that were never plugin-specific — tax charged on the pre-discount subtotal, tax-inclusive stores double-counting tax in every derived grand total, and a discount engine that treated an unknown condition as no condition. Those are fixed for every store, discounts installed or not.

    Added

    • Promotion notices — a seam for telling the shopper about an offer that has not paid out yet. PromotionNoticesEvent (onSolidshopPromotionNotices) fires when the promotion.notices layout renders on the cart or a product page; listeners contribute plain-text PromotionNotice objects and the layout owns the markup. The cart re-renders the fragment on every cart.update, so an offer can appear or lapse on a quantity change without a reload. DiscountEngine::resolveApplicable() is split out of evaluate() so listeners share checkout's exact definition of "qualifies". First consumer: the Discount plugin's free-item notice.
    • Shipping discounts. New total_shipping_discount column on #__sshop_orders and #__sshop_invoices; checkout writes what the discount engine resolved and every grand total subtracts it (order screens, orders list, customer spend, account order list, reports, invoices, subscription renewals, the Square plugin's order total). A "Shipping discount" line (COM_SOLIDSHOP_ORDER_SHIPPING_DISCOUNT, 23 locales) shows wherever totals are printed — admin, account, checkout finish, every invoice layout, and the order_completed / invoice_issued emails — only when greater than zero. The Sales-by-period report's Shipping column is net of it so the row still sums to its total.
    • Discount engine: a discount whose action only reduces shipping is now recorded in DiscountResult::$appliedDiscounts (new shipping_amount key), so it gets a usage row, a coupon badge and a name on the order instead of reading "invalid". On the cart page such a code is accepted and marked "Confirmed at checkout", like a location-restricted code — the shipping cost is unknown until a rate is chosen. Both clamps are unchanged.
    • Orders: new discount_total column on #__sshop_order_products — the promotion amount allocated to that line at checkout, kept separate from the manual admin line-discount rule. SUM(discount_total) equals orders.total_discount for checkout-created orders. Admin edits round-trip it (so editing a coupon order no longer erases the promotion from the totals), InvoiceGenerator folds it into invoice_items.discount_amount, and the Sales-by-product report attributes coupon discounts to the right products. Renewal pricing ignores it — a one-time coupon must not reprice every renewal.
    • Discounts list: a Published status filter. The model already honoured it; the field was missing from the filter form, so drafts and finished campaigns could only be found by scanning.

    Fixed

    • Tax is now calculated on the discounted amount. The engine allocates every cart discount to the lines it belongs to (DiscountResult::$lineDiscounts, opt-in LineAllocatingActionInterface, proportional proration for scalar actions). Checkout, the checkout summary, the cart and the mini-cart feed each line's discounted subtotal to the tax computation, so a free item carries $0 tax and a 10 % discount reduces each affected rate by exactly 10 %, in both tax modes. Previously tax was computed pre-discount and subtracted after — the shopper paid tax on money they never spent, and #__sshop_order_taxes overstated collected tax.
    • Tax-inclusive stores no longer double-count tax in derived grand totals. With tax_price_input_type = inclusive the subtotal is gross and already contains total_tax, but every grand-total formula added it again — inflating the admin order screen, orders list, dashboard KPIs, customer spend, the account order list, stored invoice totals and (worst) the amount handed to payment gateways. All formulas now branch on the new TaxHelper::pricesIncludeTax(). Exclusive-mode stores are byte-identical to before; checkout summaries were already correct.
    • Per-line clamping also corrects over-stacking: two 100 % product-scoped discounts on one line no longer both pay out against unrelated lines' money — each sees only the headroom earlier discounts left. appliedDiscounts[n]['amount'] is now the post-clamp figure.
    • Discount engine: a condition whose handler is not registered now fails closed. Previously disabling the Discount & Coupons plugin turned every discount gated only by a rule-builder condition into an unconditional one.
    • Cart page and mini-cart: the discount row shows the display-basis (tax-adjusted) amount, so subtotal − discount = estimated total holds on screen when prices include tax. The cart.update JSON keeps its discount_total key; only the basis changed.
    • Payment method names read as labels, not slugs. payment_method_id stores a machine id (cod, banktransfer, free) and several surfaces printed it raw — confirmation, payment-received and order-cancelled emails said "Payment method: Cod", and the report and admin order screen showed cash / free / (none). New PaymentMethodHelper::label() resolves both key namespaces and de-slugs when the string is missing, so an uninstalled plugin can no longer leak a raw language key to a shopper. Every display surface funnels through it (emails via a new payment_method_label variable); the stored id is unchanged and stays what reports group on and CSV exports carry.
    • The orders-list payment filter now offers every method the orders actually used. Its options came from enabled payment plugins only, so admin-created orders (cash, bank_transfer, other), free orders and orders with no method were never offered — and disabling a plugin made every order paid through it unreachable. Options are now read from the orders themselves (PaymentMethodUsage, store-scoped); orders with no method are offered as Not specified. New composite index idx_#__orders_store_payment_method keeps that query index-only.
    • The cart's "Discount" row no longer renders when nothing was discounted. The row carries hidden, but that attribute's display: none comes from the UA stylesheet and lost to Bootstrap's .d-flex and Foundra's summary-row rule — so every cart with no qualifying discount showed a green "Discount −$0.00". Both stylesheets now carry a [hidden][hidden] { display: none !important } guard, which fixes every hidden toggle at once: under Foundra that also covers the collapsed coupon form, the billing address block, and the mini-cart badge's "0" bubble on an empty cart.
    • Checkout no longer fatals on a free product. A product saved with no price stores NULL, and the cart line carried it into DisplayCurrency::format() — a type error whenever a free item was in the cart, exactly what the Discount plugin's Free item action produces. CartController casts on add and quantity change, and both checkout summaries coerce on render, so existing sessions are safe too.
    • Filters module: the section headers were dead on a clean install — they are Bootstrap collapse toggles and nothing loaded bootstrap.collapse, so with Collapsed by default on, every filter stayed hidden. The module now declares the dependency itself.
    • The applied-coupon chip showed a wide gap before its remove control (.btn-close padding plus ms-1 plus a collapsed source newline). The chip is now flex, btn-sm ms-1 is gone from both coupon layouts, and under Foundra the cross is masked from currentColor so it works in both themes.
    • The component stylesheet now has an owner on every view that needs it. com_solidshop.core was reaching checkout second-hand from the mini-cart module, which is not rendered under tmpl=component. Checkout, Account and Tracking load it themselves, covering the Foundra override and the plugin-owned account templates too; the redundant layout-level call is removed.

    Changed

    • Discount edit form: the action row's config JSON now carries handler-specific settings submitted through an action_config group, and loadItem() exposes the decoded JSON so fields bind back on edit. Only keys whose showon matches the chosen action type are stored, so hidden fields' defaults don't leak between types. This is the seam plugin action types (BOGO, Fixed price, Tiered, Free item) save through — no plugin-side save hook needed. Released 1.3.1 accepted these fields and dropped them on save.
    • Discount edit form: saving a discount whose stored action type the form cannot offer (a plugin-registered type while that plugin is disabled) leaves the action row untouched instead of rewriting it as a plain percentage discount.
    • Filters module: colours resolve through the active template's tokens — --solidshop-filters-* falling back Foundra → Bootstrap → literals — so the module follows the active Foundra preset and its dark values and still looks right under Cassiopeia.
    • Admin list filters: the empty option on the discount and invoice status filters reads - All - (JALL), matching the other Solidshop lists and Joomla's own.

    Upgrade notes

    • Schema update 1.3.2.sql adds total_shipping_discount to #__sshop_orders and #__sshop_invoices, discount_total to #__sshop_order_products, and the composite index on #__sshop_orders. Existing rows keep NULL / 0 and their totals are unchanged — historical orders are never restated; new orders on discounted carts simply collect less tax (the fix).
    • Third-party discount actions registered via DiscountRegisterEvent keep working unchanged: calculate() is untouched and scalar amounts get prorated allocation automatically; LineAllocatingActionInterface is opt-in. Callers evaluating the engine without cart_items keep the previous cart-level behaviour.
    • Discount & Coupons 1.0.1 pins 1.3.2 as its minimum core and refuses to install against anything older; update core first.
    • Stores that customised the order_completed (seed 1.0.11), invoice_issued (1.0.1), order_cancelled (1.0.1) or payment_received (1.0.1) bodies will see a drift banner: those defaults gained the shipping-discount row and now print payment_method_label instead of payment_method_id. A customised body keeps the old lines until it is reset to the default; the raw payment_method_id stays available to templates that branch on it, such as the bank-transfer instructions partial.
  3. 1.3.1

    Stable

    A maintenance release: no schema changes and no storefront changes. It adds the order backlog to the Joomla administrator dashboard, teaches the order screen to render the payment entries the gateway plugins record, and moves the shared payment-callback form field into the component so two payment plugins can ship the same field without colliding.

    Added

    • Order backlog quick icons: new bundled plugin plg_quickicon_solidshop puts the shop's backlog on the Joomla administrator home page — three tiles counting Pending, Unpaid and Unfulfilled orders, each linking to exactly the rows it counted. Pending is on by default and reads the same Options → Pending order status the dashboard alert uses, so the two can never disagree; the other two tiles are opt-in. The panel it appears in (Third party / Site / System / Notifications) is a plugin option, and a tile is never shown to a user the orders list would 403.
    • Payment entries on the admin Activity Timeline: captures recorded by the payment plugins now render as readable lines instead of blank generic rows — a partial payment says so, a webhook-reconciled capture names the webhook, an inline confirm at checkout stays unqualified, and the gateway transaction id prints underneath in muted type for copying into the gateway dashboard.

    Changed

    • Library: OrderDescriptor builds the description a gateway shows in its own dashboard from the store name and the order reference, trimming the store name (never the reference) to fit each gateway's field limit; OrderTimelineHelper::log() gained a dedupeOn parameter so a capture reconciled twice logs one entry; FulfillmentStatus joins PaymentStatus in Solidshop\Lib\Status instead of the seeded code being hardcoded at call sites.
    • Orders ACL funnels through OrdersModel::authorise(), the same shape as ReportsModel::authorise() — one seam per feature where a per-store restriction attaches when multi-vendor lands.
    • The callbackurl form field type moved out of the PayPal plugin into the component (Joomla\Component\Solidshop\Administrator\Field\CallbackurlField) and gained a payment XML attribute; its language keys moved to com_solidshop.ini across all 23 locales. Joomla resolves field types from one global prefix stack, so two payment plugins shipping the same type name collided.

    Upgrade notes

    • The core package gains plg_quickicon_solidshop. It publishes itself from its own scriptfile on first install, so sites updating from 1.3.0 get the tiles too; every later update arrives as an update, so tiles you switch off stay off.
    • Payment plugins must be current for the timeline entries to appear: Stripe 1.0.0 and PayPal 1.0.2 are the versions that record them, and both pin 1.3.1 as their minimum core.
    • Update PayPal to 1.0.2 alongside this release. PayPal 1.0.1 and earlier still ship their own callbackurl field class, which now collides with the component's — the symptom is a Webhook URL field on another payment plugin's tab rendering PayPal's URL.
  4. 1.3.0

    Stable

    Added

    • Custom fields on products: define product attributes in Joomla's own Custom Fields screens (all 19 field types, validation, view levels, groups, ordering, language assignment). Values save and roll back with the product, render in a storefront Specifications tab, emit Schema.org additionalProperty on the product's JSON-LD node, index into Smart Search (value and/or facet per field), round-trip through CSV as Field: {name} columns, and read/write through the REST products API. {field N} / {fieldgroup N} placeholders work in descriptions
    • Custom fields at checkout: an Additional information panel collects order-level input (gift message, delivery slot, PO number) — required/pattern rules enforced server-side before the order is created
    • Custom fields on addresses: address book, admin customer screen and both checkout addresses; each field can be restricted to shipping, billing, or asked on both
    • Custom fields on order lines: per-item input on the product page (engraving, recipient, date), assigned to products via the field's Category assignment. Lines with different answers stay separate cart lines and draw on shared stock; identical answers merge. Editable from the cart; shown in cart, mini-cart and checkout summary
    • Captured values are frozen onto the order with their labels — renaming, retyping or deleting a definition never rewrites a placed order. They appear on the order screen (editable with the same validation, timeline-logged), the customer's order detail, the confirmation and shipment emails; per-field switches gate each surface
    • Related products: pick and drag-order suggestions on a product's Related tab; shown as ordinary product cards under the description. One-directional by default with an opt-in "both ways" display option resolved at read time — nothing is written to the other product, unlike HikaShop's mirror rows. Storefront visibility rules apply (unpublished / windowed / access-restricted products drop out); enabled and sized via Options → Products
    • Currency switcher: new module mod_sshop_currency (dropdown or links; labelled by code, sign, name) lets shoppers read catalogue, cart and checkout in any published currency that has an exchange rate; the sidebar price filter follows. Display-only: the order, invoice, emails, reports and the gateway amount stay in the store currency — stated with the exact amount at checkout. Hides itself when only one currency qualifies
    • Catalog REST API: read-only GET v1/solidshop/products|categories|brands (+ :id, products/:id/variants) with search / category / brand / price / stock filters, lang and currency args, served by the rebuilt plg_webservices_solidshop. Guest-visible data only — no cost prices, no stock quantities (boolean in_stock), no customer or order data. Public by default; a Public catalog access plugin switch gates everything behind Joomla API tokens instead
    • MCP server: POST v1/solidshop/mcp exposes the catalog to AI agents as five read-only tools (solidshop_search_products, solidshop_get_product, solidshop_list_categories, solidshop_list_brands, solidshop_get_store_info) over stateless Streamable HTTP. Same shapers and visibility rules as the REST layer; every response carries the canonical storefront URL, so agent referrals land on the merchant's own pages

    Changed

    • Product descriptions now run through Joomla's content plugins (this is what renders {field N}); Options → Run content plugins on product descriptions turns it off if literal {…} braces matter
    • The order confirmation email gained an Additional information block and per-line custom fields under each item; the shipment email lists them per shipped item. Customised bodies are offered the change via the editor's drift banner
    • Stock checks now sum every cart line of the same product instead of checking each line alone — with per-line fields one product can sit on several lines. Same correction for admin order-product edits

    Fixed

    • Address forms posted radio groups, checkboxes and multi-selects incorrectly — every radio in a group and every checkbox, ticked or not
    • Product cards in listings emitted an empty priceCurrency in their microdata; they now carry the store's currency code

    Upgrade notes

    • Schema: nullable JSON custom_fields columns on #__sshop_orders / #__sshop_order_products, and the new #__sshop_product_related table (both sides cascade on delete)
    • The core package gains mod_sshop_currency (installs unpublished, like every module) and plg_webservices_solidshop (enabled on install; disabling it removes all API routes)
    • Currency switching is display-only by design; Joomla's System - Page Cache plugin serves one cached copy per URL to all guests, so leave it off on priced pages if you publish the switcher
    • Related products are not yet in the CSV columns or the REST products API; listing pages cannot yet filter or sort by a custom field value (Smart Search facets cover narrowing inside search results)
  5. 1.2.0

    Stable

    Added

    • Reports: new admin screen with four reports over a date range — sales by period (day / week / month), sales by product, tax collected, sales by payment method — each downloadable as a self-describing CSV. Payment-status scoped, store-scoped, one section per currency (never summed across); plugins can register more via onSolidshopReportRegister
    • Smart Search: new bundled plugin plg_finder_solidshop indexes products into Joomla's site-wide search (com_finder) — live sync on save/delete/publish, storefront-visibility parity, per-language entries, and Type / Category / Brand / Language filters
    • SEO: native XML sitemap of product and category URLs at view=sitemap&format=xml — sharded index, honest per-product lastmod, guest-visibility parity, per-language alternates, plus a per-store SEO tab and an Options → Sitemap fieldset
    • Mini-cart: mod_sshop_cart gains an off-canvas drawer — line items, quantity stepper, tax-adjusted estimated total including discounts, View cart / Checkout — updating live via the new onSolidshopCartChanged document event. Legacy badge-only behaviour stays available via the Display mode option
    • Cart: coupon field plus subtotal / discount / estimated-total rows on the cart page, so automatic discounts are visible before checkout; a code applied in the cart carries into checkout. Location-restricted codes are accepted with a "Confirmed at checkout" hint
    • System: cache-directory health check with one-click repair — reports the Twig and email cache areas with owner and mode, and the chown command for what PHP cannot fix itself

    Changed

    • Currency is now a store property, set on the Store edit screen; the global "Default currency" option is removed and migrated automatically on update (if the two disagreed, the option's value wins — that is what orders were placed in). Storefront behaviour is unchanged
    • Dashboard KPIs, sales chart, top products and customer insights now use the Reports engine and its payment scope. Revenue reads lower than in 1.1.x — pending, unpaid and abandoned-at-payment orders no longer count as revenue. Revenue and AOV cover the store currency only; in-scope orders in other currencies are flagged with a link to Reports
    • Cart badge counts units (sum of quantities), not lines; cart.add / cart.update expose the same value in a new count field
    • Shipping: a profile with no assigned products covers the whole catalogue, so the store page shows it as a plain badge instead of an alert — the warning invited a "fix" that broke shipping
    • Account: removed the "Payment Methods" page, which only ever showed "Coming Soon"; the sidebar highlight now follows the layout that actually rendered
    • System: every filesystem path renders through one layout — LTR-locked, escaped, JPATH_ROOT stripped

    Fixed

    • Checkout: an unwritable cache/com_solidshop directory aborted checkout and payment callbacks; templates now recompile per render and log instead of throwing
    • System: the "Menu items" panel matched the wrong view for Customer Profile, omitted Products, and hid unpublished or trashed items — all states are reported now, badged, and scoped to site menus
    • Shipping: the seeded sample rates capped order value in every non-domestic zone, so carts above it matched no rate and checkout offered no shipping. Rebuilt as five gapless zones covering 77 countries
    • Coupons: an invalid code no longer reports "Coupon applied!" when an unrelated automatic discount happens to be active
    • Cart / checkout: load the SweetAlert preset so Solidshop.alert dialogs render as dialogs, not unstyled text at the page bottom
    • Translations: 110 new keys synced to all 22 locales; el-GR double-escaped ampersands at checkout, de-DE carried a raw & in markup, hi-IN mistranslated Subtotal. build/validate.php now checks placeholder parity and ampersand encoding
  6. 1.1.0

    Stable

    Added

    • Products: CSV import and export — one file, one row per variant, with auto-mapping of Shopify/WooCommerce headings, per-language Name [xx-XX] columns, and chunked processing so large catalogues import without timeouts. Empty mapped cells leave stored values alone, so a two-column alias+price file is a safe bulk update.
    • Installer: uninstalling the Solidshop package is now blocked while Solidshop add-on plugins are still installed — the error message lists them, so add-ons are removed first and never left stranded without the core tables and library their own uninstall depends on.

    Changed

    • Product listing: build cards from the listing row instead of a per-card detail-model round trip (82 fewer queries on a 20-card page); mod_sshop_products gets the same win.
    • Product listing: share one visibility predicate between the list and detail queries so a draft, scheduled, or category-unpublished product no longer 404s the whole page.
    • Product List menu item: honour the menu item's category selection (was ignored — every menu item listed all products).
    • Wishlist: join what the cards read and batch variant options instead of per-row detail-model lookups; one unpublished wishlisted product no longer 500s the page — it is left out like the listing already does.
  7. 1.0.6

    Stable
    • Security: fix SQL injection via cart option values (reported by Krzysztof Zając, CERT PL)
    • Shipping: product-level profile assignment covers all variants, tri-state variant picker
    • Shipping: match rates against total shipment weight (variant weight x quantity, summed per profile)
    • Shipping: match every state of a multi-state zone region, not just the last
    • Shipping: rate-less zones return empty instead of TypeError, drop unpriced carrier rates
    • Checkout: mirror the shipping address when no separate billing address was supplied
    • Checkout: URL-encode the guest email so plus-addressed buyers can check out
    • Address: require address_1 and city, filter both as string
    • Product listing: show star ratings on product cards
    • Orders: drop the foreign keys from status columns to #__sshop_statuses
    • System: link multi-routine task plugins in the Task Scheduler health check
  8. 1.0.5

    Stable
    • Install: load language in postflight, require Joomla 6.0.0 stable
    • SEO: merge review rating into the single Product JSON-LD node
    • PayPal: capture reconciliation via webhooks, min core version on install
    • Checkout: close the inventory oversell race
    • Install: scope postflight menu lookup to site items (client_id=0) so admin submenu links don't falsely skip site menu creation
    • Install: remove site menu items on uninstall + auto-publish core payment plugins (COD/Bank Transfer) on fresh install
    • Orders: admin list filters for order/payment/fulfillment status, store and created date range
    • Orders: translate invoice_email_sent timeline entry in admin order view
    • Orders: store activity-timeline timestamps in UTC to fix +7h timezone offset in admin display
    • Admin menu: reorder submenu into logical groups and sync with preset
    • Checkout: store-scoped policy consents
    • Responsive admin lists: admin list views now reflow into mobile cards below the md breakpoint via a shared %solidshop-list-cards SCSS placeholder
  9. 1.0.4

    Stable
    • Payment: add per-plugin logging facility, instrument COD + bank transfer
    • Core JS: dark-mode aware SweetAlert dialogs + prettier pass
    • Search module: match input height to submit button
    • Admin menu - Menu preset
  10. 1.0.3

    Stable
    • Add Bank Transfer to the Core installation package
    • Discount: move action_scope field to plg_solidshop_discount
    • Discounts: alias 'published' to 'state' on DiscountTable so toolbar publish/unpublish works
    • Products: expose variant SKU input in product edit variant table
    • Products: hide media section and silence thumbnail warning when product has no renderable media
    • Products: replace base64+atob share button pattern with TEMPLATE tag clone
    • Customer: declare core.edit.value ACL and remove com_fields force-enable workaround. Customer edit form no longer force-enables every custom field on the site client. The core.edit.value ACL on the com_users asset is now respected — admins must grant core.edit.value to the customer user group (Users → Options → Permissions) so logged-in customers can fill or update Joomla custom fields on their profile.
  11. 1.0.2

    Stable

    First public release of Solidshop. This entry establishes the baseline that all future changelog entries are deltas against.

    • Single-store catalog with products, options, variants, brands, categories, and per-SKU product stats.
    • Multi-step cart and checkout with guest support, address book, shipping rate selection, and order-time tax calculation.
    • Customer accounts linked to Joomla users — order history, address book, and wishlists.
    • Order lifecycle with order, payment, fulfillment, and return statuses, plus per-order timeline.
    • Tax engine with classes, zones, and zone × class rate resolution (inclusive/exclusive pricing).
    • Shipping engine with profiles, zones, regions, and rate tiers.
    • Stackable discount engine with pluggable conditions and actions.
    • Auto-generated invoices with credit-note support.
    • Wishlists for logged-in customers.
    • Customisable email notification templates editable from the admin — order confirmations, shipping updates, and status change emails.
    • Store dashboard with KPI cards, sales analytics, recent orders, product insights, and a plugin-extensible widget system.
    • Granular Joomla ACL integration across every admin view (view, create, edit, delete, publish, manage).
    • Product Filters module for category-page filtering by category, brand, price, rating, and tags.
    • Built-in payment plugins: Bank Transfer, Cash on Delivery.
    • Storefront translations for 23 languages including full RTL support (Arabic, Hebrew, Persian).
    • Multilingual content — translate product, category, option, status, and email-template text with per-language slugs, hreflang tags, and recipient-language emails.
    • SEO-optimised storefront — clean URLs, semantic H1 hierarchy, canonical tags, Open Graph and Twitter cards, dynamic stock availability, and Product/Offer JSON-LD structured data.
    • WCAG 2.1 AA accessibility — semantic landmarks, ARIA attributes, full keyboard navigation, visible focus states, and a contrast-preserving dark-mode palette.
    • Joomla 6.x native; requires PHP 8.4 and MySQL 8.4 / MariaDB 11.4+.