Changelog

  1. 1.6.0

    Stable

    The release about a store's first hour. A fresh install opens on a setup wizard for the sample store, then a Store setup checklist tracks what is left. The sitemap covers the whole site rather than only the catalogue, and a merchant can choose what the product page shows. Tax is now charged by zone, as the Tax tab always implied, and order lines record their cost at the time of sale.

    Schema: one nullable column on order lines.

    Added

    • Setup wizard for a fresh store. While the store email is still the sample store@example.com, the dashboard offers Set up your store: the store's details, how it charges tax (not at all, one rate for the country, or later) and one payment method with its own settings, saved together on Finish setup. It keeps no state of its own, and replaces the sample tax only while it is still untouched. Works without JavaScript, in dark mode and right to left
    • Store setup checklist on the dashboard — ten steps, from store details and a first product to live payments, worked out from the store's own data on every visit and each linked to the screen that completes it. Nothing is stored as done, so a step that is undone reopens. Tax and shipping can be marked not needed; the panel can be hidden per user and collapses to one line when complete
    • Move the sample products to the trash from a notice on the Products list. Only the three seeded products are touched, never one that has been ordered
    • A sample tax setup on fresh installs — a Standard class, an Illinois zone and a 6.25% rate matching the sample store's address, so a first test order shows a tax line
    • The sitemap covers the whole site. The new core plugin Solidshop - Site Content Sitemap adds menu items, articles and article categories to the sitemap a store has already submitted, so no second sitemap extension is needed. Noindex pages, sign-in, search, cart, checkout and account pages, and articles no menu item leads to, are left out. Enabled on install and once on this update
    • /sitemap.xml — Options → Sitemap → Serve at /sitemap.xml, off by default because another extension may already serve that path. System → Overview warns when a sitemap.xml file shadows it or the site is set to noindex
    • Choose what the product page shows — a new store Storefront tab with switches for the sold count, the share button and the wishlist button, all on by default, in Cassiopeia, Foundra and the booking and download product pages. The sold count is now a translated, plural-aware string
    • For developers: SetupStepsRegisterEvent (add a checklist step), SitemapSourcesRegisterEvent (add a paged sitemap source; SitemapUrlsEvent stays for small sets), PaymentPluginAbstract::isConfigured() / isTestMode() (PayPal, Stripe and Square override them in their next releases and work without them meanwhile), and Solidshop\Lib\Store\StoreParams / ProductPageSettings for typed store settings

    Fixed

    • Tax rates apply where their zone says. A rate's zone was never consulted, so every rate was charged on every order wherever the shopper was. Checkout now charges the rates whose zone covers the shipping address — a state territory covers that state, a country territory the whole country, and overlapping zones stack — and updates as the address changes. Prices shown before checkout use the store's own address. Order tax lines now record the rate they were charged under (tax_rate_id)
    • "Store default" taxes the product. A product left on - Store default - paid no tax at all; it now follows the store's default tax class
    • Still open in tax: ZIP-range territories, tax on shipping, the per-rate inclusive flag, and tax in the order editor's recalculation
    • Editing an order no longer rebuilds its lines. Saving the order editor deleted and re-inserted every line, losing download links, line types, the product name at the time of sale and the line ids that reviews and subscriptions point at. Lines are now updated in place in one transaction, and a line on a shipment cannot be removed. A row added after removing one from the middle is no longer lost
    • One Product per product page. Product modules below a product page declared every card as a Product of its own, which cost the page its rich result in Google. Cards on listing pages now carry image, description and sku
    • Sitemap URLs follow the SEF plugin's Site Domain, so they match the pages' canonicals. A shard that fails answers 503 with Retry-After, and only a guest's rendering is cached
    • Admin screens that showed a blank or a raw language key: 23 strings, including an unnamed entry in a menu item's Layout dropdown and unlabelled search boxes. Brands and Stores show their empty-state page
    • The loading indicator is announced to screen readers and follows the template's colour scheme (--solidshop-loader-* variables)
    • Outline buttons on admin screens were unreadable in Atum's dark mode — navy, green, red and blue text on a near-black page. They now use the dark theme's own colours on every Solidshop admin screen, plugin screens included
    • The Cost per item help text described the compare-at price
    • The store's Time zone field offers UTC. Without it, a store set to UTC (as the sample store is) was saved as Africa/Abidjan
    • The sample T-shirt was seeded checked out by a user that does not exist (existing installs: System → Global Check-in)

    Changed

    • Order lines record their cost at the time of sale — the variant's Cost per item, else the product's, else unknown, and never recomputed. Nothing displays it yet: a profit report can only cover orders placed after recording began. Generate variants leaves the cost empty rather than 0
    • The store phone number is optional; a blank one is left out of structured data, /llms.txt, the catalogue API and the email footer

    Upgrade notes

    • Check each store's Tax tab. Tax now depends on the shipping address: a rate on a zone with no territories or on an unpublished zone stops being charged, a shopper outside every zone pays no tax, and products on Store default start paying the default class's rates. With tax-inclusive prices, a shopper outside every zone pays the same price with no tax taken out of it
    • Foundra users: update to Foundra 1.0.1 at the same time. Foundra 1.0.0's checkout page shows tax at the store's address rather than the shipping address
    • 1.6.0.sql adds cost_per_item to order lines. Existing lines stay unknown: they are never backfilled from today's cost
    • Download links lost by editing an order on an earlier release are not recovered; re-issue them by hand
    • Product pages look the same after updating (every switch reads Show). A template override of product/stats.php or product/share_wishlist.php ignores the switches until it is updated from the new layout
    • The setup checklist appears on the first dashboard visit for admins with core.manage and collapses to one line on an established store. A store still on store@example.com is offered the wizard instead
    • The sample tax setup and sample product fixes apply to fresh installs only; an updated store keeps its own data
  2. 1.5.0

    Stable

    The release that gives a store a front page. Five new storefront surfaces — a slideshow hero, category tiles, a brand band, a recently-viewed strip and a Storefront home menu item — plus a Deals page built from the product's own compare-at price, so a landing page is modules rather than an empty article holding hand-written HTML.

    Alongside them: every store becomes discoverable to AI agents from its domain alone through /llms.txt, the admin gains an Extensions tab over everything Solidshop has installed, and prices get their decimal places back — a $104.32 product had been rendering as USD104, and being handed to payment gateways as 104 minor units.

    Schema: two nullable columns on brands, plus one guarded data migration.

    Added

    • Storefront home menu item (view=home) — a landing page is modules, and until now they needed an empty article to sit on. It prints at most a page heading (off when a hero carries the H1) and the store tagline, and does the head's work: title and meta description from the store, canonical on the site root, and one JSON-LD block with an Organization node and a WebSite node whose SearchAction points at the Products menu item. Nodes built by Solidshop\Lib\Seo\StorefrontSchema (pure, unit-tested); a new Tagline field on Store → SEO is printed on the page and published as the slogan.
    • Slideshow module (mod_sshop_slideshow, core bundle) — a slides subform of picture, eyebrow, heading, text and a button linking to a product, a category (RouteHelper::getCategoryRoute(), the URL that listing publishes as canonical), a menu item or a URL. CSS scroll-snap track, so swiping and keyboard scrolling work before any script runs and the first picture is fetchpriority=high; the script adds Previous / Next, dots, an optional 3–60 s timer with Pause and WAI-ARIA carousel semantics (paused on hover, on focus, off-screen and for reduced motion). One slide is a static hero. No slider library in front of the page's largest element.
    • Category showcase module (mod_sshop_categories, core bundle) — a 2–6 column grid or a scroll-snap strip of category tiles: everything below a parent to a chosen depth, or categories picked by hand. A tile borrows the first product's picture when the category has none, the optional count uses the listing's own predicates, empty categories are left out, and no tile links to a category the visitor may not view. The query count does not move with the tile count.
    • Brands module and brand logos (mod_sshop_brands, core bundle) — brands gain a logo (media field, resolved under the store's media folder), and the module prints a "Shop by brand" band, grid or strip, each logo linking to the listing filtered by that brand. Two queries whatever the band holds; brands with nothing published are left out. The logo plate stays light in both colour schemes, since brand artwork is nearly always dark ink on transparency.
    • Brands carry a store scope — a nullable store_id on #__sshop_brands, NULL on every existing row, meaning the brand belongs to the site and every store may use it. Solidshop\Lib\Product\BrandQuery is the single place that says which brands a store may use and resolves a name to an id; the CSV importer and the VirtueMart migrator go through it instead of assuming a brand name is unique across the table.
    • Recently viewed module (mod_sshop_recent, core bundle) — the shopper's own trail, written into the session by the site display controller (a guest's product page is served from Joomla's view cache, where the view never runs) and nowhere else: no table, no profile. Solidshop\Lib\Product\RecentlyViewed (pure, unit-tested) holds the rules — a repeat view moves rather than repeats, session junk is dropped, twenty ids are kept so a product that has gone gives up its place instead of shortening the strip. Cards carry the shopper's view levels.
    • Deals — sale surfaces on the product's own price pair. price_origin > price is now one predicate in ProductListingQuery, with four surfaces on it: a Products on Sale Only menu item (a Deals page), Show / Hide / Only plus a Discount ordering on the products module, an On sale filter toggle counted like every facet, and a Biggest discount sort key (discount_desc), also on the REST API and the MCP solidshop_search_products tool. The product row and nothing else: a discount granted at cart time is not a listing predicate.
    • The filters module folds into a drawer on narrow screens. Below Drawer Below Width (default 992 px) the panel moves into an off-canvas drawer and a Filters button appears on the listing toolbar beside the sort control. Filters still apply live; the primary button reads Show N results, a Clear all sits beside it, and the toolbar button counts the active values. Dialog semantics as the mini-cart drawer — focus trap, Escape, scroll lock, reduced motion, RTL. 0 keeps the panel inline.
    • /llms.txt agent-discovery file. Served at the domain root through a parse rule in the core system plugin, and at the raw URL index.php?option=com_solidshop&view=llms&format=raw (&store_id={id} for one store). Rendered on request from the store: name and summary, country, currencies and languages, the MCP endpoint and its tool names, the REST routes, the sitemap, up to 50 top-level categories with a visible product, policy pages, contact details, merchant notes, plugin sections and a platform credit. Declarative throughout — it states what exists and where, never instructs the agent; merchant text is escaped and headings in it flattened. text/plain, one-hour cache, X-Robots-Tag: noindex, and a physical llms.txt in the web root always wins.
    • AI-discovery settings — Options → AI discovery (llms.txt) (serve the file, per-store documents, list categories, platform credit; all on by default), two Store → SEO fields (Store summary for AI agents, also the store description in the MCP solidshop_get_store_info, and Notes for AI agents), and a System → Dashboard card with the URLs and two group-aware robots.txt checks (RFC 9309): which of GPTBot, OAI-SearchBot, ChatGPT-User, PerplexityBot, ClaudeBot are shut out of the site, and which are shut out of the catalogue API — Joomla's stock robots.txt ships Disallow: /api/, so that row is red on every unmodified install and shows the Allow: line that reopens the Solidshop routes.
    • LlmsSectionsEvent (onSolidshopLlmsSections) so a plugin can append a store-aware section, and ToolRegistry::CORE_TOOLS, the pinned MCP tool names the file prints, asserted against the tools/list snapshot so the two cannot drift.
    • System → Extensions tab — every Solidshop extension on the site with its version, enabled state, pending update and anything wrong with it, then the add-ons published but not installed here. Rows are discovered from #__extensions by naming convention plus the three plugin groups we own, so a third-party plugin in them is reported too and the tab cannot go stale against later releases. Updates come from #__updates including the unmapped rows com_installer hides, and an extension shipping an update server with no update site row is flagged — Joomla would never offer it an update. What discovery cannot know is declared in Solidshop\Lib\Extension\ExtensionCatalog, guarded by two drift tests.
    • Regenerate images after a media-settings change. Derivatives are named after their size, so changing a size or the aspect ratio left the storefront asking for files that were never written. The media manager now counts the mismatched images and offers Regenerate images: a batched, resumable AJAX run with a written / up-to-date / failed summary, an optional force rewrite and an optional prune of sizes no longer configured. Same run on the command line — php cli/joomla.php solidshop:media:regenerate with --store=, --force, --prune, --dry-run. ThumbnailSizes and ThumbnailPlanner hold the decisions, pure and unit-tested; uploads now cut all four sizes from one decode.
    • sku on the product page's JSON-LD, on the Product node and every Offer; and Media::storeImageUrl(), one resolver for a store's logo URL.

    Fixed

    • Prices rendered without decimal places. Currency::parse() fell back to number_decimal_points = 0 while the same field's form default is 2, and no currency has ever shipped with params — so the fallback was what every store used, and a $104.32 product read USD104 everywhere. Precision is not cosmetic: toFlatAmount() multiplies by 10^precision for the minor-unit amount a gateway is charged, so the same order was worth 104 minor units — a hundredth of the real total.
    • Currencies whose ISO 4217 minor unit is not 2 are corrected with it — 0 for BIF, CLP, DJF, GNF, ISK, JPY, KMF, KRW, PYG, RWF, UGX, UYI, VND, VUV, XAF, XOF, XPF, ZWD; 3 for BHD, IQD, JOD, KWD, LYD, OMR, TND. Rows that already carry params are left alone — saving a Locale tab is an opinion an update must not overwrite.
    • Choosing a variant answered "Variant does not exist" for most combinations. The lookup joins the combinations table once per chosen value and bound each join's placeholder to the loop variable; Joomla's bind() holds a reference and reads it when the query runs, so every join carried the last value. Only a combination sorting first among those sharing that value ever resolved — shipped since 1.1.0. Each placeholder now binds to its own array slot.
    • A blank box in the variants grid discarded every variant edit. The grid posts '' for a blank box, and '' in a DECIMAL column is rejected under STRICT_TRANS_TABLES, so a variant with no compare-at price failed its UPDATE and took the new price with it — for every variant of that product. Blank price, compare-at, cost and weight now bind as NULL, blank quantity as 0. The failure was silent because the product model ignored the variant model's return; both loops now propagate it.
    • Product and category pages contributed no breadcrumbs. No storefront view had touched Joomla's pathway, so a product read Home › Shop — and the breadcrumbs module builds its BreadcrumbList structured data from that same pathway, so search engines saw the same gap. A product now appends its category (linked through the route its canonical uses) and its own name, unlinked; a listing narrowed to one category or brand names itself the same way; nothing is added when the menu item already is that page.
    • The mini-cart drawer's remove button and quantity stepper did nothing on every page but the product and cart views. Both primitives lived in order.js, which only those views load, while mod_sshop_cart correctly depends on com_solidshop.core alone; the stepper threw into a silent fetch handler before the badge could sync. Both are generic cart-line behaviour and moved to core.js.
    • Option buttons wrapped onto rows with no space between them — the values were inline-block labels in a container with no layout, so their only spacing was markup whitespace, which spaces nothing vertically. Now a wrapping flex row with an 8 px gap.
    • The products module ignored its own ordering and Featured settings — the model read only the shopper-facing sort key and defaulted it to popular, so a "Featured products" strip listed the same products as "Best sellers". ProductListingQuery gained applyOrdering() (an unknown key degrades to the default sort rather than failing the page) and applyFeatured() on show / only / hide; the module's ordering list gained Price and Units Sold.
    • A facet section with nothing in it printed a header over an empty box — with hide empty filters on, rows were skipped but nothing checked whether all of them had been, and since core ships no review plugin the Rating facet hit this on every storefront. Sections now gate on the rows that will render, in the template and after an AJAX refresh. Also: Clear All Filters left the boxes ticked over an unfiltered grid.
    • The four media size settings were never filtered — the attribute was misspelled (fitler="uint"), so 800px raised a warning on every image and an emptied field took the page down with a TypeError. They are now required, integer-filtered and bounded, and srcset candidates carry the width each file was actually cut at instead of four hard-coded defaults.
    • Capability badges on the order page were unreadable — bg-info text-dark was written against stock Bootstrap's light cyan, but Atum redefines --info to a dark blue, giving roughly 2.8:1, below WCAG AA. Dropping text-dark restores Atum's white at about 5.4:1 in both schemes; four badges were affected, and the order products table gained the edge gutter its neighbouring cards have.
    • The Stores list reported the country as the store's state — the state join selected the country table's columns.
    • The XML sitemap was served as text/html with no X-Robots-Tag — the view queued both headers and then closed the application before Joomla's response stage flushes them. Found while building /llms.txt on the same pattern.

    Changed

    • The default store a fresh install starts from. sql/sample.sql runs unconditionally, so its row is every merchant's first screen — and it shipped as Lotus S.p.A with an Italian address, contact@solidshop.app as the store email and Joomla's powered_by.png as the logo, in EUR against the US dollar rate table seeded beside it. Now My Store, US/USD matching the seeded Springfield IL location, store@example.com (RFC 2606; the field is the merchant's own notification recipient, so it cannot be blank), and empty legal_business_name and phone, both of which print as-is on documents carrying legal weight. Existing installs are untouched.
    • Fresh installs show a currency's sign rather than its code — $104.32, not USD104.32. Seeded onto the currencies that ship published rather than made a global default: how an existing store renders a price is not an update's decision. Switchable per currency under Currencies → Locale.
    • The sample catalogue has brands, so a plain install has a working Brand facet instead of a filter panel that silently drops the section.
    • The filters module renders only on view=products — a product page that inherits a listing's menu item inherited its module assignment too, and the panel there was a set of links to another page.
    • All 22 translated locales re-synced against en-GB — 161 keys each (AI discovery, media regeneration, the home menu item, the Extensions tab, the on-sale surfaces, the filters drawer, the products module's new options), rebuilt from the en-GB skeleton so key order matches line for line; existing translations pass through untouched.

    Upgrade notes

    • Schema. 1.5.0.sql adds image and a nullable store_id to #__sshop_brands (existing rows stay NULL — shared by every store), and sets number_decimal_points on the currencies whose ISO 4217 minor unit is not 2, only where params is unset, so a Locale tab you have saved survives.
    • Prices change on screen, and in what gateways are charged. A store that never opened Currencies → Locale has been formatting without decimals; the same catalogue now reads $104.32 where it read USD104. Nothing stored changes — but the flat amount handed to a gateway was a hundredth of the real total for a two-decimal currency and is now correct. Check any price set to compensate, and reconcile gateway records across the update.
    • The core package gains four site modules — mod_sshop_slideshow, mod_sshop_categories, mod_sshop_brands, mod_sshop_recent. Joomla installs modules unpublished and position-less; publish and place the ones you want. An update never touches an existing module layout.
    • Check System → Dashboard → AI discovery. Joomla's stock robots.txt ships Disallow: /api/, which shuts AI crawlers out of the catalogue API /llms.txt points them at; the card shows the single Allow: line that reopens the Solidshop routes. The file is on by default and can be switched off under Options → AI discovery.
    • A storefront with a customised products toolbar keeps the filters button in the module's own position rather than on the toolbar — the drawer needs the slot the shipped toolbar template renders.
  3. 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 changes — 1.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.
  4. 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.
  5. 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.

2026

  1. 1.3.0

    Stable
    Release notes for version 1.3.0

    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)
  2. 1.2.0

    Stable
    Release notes for version 1.2.0

    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
  3. 1.1.0

    Stable
    Release notes for version 1.1.0

    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.
  4. 1.0.6

    Stable
    Release notes for version 1.0.6
    • 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
  5. 1.0.5

    Stable
    Release notes for version 1.0.5
    • 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
  6. 1.0.4

    Stable
    Release notes for version 1.0.4
    • 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
  7. 1.0.3

    Stable
    Release notes for version 1.0.3
    • 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.
  8. 1.0.2

    Stable
    Release notes for version 1.0.2

    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+.