Core 1.4.0 Stable
Released on:
Wednesday, 02 September 2026 19:12
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:andorder: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 nextplg_task_solidshopemailqueuerun — and rows can be deleted (core.delete). The screen is gated throughEmailqueueModel::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 newemail_requeuedentry. Library additions:Solidshop\Lib\Email\QueueStatusenum (unit-tested) andEmailQueueService::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 newSolidshop\Lib\Searchdomain (QueryClassifier,SearchQuery,SearchResultItem,SearchProviderRegistry— unit-tested), providers are thin query adapters in the component, andQuicksearchRegisterEvent(onSolidshopQuicksearchRegister) lets feature plugins register more result groups (invoices, subscriptions, reviews — follow-up work in their own plugins). Endpoint istask=quicksearch.search(CSRF +core.managegated; each provider additionally checks its owncore.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.phpgained an admin-module packaging pass, and the package script auto-publishes the module to Atum'sstatusposition 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
Shippablecapability instead of always being present. Track quantity now defaults from the type'sStockablecapability (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}.pdfinside 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, andsecureDir()drops.htaccess(Apache 2.2/2.4/LiteSpeed),web.config(IIS) and a blankindex.htmlinto the cache directory — called fromsaveToFile()and, self-healing for existing installs, fromcachedPath(). The first securing of a directory sweeps legacy token-less{id}.pdffiles; they regenerate on demand at the tokened name, andpdf_pathstays 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
InvoiceLayoutResolverwith 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 thesolidshop/solidshoppaymentplugin 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
trackingview was never registered with the component router, so its links stayed in rawindex.php?option=…form on sites with SEF URLs enabled. - A phantom empty media entry produced an empty storefront slideshow.
MediaManagerFieldseeded its hidden input with[""]for a new product, so saving without ever opening the picker stored a blank entry in themediacolumn; 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 likeproduct/default.phpdoes. - Cart page: the Estimated total row is laid out like the Subtotal row above it (
space-betweeninstead 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) undersite/com_solidshop/src/Model/, shadowed into the component namespace by the plugin autoloader and reached through the component'sMVCFactory— 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 asDownloadAvailability+DownloadState, replacing four independent copies (three templates andDownload.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_SHIPPINGvariants (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.sqlis 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_quicksearchto the Atumstatusposition 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.htaccessnorweb.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.
Solidshop Core v1.4.0
| File size | 9.22 Mb |
| SHA-256 Signature | e36495d3902076e550e0723c8fba270482148cf93adb74fc00d83602c1af9125 |
| Compatibility |