Complete administrator, store-owner, and extension-author documentation for protected purchases, private Joomla updates, expiring licences, and renewal reminders.
Author: Darko Fatur ·
VirtueMart Update Key Manager supports both licensed Joomla extension packages and ordinary protected digital files. Once an order enters a configured paid status, it sends the buyer a protected purchase link; extension products also receive a time-limited key for automatic Joomla updates.
Files are served from the VirtueMart Safe Path, outside the public web root. Purchase downloads have a configurable limit. A single-file product such as a JPEG or PDF is limited only by that count, while extension update downloads remain unlimited while the licence is active.
MAJOR.MINOR.PATCH).en-GB) and Slovenian (sl-SI) language files.| Extension | Identifier | Responsibility |
|---|---|---|
| Component | com_vmupdatekeymanager |
Licence database and administrator list; purchase/update download endpoint; dynamic feed endpoint; static feed publishing service; component options. |
| VirtueMart custom-field plugin | plg_vmcustom_vmupdatekeymanager |
Product mapping, version display, paid-order processing, storage creation, licence issuance, customer email, protected order-detail link, and renewal-term snapshot. |
| Installer plugin | plg_installer_vmupdatekeymanagerupdatekey |
Adds this manager's saved key only to protected manager-package downloads from shop.topoweryou.com. |
| Scheduled Tasks plugin | plg_task_vmupdatekeymanagerrenewal |
Runs renewal reminders/coupon creation and publishes all configured static update XML files. |
The parent package installs all four children, enables all three plugins, creates required database structure, migrates older installations, updates the manager update-site URL, and creates the two daily scheduled tasks if they do not already exist.
public_html or the equivalent document root../build.sh
The build creates the installable parent package:
dist/pkg_vmupdatekeymanager_v1.2.1.zip
The build also prepares dist/updatesxml/vmupdatekeymanager.xml. It checks expected versions and, when xmllint is installed, validates relevant manifests/XML.
pkg_vmupdatekeymanager_v1.2.1.zip.Install the newer parent package over the existing installation, or use Joomla Update after saving a valid manager update key in the component options. Installation migrations preserve licence rows and add missing columns. Always back up first and test the resulting scheduled tasks and feed.
Configure VirtueMart's Safe Path outside the public web root. A typical layout is:
/home/ACCOUNT/domains/shop.example.com/vmfiles/
└── salefiles/
├── route-calculation/
│ ├── pkg_route_calculation_help_for_accounting_v1.6.1.zip
│ └── pkg_route_calculation_help_for_accounting_v1.7.0.zip
├── accounting-tools/
│ └── pkg_accounting_tools_v2.0.0.zip
└── vmupdatekeymanager/
└── pkg_vmupdatekeymanager_v1.2.1.zip
The component installer creates the default salefiles directory when the Safe Path resolves. The custom-field plugin creates each extension subdirectory when required. Upload the first matching release before accepting payment.
| Item | Rule | Example |
|---|---|---|
| Protected base folder | Lowercase ASCII letter/number first, then lowercase letters, numbers, hyphen, underscore. | salefiles |
| Extension folder | Same rule; permanent identifier for the product. | route-calculation |
| Package prefix | Starts with pkg_, ends with _v; middle permits letters, numbers, dot, hyphen, underscore. |
pkg_route_calculation_v |
| Version | Exactly three numeric semantic-version parts. | 1.7.0 |
| ZIP filename | PACKAGE_PREFIX + MAJOR.MINOR.PATCH.zip. |
pkg_route_calculation_v1.7.0.zip |
| Update XML filename | Lowercase letters/numbers/hyphens/underscores, ending in .xml. |
route-calculation.xml |
Open Components → VirtueMart Update Key Manager → Options.
| Option | Default | Purpose |
|---|---|---|
| Update key | Empty | The key received when this manager was purchased. The installer plugin adds it only to matching manager downloads from the official host. |
| Public XML directory | files/updatesxml |
Path relative to the Joomla root. Every segment must match the lowercase directory naming rule and the resolved destination must remain inside the Joomla root. |
| Public XML base URL | https://shop.topoweryou.com/files/updatesxml |
Public URL corresponding to the directory. Its scheme/host also determines generated download URLs. |
| Permissions | Inherited | core.manage, core.edit.state, core.delete, and core.admin. |
Open System → Manage → Plugins → VirtueMart Update Key Manager.
| Option | Default / range | Meaning |
|---|---|---|
| Protected base folder | salefiles |
Directory created inside the VirtueMart Safe Path. |
| Paid statuses | C,S |
Comma-separated VirtueMart order status codes treated as paid. Configure these to match the store's actual payment workflow. |
| Maximum initial-package downloads | 3; 1–1000 | Limit for the protected purchase package. |
| Validity days | 365; 1–3650 | Licence term from issuance/payment processing. |
| Licence email subject/body | Translated defaults | Custom templates; leave empty to use language defaults. |
| Enable renewal reminder | No | Enables snapshotting and scheduled reminders for newly issued licences. |
| Days before expiration | 10; 1–365 | Start of the eligible reminder window. |
| Renewal discount | 10%; 0.01–100 | Percentage used for the generated Gift coupon. |
| Coupon validity | 30 days; 1–365 | Validity from coupon creation. |
| Renewal subject/body | Translated defaults | Custom renewal templates; leave empty for language defaults. |
| Template | Available placeholders |
|---|---|
| Licence email | {key}, {expires}, {product}, {order}, {download_url}, {download_limit} |
| Renewal email | {product}, {expires}, {coupon}, {renewal_url}, {coupon_expires}, {discount} |
Templates are converted to safe HTML: text is escaped, line breaks are preserved, and the download/renewal URL is made clickable.
The package installer also corrects these flags on existing Update Key Manager fields because VirtueMart requires a cart attribute to call the order-display hooks. The plugin renders no product/cart selector.
Open the product's Custom Fields tab, add the reusable field once, and choose one of the two clearly separated download types.
Download type: Extension package (ZIP + updates)
Protected product folder: route-calculation
Package prefix: pkg_route_calculation_help_for_accounting_v
Update XML filename: route-calculation.xml
Renewal product URL: (optional; blank uses this product's detail URL)
Download type: Single protected file (limited downloads)
Protected product folder: purchased-photos
Exact filename: landscape.jpeg
Package prefix: (leave empty)
Update XML filename: (leave empty)
Store the exact file at SAFE_PATH/base_folder/protected_product_folder/filename. A single-file purchase does not expire, does not publish an update feed, and remains available until its purchase-download count is used.
SAFE_PATH/base_folder/extension_folder/.v1.6.1.The buyer may use either the emailed key URL or the link shown in VirtueMart order details. The order-details link uses a 64-character HMAC token derived from the licence, order item, file, Joomla secret, and VirtueMart order password; it does not reveal the update key.
The endpoint verifies status, exact originally issued filename, storage containment/readability, and remaining count. Extension purchases also enforce licence expiry; single-file purchases are limited by download count and optional administrator revocation. It increments purchase_download_count atomically and records last_used_on.
key=….Update downloads do not consume the initial-purchase limit.
Open Components → VirtueMart Update Key Manager. The list supports search by customer email, order number, product name, product SKU, and key hint. Sortable fields include email, order, product, expiry, reminder date, purchase/update counts, and status.
| Action | Permission | Effect |
|---|---|---|
| Activate | core.edit.state |
Clears revoked status. It does not extend an expired date or reset counters. |
| Revoke | core.edit.state |
Immediately blocks both purchase and update downloads. |
| Delete | core.delete |
Permanently deletes selected licence rows and manager-created renewal coupons whose stored ID/code match. |
| Publish update XML files | core.admin |
Processes all valid published product assignments immediately and reports rewritten, unchanged, and failed feeds. |
| Options | core.admin |
Opens component update-key, publishing, and ACL settings. |
extension-folder.xml; the manager preserves vmupdatekeymanager.xml compatibility.(5|6)\..*.Recommended public static feed:
https://shop.example.com/files/updatesxml/route-calculation.xml
Protected update download generated in that XML:
https://shop.example.com/index.php?option=com_vmupdatekeymanager&task=download.get&file=pkg_route_calculation_v1.7.0.zip&channel=update
A dynamic feed endpoint also exists and selects the current package at request time:
https://shop.example.com/index.php?option=com_vmupdatekeymanager&task=updatefeed.get&extension=route-calculation
Static feeds are recommended for reliability, caching, predictable public availability, and easier firewall/CDN handling.
For the manager's repository-generated XML only:
./publish-update-xml.sh USER@HOST:/ABSOLUTE/PUBLIC/PATH/files/updatesxml/
# Or use a local target / environment setting
VMUKM_XML_UPLOAD_TARGET=/path/to/files/updatesxml ./publish-update-xml.sh
The task selects non-revoked, non-expired licences with stored renewal parameters, no successful reminder date, and an expiry within the snapshotted reminder window. Settings are frozen when the licence is issued so later offer changes do not retroactively alter sold terms.
RENEW-XXXXXXXX, generated from cryptographically random bytes.If email fails, renewal_reminder_sent_on remains empty. A still-valid stored coupon is reused on the next run. If that unsent coupon expires, the task unpublishes it and creates a replacement. A successful paid renewal order using the manager-created code unpublishes that coupon. Deleting a licence through the component deletes its matching coupon.
| Control | Protection |
|---|---|
| Random key | 32 bytes from a cryptographically secure generator (256 bits). |
| Database storage | Only SHA-256 hash and short key hint are retained. |
| Order link | HMAC token binds licence, order item, original file, Joomla secret, and VirtueMart order password. |
| Path validation | Strict names, realpath containment beneath Safe Path, direct-child file check, readability/file checks, and basename handling. |
| Package authorization | Purchase channel permits only the recorded original filename; update channel permits only the licence prefix plus a strict semantic version. |
| Licence state | Every download checks existence, expiry, and revoked status. |
| Initial limit | Database update increments only while count remains below the limit, preventing simple concurrent overuse. |
| Response headers | ZIP content type, attachment disposition, private/no-store cache control, and X-Content-Type-Options: nosniff. |
| Admin actions | CSRF token and Joomla ACL checks. |
| Feed integrity | SHA-256 is calculated from the exact protected release and XML is replaced atomically. |
| Key injection | The bundled manager installer plugin limits injection to official matching manager downloads. |
Automated Joomla clients cannot solve JavaScript challenges. Allow direct machine access to:
/files/updatesxml/
/index.php?option=com_vmupdatekeymanager&task=download.get
The endpoint still performs licence validation. Removing a browser challenge does not make ZIP files public.
| Symptom | Likely cause | Resolution |
|---|---|---|
| No licence/email after payment | Status not in paid list; invalid billing email; missing/conflicting product mapping; no matching ZIP or exact file; mail failure. | Confirm the actual order status code, assignment, file naming/storage, and Joomla mail. Correct it, then cause a safe paid-status callback/retry. |
| Product version is absent | No readable matching strict semantic-version ZIP or invalid field mapping. | Check Safe Path, base/extension folders, prefix, filename, permissions, and published field. |
| Purchase link says forbidden/limit reached | Expired/revoked licence, bad key/token, wrong file/channel, or consumed limit. | Check licence status/expiry/count in the component and use the original link/file. Reactivation does not reset the count. |
| Package/file not found | The recorded purchase file was removed/renamed or filesystem access changed. | Restore the exact original file to the recorded protected folder and correct owner/group/read permissions. |
| Joomla sees no update | Manifest update server wrong; XML stale; cache; feed returns HTML/challenge; version not newer. | Publish feeds, open XML directly, clear update cache, confirm target/version, and whitelist machine access. |
| Update detected but download fails | Key absent/invalid/expired/revoked; host/installer plugin mismatch; package mapping mismatch. | Save the correct key, enable/configure the sold extension's installer plugin, and verify host/prefix/filename. |
| Feed publishing reports conflict | Same extension folder has different mappings, or one XML filename is assigned to multiple extensions. | Make assignments unique and consistent; use one permanent prefix per extension. |
| Feed directory error | Invalid directory segments, path outside Joomla root, absent/unwritable directory, or mismatched base URL. | Use a valid relative path such as files/updatesxml, create it, fix ownership, and set its exact public URL. |
| Checksum mismatch | ZIP changed after XML generation or wrong ZIP/XML deployed. | Re-upload the final exact ZIP, republish XML, and verify SHA-256 independently. |
| Renewal reminders never send | Old licence lacks snapshot; reminders disabled; outside window; task/runner inactive; mail error. | Inspect stored/licence timing, task status/history, scheduler runner, and mail logs. Test with a newly issued staging licence. |
| Coupon exists but reminder failed | Mail failed after coupon creation. | Fix mail and rerun. The task reuses the valid coupon or replaces it after expiry. |
| All issues / Bugs | GitHub | Please report all issues on GitHub. |
#__vmupdatekeymanager_licenses, VirtueMart order/coupon tables, extension parameters, update sites, and scheduled tasks.configuration.php consistently with the database because the Joomla secret participates in order-link HMAC validation.The full customer keys cannot be reconstructed from the database hashes. A lost key must be recovered from the customer's original email or replaced through a new purchase/licence workflow.
Uninstall the parent package through Joomla. The component uninstall SQL drops #__vmupdatekeymanager_licenses, permanently removing licence/audit records. Treat uninstall as destructive and take a verified backup first. Protected package files and public XML may remain on disk and should be reviewed/removed manually only when no longer required.
| Purpose | Task | Parameters |
|---|---|---|
| Protected download | download.get |
file; channel=purchase|update; either key/dlid, or for purchase links license + token. |
| Dynamic feed | updatefeed.get |
extension matching the configured extension folder. |
| Area | Fields |
|---|---|
| Identity/security | id, license_hash, key_hint, revoked |
| Order/product | order_id, order_number, order_item_id (unique), virtuemart_product_id, product_sku, product_name, joomla_user_id, email |
| Package/storage | package_prefix, storage_folder, purchase_file |
| Usage/term | purchase_download_limit, purchase_download_count, download_count, created_on, expires_on, last_used_on |
| Renewal | renewal_params, renewal_coupon_id, renewal_coupon_code, renewal_coupon_expires_on, renewal_reminder_sent_on |
vmupdatekeymanager.renewal — create/reuse coupons and send eligible renewal reminders.vmupdatekeymanager.publishfeed — publish all configured protected extension update XML files.purchase_download_count counts successful initial-package responses and is constrained by purchase_download_limit.download_count counts successful update-channel downloads and has no configured maximum while active.last_used_on records the most recent successful download of either channel.