Release history for Cybersalt Release Manager. For install and usage instructions see the documentation.

📋 Cybersalt Release Manager — Changelog

🚀 Version 1.12.8 (October 20, 2026)

📝 Changed

  • Consistent extension names. The package now installs as Cybersalt Release Manager (it was "Cybersalt Release Manager Package"), the component shows as Cybersalt Release Manager in Extensions: Manage and the Components menu (it was "Release Manager"), and the shortcode plugin is now Content - Cybersalt Release Manager (it was "Content - CS Download Embed"). Only the display names changed. The elements (pkg_csreleasemanager, com_csreleasemanager, plg_content_csreleasemanager), the update server and every setting are the same, so existing sites update in place.
  • The package manifest's <name> is now the language key PKG_CSRELEASEMANAGER, so Joomla and JED Checker read the name from pkg_csreleasemanager.sys.ini.

🐛 Fixed

  • External Download URL was missing from the Package Version form, and saving a version could wipe it. The field existed in the form definition but the edit screen never showed it, and the model reset the column to empty whenever a save did not include it. The field is now on the edit screen (under the file source), and a save that does not carry it leaves the stored URL alone.
  • Renewable User Groups and Renewable Denial Message could not be set from the Package screen. Both were defined in the form but never displayed. They now appear under Allowed User Groups and Denial Message for paid packages.
  • The Downloads count on the Packages list opened an unfiltered Activity Log. The link passed a package filter that the Activity Log ignored, and it always asked for member downloads. The Activity Log now has a Package filter, and the link opens public downloads for free packages and member downloads for paid ones.
  • Options no longer offer settings that did nothing. Log Retention, Auto-delete Unlinked, Staging Path Exclusions, Allow Subdomain Sharing, the rate-limit Grace Period and Site Name were saved but never read by any code. They have been removed from Options (stored values are left in place and ignored, as before). The installation form's Notify Owner switch, which nothing read either, is no longer shown.
  • The Public / Free Extension help text said rate limiting and the blacklist still apply to downloads. They apply to registering and linking an installation, not to downloads or the update feed. The text now says what happens.
  • Installations list said "Can't Update - Membership Expired" for free packages. The Update Access column ran the membership check even when the package is public, so any linked site of a free extension whose owner was not in a paid group looked locked out. Free packages now show Can Update, which is what the update feed and download already do. (This only changes the admin column; the feed and download code are untouched.)
  • Dashboard "Today's Activity" undercounted. Update Checks Today only counted the legacy api.checkupdate route, which current clients don't use, so it sat at 0; Downloads Today skipped free and logged-in-member downloads; Failed Checks Today skipped refused downloads. The tiles are now Sites Checked Today (installations whose last update check was today), Denied Requests Today (refused update checks and downloads) and Downloads Today (every download route).
  • Sort menus showed raw language keys (for example COM_CSRELEASEMANAGER_PACKAGE_EXTENSION_ELEMENT ASC) on the Packages, Package Versions, Installations, Blacklist, Email Templates and Activity Log lists, and the Options screen was titled com_csreleasemanager_configuration. Both now show proper labels.
  • My Installations pushed the action buttons off the page in normal-width templates such as Cassiopeia: the installation ID had a column of its own, so Deactivate, Remove and Report needed a sideways scroll. The ID now sits in small type under the domain.
  • "Forgot your password?" and "Forgot your username?" links on the My Installations login form used com_users language keys that are not loaded on that page, so a site could show the raw keys. They now use the component's own strings.
  • The component's two .ini language files had Windows line endings. They now ship with Unix line endings, which is what Joomla's language checks expect (a .gitattributes rule keeps them that way).

📦 Repository

  • LICENSE is now the verbatim GPL-2.0 text; the copyright notice and the logo attribution (Tabler Icons, MIT) are in NOTICE.

🚀 Version 1.12.7 (October 15, 2026)

🐛 Fixed

  • Database screen no longer reports false "missing column IF" errors. System → Maintenance → Database listed nine problems for Release Manager, such as Table 'j6twa_csrm_packages' does not have column 'IF'. (From file 1.6.0.sql.). The update files for 1.6.0, 1.7.0, 1.8.1, 1.9.0 and 1.10.0 used ADD COLUMN IF NOT EXISTS and ADD INDEX IF NOT EXISTS. Joomla's schema checker takes the word after ADD COLUMN as the column name, so it went looking for a column called IF. Every real column and index was there all along. Those statements are now written the way Joomla core writes its own: plain ADD COLUMN / ADD INDEX ending in /** CAN FAIL **/;. The checker now reads the real names, and the installer still ignores the "already exists" error on a database that has them. The columns, types, defaults, positions and the index are unchanged, and script.php postflight still adds any that are missing.

🚀 Version 1.12.6 (October 10, 2026)

🐛 Fixed

  • Promoting a version to "latest" through the API or MCP left the old one marked latest too. The model cleared the other versions only when the save carried package_id, which a partial update does not, so two versions ended up flagged and the update feed served whichever it read first. Hit when publishing MCP for Joomla 2.8.2: the feed kept offering 2.8.1 until the old flag was cleared by hand. The package is now looked up from the version being saved.
  • The release manager's own "Changelog" button had never worked. Its manifest pointed at a CHANGELOG.html on a main branch that does not exist, in a private repository, and Joomla expects its changelog XML there, not HTML.

🚀 Added

  • api.changelogxml&element=: Joomla changelog XML for any package, built from the release notes already stored on each version. Point a manifest's <changelogurl> at it and Joomla's Changelog buttons work, with nothing extra to host or keep in step. Public and read-only.
  • README.md, covering the package, the access model, every public endpoint, the Web Services routes and how to build a release.

🚀 Version 1.12.5 (October 9, 2026)

Fixed

  • Paid add-ons could not be updated from mySites.guru (or any remote manager). The update feed's <downloadurl> carried no download ID, and the feed relied on the client appending extra_query itself. Joomla's own Extensions > Update screen does that (UpdateModel::update() re-reads the update site's extra_query), but mySites.guru's connector does not go through it: installExtensionFromUrl downloads exactly the URL its server read from the feed. So the download reached api.download bare and was refused "Missing parameters", on every paid add-on on every site, while free add-ons (api.userdownload, no auth) updated fine. Found 2026-10-09 when a mass update failed on about ten sites.

updatexml now appends the dlid to <downloadurl> when the feed request itself carried one and verifyUpdateAccess() granted it. Sites on cs-mcp-for-j 2.7.5+ put the dlid in the update-site location, so their feed requests carry it. Only a granted dlid is echoed, and only back to the request that sent it. The Cloudflare edge cache on this endpoint keys on the full query string (verified), so it cannot be served to another site.

🚀 Version 1.12.4 (October 9, 2026)

Fixed

  • Free add-ons returned 403 "membership not active" on the members download route. AccessCheckHelper::verifyUpdateAccess() ran the membership group check regardless of is_public, so a free package refused anyone whose email was not in a paying group — and refused one step earlier, at no_user_account, anyone with no Joomla account at all. The message said their membership was inactive, which is both wrong and unactionable for a package that has no membership.

The catalog's own public link (api.userdownload) already guarded on is_public and worked, so this presented as a client bug rather than a server one. Steps 5–7 are now skipped for a public package, which is the rule computeEntitlement() has always applied — just on the route that was missing it. One fix covers four call sites: ApiController::download(), the catalog flow, :1102, and the API CheckupdateController.

Reported by Dragan Subotic 2026-10-07 against the free Convert Forms add-on (package 18, activity log entry 4538). Affected every free-add-on user on that route.

Also in this build

  • 1.12.3 was never published. It was committed on 2026-10-09 ("paid packages now always show a buy button") but no version was ever uploaded, so the newest release customers could see was 1.12.2. That fix ships here.

🚀 Version 1.12.3 (September 22, 2026)

🐛 Fixed — paid packages offered no way to buy

  • A visitor who could not download a paid package was shown a dead end instead of a buy button. Two separate gaps produced it. A guest hit an early return that rendered only the login prompt, so the purchase branch below it was unreachable to anyone not logged in — that is, unreachable to every prospective customer. And a logged-in non-member only got a button when the package had its own renewal_url, which 6 of 12 paid packages did not. The result was the message "You do not have an active membership for {package}" with nothing to click, which is how it was reported: by a customer who had logged in, tried to buy, and had to email to ask how.
  • The purchase call-to-action is now shared by both branches. A new renderPurchaseLink() helper renders it for guests as well as for logged-in non-members, so a paid package always offers a way to buy.
  • New purchase_url plugin parameter provides a site-wide fallback, so a package with no renewal URL of its own still sends the visitor somewhere they can pay rather than nowhere.
  • Content plugin 1.1.0 → 1.2.0.

🚀 Version 1.12.2 (September 14, 2026)

🐛 Fixed — 1.12.1 told every polling site its update was unavailable

  • 1.12.1's entitlement annotation fired for everyone, including fully paid sites. It assumed an absent dlid on the update-XML request meant "this site is not linked". It does not. Joomla never appends an update site's extra_query to the update-XML fetch — only to the download URL. Updater::findUpdates() copies extra_query onto the parsed update record for the installer to use afterwards; the XML itself is requested from the bare location. So no dlid ever arrived, and every site was told "UPDATE NOT AVAILABLE TO THIS SITE: This site is not linked to a Cybersalt account."
  • Caught within the hour on a site that had just linked successfully and still cached the "not linked" text.
  • A missing dlid is now silent. No annotation, normal description — the 1.12.0 behaviour. The annotation only runs when a dlid actually arrives.
  • cs-mcp-for-j 2.7.5+ writes the dlid into the update site's location, which does reach this endpoint, so those sites get a real answer. Anything older stays silent rather than guessing.

🚀 Version 1.12.1 (September 14, 2026)

Tells a customer why a members-only update cannot be downloaded, instead of letting Joomla show a dead end.

🐛 Bug Fixes

  • The update XML now explains an entitlement failure before the customer clicks Update. When a site cannot download a members-only package, Joomla shows its own COM_INSTALLER_PACKAGE_DOWNLOAD_FAILED — "Failed to download package. Download it and install manually from <url>" — quoting the very URL that just refused them, since it is the same credential-less URL. That string is Joomla core and an extension cannot rewrite it, so the only place left to speak is the update XML itself. api.updatexml now reads the dlid that Joomla appends from the update site's extra_query, resolves entitlement through the same AccessCheckHelper::verifyUpdateAccess() the download endpoint uses, and when the answer is no, puts the reason into <description> and a <infourl> pointing at the renewal or signup page.
  • The reason is written for a customer, not for an API. The raw denial messages are internal — a real site under test reported literally Installation not found., which means nothing to the person reading it. installation_not_found, email_mismatch, no_user_account, membership_inactive and package_not_found are now mapped to plain text that says what to do about it. Anything unmapped falls back to the raw message rather than inventing a reason we cannot stand behind.
  • Annotate, do not hide. Suppressing the <update> element for an unentitled site was the alternative; it would leave a lapsed customer silently stuck on an old version with no signal anything exists, which is worse than telling them their membership needs attention.
  • An entitled customer sees none of this. Description stays normal, no infourl is emitted, and the download proceeds. The entitlement probe is wrapped so that if it throws, the update is still offered — a paying customer must never lose sight of an update because a diagnostic failed.

Context: this surfaced alongside a client-side bug where cs-mcp-for-j never populated #__update_sites.extra_query, so every Pro add-on update failed for every site, entitled or not. That half is fixed in cs-mcp-for-j 2.7.3. This half is what remains once credentials are flowing: saying something useful to the sites that still cannot download.

🚀 Version 1.12.0 (September 13, 2026)

Fixes the pair of problems behind a customer who was told three times that his installation had been linked, while his dashboard said "Not a member" and Pro stayed locked.

🐛 Bug Fixes

  • A membership is no longer found by one email address only. Entitlement was resolved by exactly one string — #__users.email — so a customer who checks out through PayPal under one address and holds their Cybersalt account under another got not_a_member, the same answer a total stranger gets, despite a paid and in-date subscription. When the account-email lookup finds nothing, the check now asks Membership Pro who paid with that address (#__osmembership_subscribers.email → user_id) and continues into the existing group comparison unchanged. This widens how we find the user, not what grants access — entitlement still comes only from Joomla group membership. New resolver: MemberResolverHelper::resolve(), used by all three lookup sites (verifyUpdateAccess, checkAccessDetailed, getAccessStatus). Observed 2026-09-13 on paxstellar.sg: the customer linked three times with two addresses, neither of which was his account address, and was locked out for a day until the row was repaired by hand in SQL. Safety rules: the account email stays the primary lookup, only published = 1 subscriptions are matched (so abandoned checkouts can't resurrect), and an address that maps to two different accounts resolves to nothing and is logged rather than guessed, because picking the first row would be an account-takeover path. Sites without Membership Pro simply never get the fallback.
  • linkemail no longer congratulates customers on a link that granted nothing. Both link endpoints (api/LinkemailController::link() and site/ApiController::linkemail()) used to set status = 'active', log success, and send "New installation linked to your account" without ever consulting membership state — the entitlement check lived entirely downstream in checkAccessDetailed(), which the client calls a fraction of a second later. Success email, failure screen, same click. The shared tail of that flow now lives in LinkHelper::completeLink(), which resolves entitlement through the same code path the client is about to call, so the two answers cannot disagree, and the two endpoints cannot drift apart again.
  • The Installations list tells the truth. status was written once, at link time, hardcoded to active — it meant "an email was attached", not "this install has access", and nothing in the UI said so. Anyone triaging saw three healthy-looking rows for a locked-out customer. The access check now writes back what it actually found.

📦 New Features

  • New installation status: unverified — linked, but no membership matched. Filterable on the admin Installations list and shown as a warning badge on both the admin and customer views. It is self-healing in both directions: a row parked as unverified is promoted to active the moment a check passes (so someone who links while lapsed and then renews is not stuck forever), and a row is demoted when the account can install nothing at all. À-la-carte buyers are explicitly not demoted — they read lapsed against the All-Access anchor package while legitimately owning an add-on. inactive, suspended and unlinked are deliberate states set elsewhere and are never touched. Note COM_CSRELEASEMANAGER_STATUS_ACTIVE is relabelled from "Linked" to "Active", since that is now what it means.
  • New email template: install_linked_unverified. When the address doesn't match a membership, the customer is told plainly that it was recorded but nothing matched, pointed at the likely cause (the PayPal-versus-account address mismatch above), and given somewhere to go. The "New installation linked" copy is now reserved for links that actually granted something. Seeded on both fresh install and update.
  • api.verifyaccess and both link endpoints return matched_via — 'account' or 'subscription', so the dashboard can say "matched via your subscription email" instead of leaving the customer to wonder why an address they don't recognise is the one that worked. Additive; old clients ignore it.
  • The link endpoints return the resolved entitlement in the same round trip (access_state, access_reason, matched_via, entitled_elements, renewal_url, signup_url), so a client can render the right call to action without a second call to api.verifyaccess.

🔧 Improvements

  • Attaching an email by hand in the admin no longer asserts entitlement. Editing an installation that was unlinked now parks it as unverified and lets the next access check decide, instead of flipping it straight to active.

🚀 Version 1.11.6 (September 11, 2026)

Per-add-on entitlement support for cs-mcp-for-j's catalog, plus a login form on the customer My Licenses page.

📦 New Features

  • api.verifyaccess now returns an entitled_elements array — the extension_elements the linked account can install, resolved across the whole catalog (free packages for everyone; owned Pro packages via the account's group memberships, covering à-la-carte plans AND All-Access). Additive and backward-compatible: existing clients ignore the field, while cs-mcp-for-j 2.6.1+ uses it to show Install vs "Get it" per add-on. Fixes the case where an à-la-carte buyer (e.g. only "MCP for 4SEO") was reported as having no membership because the check only looked at the All-Access anchor package. Resolver lives in AccessCheckHelper::computeEntitledElements().
  • Login form on the customer My Licenses (installations) page. The guest view previously showed only a "please log in" notice; it now renders a username/password form (with Forgot-Password/Username links) that returns the visitor to their installations after login, without changing the site-wide default login redirect.

🚀 Version 1.11.5 (July 7, 2026)

Fixes a subtle Joomla-Updater compatibility bug in the api.updatexml XML output that was silently breaking update discovery on every extension type that isn't a module or template — packages especially.

🐛 Bug Fixes

  • api.updatexml now emits <client> on every <update> block regardless of extension type. Was previously only emitted for modules and templates (the two types that natively carry a client_id in #__extensions). Every other type (component, plugin, package, file, library, language) got no <client> element — but Joomla's Updater walks each <update> block within a batch and, when <client> is missing, leaks the value from the previous block's parse into the current one. That produced orphan #__updates rows on client sites (client_id mismatch against the installed extension → extension_id=0 → Extensions → Manage → Update can't match the row → the release never surfaces). Discovered 2026-07-07 while chasing why MCP4J v2.3.2 wasn't surfacing as an available update on any of ~70 VMT/ET demo installs polling this endpoint, despite the endpoint serving v2.3.2 correctly. Emitting <client> unconditionally (computed from extension_client, default 0 = site) defends against the state leak regardless of what type Joomla parsed just before us.

One-line change: site/src/Controller/ApiController.php — moved the <client> emit out of the if (in_array($extType, ['module', 'template'])) block so it fires for every type.

🚀 Version 1.11.4 (July 7, 2026)

Rolls up four consecutive code-only fixes (1.11.1 → 1.11.4). No schema changes.

🐛 Bug Fixes

  • Joomla 6 native compatibility (1.11.1). Replaced Joomla\CMS\Filesystem\Folder::create() (removed in J6 core) with native PHP mkdir() in the package-version model and web-services controller. Sites on J6 with the Backward Compatibility 6 plugin disabled were fatal-erroring on version uploads.
  • api.updatexml cache headers survive the response pipeline (1.11.2). Cache-Control, Pragma, and Expires are now set via $app->setHeader(..., true) instead of raw header() calls, guaranteeing the no-cache directives reach the client unchanged. Pairs with cs-mcp-for-j v2.0.4's "Check for Updates Now" button so new releases propagate quickly.

📦 New Features

  • api.catalog emits superseded_by (1.11.3). Each catalog entry can now carry a superseded_by array read verbatim from catalog_metadata.superseded_by. cs-mcp-for-j uses this to mark rows as superseded (e.g. hide Akeeba Backup Core when Akeeba Pro is installed).
  • api.catalog emits is_new, is_experimental, and advisory (1.11.4). Operator-flippable badges plus an optional {title, message, severity} advisory rendered by the catalog view as an expandable disclosure. All three fields are read verbatim from catalog_metadata on the Package edit form.

🚀 Version 1.11.0 (June 12, 2026)

🔒 Security

  • Stored XSS hardening on denial messages. denial_message and renewable_denial_message package fields now go through Joomla's safehtml filter, so an editor with Package edit privilege can't inject <script> that runs in visitors' browsers viewing {cs-download} shortcodes. Basic HTML formatting (bold, italic, links) still works.

📦 New Features

  • Renewable User Groups (3-tier access lifecycle). Each members-only package gains a Renewable User Groups multi-select + optional Renewable Denial Message field. Subscription processor moves expired users from the active group to the renewable group on expiry, then out after the grace window. The download API + content plugin now branch into three tiers: in active group → download; in renewable group only → renewal-discount message + "Renew access" button; in neither → generic "Purchase access" prompt.
  • Allowed Extension Authors filter. Component Options → General → multi-select listing every distinct author found in #__extensions. Set to "Cybersalt" (+ any vendors you resell) to keep the package's Extension Element picker focused on your own catalogue instead of every installed extension. Empty = current behavior. Already-managed packages and the row's current value always remain available regardless.
  • Total Downloads roll-up on Packages list. New "Downloads (all versions)" column shows SUM(download_count) across every version row, with a click-through to the activity log filtered to that package's user_download events.
  • Every column sortable + filterable. Title, Extension Element, Extension Type, Access, Status, Install Limit, Active Installs, Downloads, ID — all sortable headers. Filter Options gains Extension Type, Access (Free/Paid), Has Installs (Yes/No), Has Downloads (Yes/No).
  • Access column on Versions list with Free/Paid badges plus a matching filter.
  • Maximum Joomla Version per version — declare J5+J6 support cleanly (regex becomes (5|6)\.[0-9]+).

🐛 Bug Fixes (from June 10 — folded into 1.11.0)

  • Package extension_element rename now moves the on-disk downloads folder to match — no more orphaned files after a rename.
  • Public package shortcode no longer requires login (is_public = 1 packages render the download button to guests directly).
  • New uploads keep the actual filename (the form's prefilled filename no longer overrides the uploaded file's name), with orphan cleanup of the previous file when the row's name changes.

🔧 Improvements

  • Canonical Joomla navigation. ViewNavHelper now registers cross-view nav on BOTH surfaces: the standard left sidebar (HTMLHelper::_('sidebar.addEntry', ...)) and the toolbar at the top (Toolbar::linkButton) — no custom CSS classes forced. Native Atum styling everywhere.

🚀 Version 1.8.1 (June 10, 2026)

🐛 Bug Fixes

  • Package extension_element rename: Renaming a package's extension_element now moves its on-disk downloads folder (/media/com_csreleasemanager/downloads/<element>/) to match. Previously the row was renamed but the files were left orphaned under the old folder, so downloads 404'd until manually moved.
  • Public package shortcode skipped login check: {cs-download} for a package with is_public = 1 was still showing "Please log in to download" because the content plugin neither selected is_public from the DB nor branched on it before the auth check. Public packages now render the download button to guests directly.
  • New uploads kept the old filename: When editing a version row and uploading a new ZIP, the form's prefilled filename field overrode the upload's actual name — so the new bytes were written to the previous version's path, and the row kept the old filename. Uploads now honor the file's own name, and the previous file is deleted from disk if it's no longer referenced.

📦 New Features

  • Maximum Joomla Version per release: Each version row gains a max_joomla_version field. Leave blank for the existing single-major behavior; set it to declare a span (e.g. min=5.0, max=6 emits (5|6)\.[0-9]+ in the update XML so both J5 and J6 sites see the release).

🚀 Version 1.6.0 (May 4, 2026)

🏷 Rebrand

  • CS Update Access Manager → Cybersalt Release Manager: Component element renamed com_csupdateaccessmanager → com_csreleasemanager, namespace Cybersalt\Component\CsUpdateAccessManager → Cybersalt\Component\CsReleaseManager, package element pkg_csupdateaccessmanager → pkg_csreleasemanager, plugin element csuamembed → csreleasemanager, DB table prefix #__csuam_* → #__csrm_*, CSS class prefix csuam- → csrm-. Component installer ships a preflight migration that renames any existing #__csuam_* tables in place so upgrades from the old component preserve all data.

📦 New Features

  • Multi-extension-type support: Packages can now represent any Joomla extension type, not just components. The Extension Type dropdown offers Package, Component, Plugin, Module, Library, Template, File, and Language. Extension Folder appears for plugins, Client (Site/Administrator) appears for modules and templates.
  • Update XML emits correct type metadata: <type>, plus <folder> for plugins and <client> for modules/templates, are now generated based on the package's stored values. Joomla's updater can now match updates against any extension type.
  • Bundled package install: pkg_csreleasemanager builds a single zip containing the component + content plugin. <files folder="packages"> so Joomla finds the children at the right path. Postflight script lands the admin straight on the component dashboard.
  • Custom date range on filters: Activity log, installations, and blacklist filters all gain a "Custom range…" option that reveals From/To date pickers. Empty bounds mean "from forever" / "up to now".
  • Per-column filters on activity log: Domain, IP Address, and Installation ID get explicit text-input filters in addition to the global search.
  • Created date range on blacklist: New filter on the blacklist list view.
  • Renewal button on plugin no-access message: When a logged-in user tries to view a {cs-download} shortcode for a package they don't have access to, the message now includes a configurable Purchase Access button linking to the package's renewal URL.

🔧 Improvements

  • Idempotent postflight column migrations: New columns are added by script.php postflight on every install/update if missing. Schema changes work regardless of the previous installed version.
  • Package install path fix: <files folder="packages"> matches the build script's directory layout, fixing the "Install path does not exist" error.

🚀 Version 1.5.1 (May 4, 2026)

📦 New Features

  • Embeddable Download Links: Each package version edit screen now shows three copy-paste fields — an Always-Latest URL, a This Version Only URL, and an HTML snippet — so admins can drop authenticated download links into articles, modules, or anywhere on the site. Visitors must be logged in and in an allowed user group to download; otherwise they see a clear message about what they need.
  • api.userdownload endpoint: New site-side API task that authenticates via Joomla user session + group membership instead of installation_id (which is for Joomla updater traffic). Logs as user_download event so it shows separately in analytics.

🔧 Improvements

  • Admin page titles: All admin views now show "Cybersalt Release Manager [View]" instead of just the view name, helping admins orient when switching between extensions.
  • Yes/No toggle styling: Yes buttons render solid green when selected, No buttons render solid red — matches traffic-light intuition. Loaded on every edit form via the admin CSS asset.
  • Support URL normalization: SupportHelper now auto-prepends https:// to support URLs missing a scheme, so messages always include a properly-formed URL.

🚀 Version 1.5.0 (May 4, 2026)

📦 New Features

  • Configurable Support Contact: New "Support Contact" fieldset in component Options for setting support email, URL, and label. Used automatically in all "please contact support" messages across the API and buyer dashboard.
  • Enhanced Dashboard: Stat cards reorganized into Installations, Catalog & Security, Today's Activity, and All-Time Totals sections. New stats: Reported Installations, Total Versions, Blacklist Entries, Downloads Today, Total Downloads.
  • Hotlinked Stat Cards: Every dashboard stat card is now clickable and navigates to the relevant filtered list view (e.g., Active Installations → Installations list filtered to active).
  • SHA256 Checksums in Update XML: Update XML now includes <sha256> element when the package version has a stored hash, removing Joomla's "no checksum" warning.
  • SHA256 Auto-computed on Upload: When admin uploads a ZIP via the version edit form, SHA-256 is computed and stored automatically.

🔧 Improvements

  • targetplatform regex fix: Update XML now generates <targetplatform name="joomla" version="5\.[0-9]+" /> from the major version of min_joomla_version, matching all 5.x versions instead of being restricted to 5.0.x only.
  • Admin file upload pattern: Package version file upload moved out of the Joomla Form layer (which strips file inputs) into a plain HTML input read directly from $_FILES.

🚀 Version 1.4.0 (May 4, 2026)

📦 New Features

  • Package Versions: New #__csrm_package_versions table tracks each release per package — version string, ZIP filename, release date, release notes, latest/stable flags, min Joomla/PHP versions, download counts
  • Admin Versions UI: New Package Versions submenu with full CRUD — list, filter by package, edit, file upload, mark as latest (auto-demotes others)
  • Authenticated Download Endpoint: New api.download task validates installation_id + email_hash and streams the requested ZIP file from a protected folder. Increments download counter and logs every download attempt.
  • Protected Downloads Folder: media/com_csreleasemanager/downloads/ with .htaccess and web.config denying direct HTTP access. Files organized by extension element.
  • Buyer Downloads Section: Buyer dashboard now shows packages the user has access to with latest version download button and collapsible older versions list. Release notes shown inline.

🔧 Improvements

  • Update XML uses real versions: api.updatexml now pulls actual version data from the new versions table instead of returning hardcoded 99.0.0
  • Update XML download URL: Now points to authenticated api.download endpoint instead of a static URL — Joomla appends extra_query for authentication
  • Min Joomla/PHP versions: Update XML returns the correct compatibility per version

🚀 Version 1.3.0 (April 15, 2026)

📦 New Features

  • Public Update XML Endpoint: New api.updatexml task returns Joomla-compatible update XML publicly per package element, following the standard Joomla update pattern
  • Authenticated Downloads: extra_query with installation_id and email_hash is appended to the download URL by Joomla during the download phase, separating update check from download auth
  • Full Joomla Update Flow: Proven end-to-end: update check finds new version, user clicks update, Joomla appends auth tokens to the download URL

🔧 Improvements

  • Separated update check from auth: Update check is public (returns version info), download is authenticated via extra_query — this is the standard pattern used by Akeeba and other major extensions
  • Test extension server URL: Manifest now points to the correct updatexml endpoint with the package element
  • Test extension extra_query: Only contains auth params (installation_id, email_hash), routing params are in the update server location URL

🐛 Bug Fixes

  • Fixed Joomla appending /extension.xml to update server URLs — caused by the endpoint returning errors (missing auth params) during the public update check phase, triggering Joomla's fallback behavior

🚀 Version 1.2.0 (April 15, 2026)

📦 New Features

  • Joomla Update XML: The checkupdate API endpoint now returns proper Joomla-compatible update XML, enabling automatic updates through Joomla's built-in updater
  • First-run Wizard: New extensions show a setup wizard on first dashboard visit prompting for membership email with skip option
  • Dynamic Activity Log Filters: Event Type and Result dropdowns only show values that exist in the log data
  • Configurable Server URL: Test extension defaults to cybersalt.com and updates Joomla's update server location when saved

🔧 Improvements

  • Duplicate domain prevention: Registering from a domain that already has an unlinked record updates the existing record instead of creating a duplicate
  • Blacklist error messages: Blocked domains now get a clear message about contacting support instead of generic "installation not found"
  • Blacklist check order: Email linking checks blacklist before installation lookup for accurate error messages
  • Registration status detection: Test extension checks JSON response status field instead of relying on HTTP codes
  • Params cache bypass: All test extension controllers and views read params directly from database to avoid Joomla ComponentHelper cache issues
  • IP badge visibility: Blacklist IP entries now use yellow warning badge for better dark mode visibility
  • Form field prefixes: All custom field types properly registered with addfieldprefix on form XMLs

🐛 Bug Fixes

  • Fixed registration showing "Not registered" despite successful server registration (ComponentHelper cache)
  • Fixed "View not found: install" error when redirecting after actions (missing view=dashboard in redirects)
  • Fixed wizard email form submitting into installer context instead of component context
  • Fixed dashboard showing stale wizard after email linking (cache bypass in view)

🚀 Version 1.1.0 (April 11, 2026)

📦 New Features

  • Report Installation: Buyers can report unauthorized installations with a reason selector (not my client, no longer my client, don't recognize this site, account compromised, other) and optional details via a Bootstrap modal
  • Per-email Domain Blocking: When a buyer reports or removes an installation, the domain is blocked from reconnecting to their email
  • Blocked Domains Management: Collapsible section on the buyer dashboard to view and manage blocked domains
  • Unblock & Reconnect: Buyers can unblock a domain and automatically reconnect it to their account
  • Permanently Disassociate: Irreversible action that permanently blocks a domain from a buyer's email, hidden from buyer view but still enforced
  • Admin Report Notification: Email sent to admin when an installation is reported, including reason and details
  • Update Access Column: Dynamic column in admin Installations list showing real-time membership status (Can Update, Can't Update - User Not in System, Can't Update - Membership Expired)
  • Reported Filter: Admin can filter installations by "Reported" status in the Installations list
  • Report Details in Admin: Admin installation edit view shows report reason, details, and date

🔧 Improvements

  • Status renamed: "Active" status now displays as "Linked" for clarity
  • Remove action: Frontend "Delete" renamed to "Remove" — unlinks email from installation instead of deleting the record, preserving data for admin investigation
  • Post-install message: Bootstrap card with link to the dashboard on install/upgrade
  • Form validation: All admin edit forms now load Joomla's form validator script
  • Proper SEF routing: Router follows Joomla Brain patterns with Itemid support, menu item awareness, and 4SEF compatibility
  • Menu item metadata: Added default.xml with request fields for proper menu item creation
  • Controller redirects: All site-side redirects include Itemid for correct template assignment
  • POST form actions: All buyer dashboard actions use POST forms instead of GET links
  • API status codes: Fixed HTTP response codes using http_response_code() instead of setHeader
  • JSON output: Clean JSON with JSON_UNESCAPED_SLASHES for readable URLs
  • Missing email template seeding: Upgrade script now seeds any email templates added in newer versions

🐛 Bug Fixes

  • Fixed config.xml and access.xml not included in manifest, causing Options to fail
  • Fixed empty media/images folder causing install warning
  • Fixed Bootstrap modal JS not loaded on frontend, preventing Report modal from opening
  • Fixed Bootstrap collapse JS not loaded on frontend for blocked domains section
  • Fixed form validation error (isValid undefined) preventing Save on all admin edit forms
  • Fixed SEF router mangling task names in action URLs
  • Fixed controller redirects losing template assignment after actions

🚀 Version 1.0.0 (April 9, 2026)

📦 New Features

  • Dashboard: Analytics overview with active installations, unlinked installations, total packages, update checks, and failed checks
  • Packages: Create and manage software packages with user group assignments and installation limits
  • Installations: View and manage all registered installation IDs with filtering by email, domain, status, and package
  • Blacklist: Block specific domains or IP addresses from using the update system
  • Email Templates: Configurable notification templates for new installs, suspicious activity, membership expiry, and install limits
  • Activity Log: Full audit trail of all API activity including registrations, email links, update checks, and denials
  • API Endpoints: RESTful API for client extensions to register, link email, and check update access
  • Buyer Dashboard: Site-side view where members can see and manage installations linked to their email
  • Rate Limiting: Configurable rate limits per IP and per email with grace period for new memberships
  • Staging Exclusions: Smart domain detection that skips ID regeneration for staging paths and subdomains

🔧 Improvements

  • Two-factor authentication: Installation ID + hashed email for update access verification
  • Membership-based access: Checks Joomla user group membership to determine update eligibility
  • Notification system: Email notifications when new installations are linked to an email address
  • Test extension: Included com_cstestpro dummy extension for testing the full update access flow