6 Commits

Author SHA1 Message Date
82bec621c6 Bump version to 0.3.7
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 10:18:33 +01:00
034593f896 Dashboard widget improvements and download counter feature (v0.3.7)
- Fixed: Dashboard widget "View All Licenses" link used wrong page slug
- Fixed: Download links in customer account resulted in 404 errors
- Removed: Redundant "Status Breakdown" section from dashboard widget
- Changed: License Types section now uses card style layout
- Added: Download counter for licensed product versions
- Added: Download Statistics admin dashboard widget
- Updated translations (356 strings)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 10:17:46 +01:00
202f8a6dc0 Update composer.lock with latest client library
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 21:23:27 +01:00
36b51c9fc8 Update CLAUDE.md with v0.3.6 session history
- Document security hardening changes (CSRF, IP spoofing, XSS)
- Add recursive key sorting fix for response signing
- Document trusted proxy configuration
- Add release information (SHA256, package size)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 21:22:26 +01:00
d0aaf3180f Merge branch 'main' into dev 2026-01-23 21:21:25 +01:00
4e683e2ff4 Update CLAUDE.md roadmap after v0.3.5 release
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 16:24:37 +01:00
14 changed files with 1027 additions and 725 deletions

View File

@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.3.7] - 2026-01-24
### Added
- Download counter for licensed product versions (tracked per version)
- Download Statistics admin dashboard widget showing total downloads, top products, and top versions
- New `DownloadWidgetController` class for download statistics widget
- New `incrementDownloadCount()`, `getTotalDownloadCount()`, and `getDownloadStatistics()` methods in `VersionManager`
- New `download_count` column in product versions database table
### Fixed
- Dashboard widget "View All Licenses" link now uses correct page slug (`wc-licenses`)
- Download links in customer account page no longer result in 404 errors (added query var registration)
- Added `license-download` endpoint registration during plugin activation
### Changed
- Removed redundant "Status Breakdown" section from dashboard widget (info already shown in stat cards)
- License Types section in dashboard widget now uses card style matching the stats row above
- Improved dashboard widget visual consistency
### Technical Details
- Added `addDownloadQueryVar()` method to `DownloadController` for proper endpoint registration
- Updated `Installer::activate()` to register `license-download` endpoint before flushing rewrite rules
- Updated translations (356 strings)
## [0.3.6] - 2026-01-23
### Security

View File

@@ -36,9 +36,18 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
No known bugs at the moment.
### Version 0.3.7
- Fixed: Dashboard widget "View All Licenses" link used wrong page slug
- Fixed: Download links in customer account resulted in 404 errors (missing query var registration)
- Removed: Redundant "Status Breakdown" section from dashboard widget (info already in stat cards)
- Changed: License Types section now uses card style matching the stats row above
- Added: Download counter for licensed product versions (tracked per version)
- Added: Download Statistics admin dashboard widget showing total downloads, top products, and top versions
### Version 0.4.0
- On first plugin activation, get the checksums of all security related files (at least in `src/`) as hashes, store them encrypted on the server and add a mechanism to check the integrity of the files and the license validity periodically, control via wp-cron.
No changes at the moment.
## Technical Stack
@@ -974,3 +983,57 @@ Added admin dashboard widget for license statistics and automatic license expira
- `autoExpireLicense()` updates status to expired and returns true if changed
- LicenseExpiredEmail follows same pattern as LicenseExpirationEmail (warning vs expired)
- Expired notification tracked via user meta to prevent duplicate emails
### 2026-01-23 - Version 0.3.6 - Security Hardening
**Overview:**
Security audit and implementation alignment with client/server documentation. Fixed response signing compatibility, rate limiting security, and XSS prevention.
**Security Fixes:**
- Added CSRF protection (nonce verification) to CSV export functionality
- Fixed IP header spoofing vulnerability in rate limiting - now requires explicit trusted proxy configuration
- Enabled explicit Twig autoescape (`'html'`) for XSS protection
- Fixed unescaped status values in CSS class names in Twig templates
**Implementation Fixes:**
- Fixed response signing to use recursive key sorting for client library compatibility
- ResponseSigner now recursively sorts nested array keys alphabetically as required by `magdev/wc-licensed-product-client`
**Modified files:**
- `src/Api/ResponseSigner.php` - Added `recursiveKeySort()` method for proper signature generation
- `src/Api/RestApiController.php` - Added trusted proxy support with `isTrustedProxy()`, `isCloudflareIp()`, `ipMatchesCidr()` methods
- `src/Plugin.php` - Added explicit `autoescape => 'html'` to Twig environment
- `src/Admin/AdminController.php` - Added nonce verification to `handleCsvExport()`, added `export_csv_url()` Twig function
- `templates/frontend/licenses.html.twig` - Added `esc_attr()` for CSS class status
- `templates/admin/licenses.html.twig` - Added `esc_attr()` for CSS class status, updated export link to use `export_csv_url()`
**Configuration:**
To enable trusted proxy support for rate limiting, add to `wp-config.php`:
```php
// For Cloudflare
define('WC_LICENSE_TRUSTED_PROXIES', 'CLOUDFLARE');
// Or for specific IPs/CIDR ranges
define('WC_LICENSE_TRUSTED_PROXIES', '10.0.0.1,192.168.1.0/24');
```
**Technical notes:**
- Rate limiting now only trusts proxy headers (`HTTP_CF_CONNECTING_IP`, `HTTP_X_FORWARDED_FOR`, `HTTP_X_REAL_IP`) when `WC_LICENSE_TRUSTED_PROXIES` constant is defined
- Without trusted proxy configuration, rate limiting uses `REMOTE_ADDR` only (prevents IP spoofing)
- Cloudflare IP ranges are hardcoded for convenience (as of 2024)
- CIDR notation supported for custom proxy ranges
- Recursive key sorting ensures signature compatibility with SecureLicenseClient
- References: <https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/raw/branch/main/docs/server-implementation.md>
**Release v0.3.6:**
- Created release package: `releases/wc-licensed-product-0.3.6.zip` (818 KB)
- SHA256: `b0063f0312759f090e12faba83de730baf4114139d763e46fad2b781d4b38270`
- Tagged as `v0.3.6` and pushed to `main` branch

4
composer.lock generated
View File

@@ -12,7 +12,7 @@
"source": {
"type": "git",
"url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git",
"reference": "a3a957914fd6ef74cb479e213d1d3bc0606f496b"
"reference": "9f513a819e8218a0e8e16f0be8f7edbf0f30245e"
},
"require": {
"php": "^8.3",
@@ -52,7 +52,7 @@
"issues": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/issues",
"source": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client"
},
"time": "2026-01-22T20:05:48+00:00"
"time": "2026-01-23T15:45:59+00:00"
},
{
"name": "psr/cache",

View File

@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: WC Licensed Product 0.3.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-23 21:09+0100\n"
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
"POT-Creation-Date: 2026-01-24 10:11+0100\n"
"PO-Revision-Date: 2026-01-22T17:15:00+00:00\n"
"Last-Translator: Marco Graetsch <magdev3.0@gmail.com>\n"
"Language-Team: German (Switzerland) <de_CH@li.org>\n"
@@ -97,8 +97,9 @@ msgstr "Produkt"
msgid "Domain"
msgstr "Domain"
#: src/Admin/OrderLicenseController.php:147 src/Admin/AdminController.php:1295
#: src/Admin/AdminController.php:1446 src/Admin/VersionAdminController.php:140
#: src/Admin/OrderLicenseController.php:147
#: src/Admin/VersionAdminController.php:140 src/Admin/AdminController.php:1295
#: src/Admin/AdminController.php:1446
msgid "Status"
msgstr "Status"
@@ -108,8 +109,9 @@ msgstr "Status"
msgid "Expires"
msgstr "Läuft ab"
#: src/Admin/OrderLicenseController.php:149 src/Admin/AdminController.php:1298
#: src/Admin/AdminController.php:1449 src/Admin/VersionAdminController.php:142
#: src/Admin/OrderLicenseController.php:149
#: src/Admin/VersionAdminController.php:142 src/Admin/AdminController.php:1298
#: src/Admin/AdminController.php:1449
msgid "Actions"
msgstr "Aktionen"
@@ -130,9 +132,11 @@ msgid "Cancel"
msgstr "Abbrechen"
#: src/Admin/OrderLicenseController.php:201
#: src/Admin/SettingsController.php:192 src/Admin/AdminController.php:151
#: src/Admin/AdminController.php:266 src/Admin/AdminController.php:1373
#: src/Admin/AdminController.php:1613 src/Product/LicensedProductType.php:110
#: src/Admin/SettingsController.php:192
#: src/Admin/DashboardWidgetController.php:136
#: src/Admin/AdminController.php:151 src/Admin/AdminController.php:266
#: src/Admin/AdminController.php:1373 src/Admin/AdminController.php:1613
#: src/Product/LicensedProductType.php:110
#: src/Product/LicensedProductType.php:158
msgid "Lifetime"
msgstr "Lebenslang"
@@ -141,7 +145,6 @@ msgstr "Lebenslang"
msgid "View in Licenses"
msgstr "In Lizenzen anzeigen"
#. translators: %s: Link to licenses page
#: src/Admin/OrderLicenseController.php:221
#, php-format
msgid "For more actions (revoke, extend, delete), go to the %s page."
@@ -168,12 +171,13 @@ msgid "Please enter a valid domain."
msgstr "Bitte geben Sie eine gültige Domain ein."
#: src/Admin/OrderLicenseController.php:301
#: src/Admin/OrderLicenseController.php:340 src/Admin/AdminController.php:173
#: src/Admin/OrderLicenseController.php:340
#: src/Admin/VersionAdminController.php:259
#: src/Admin/VersionAdminController.php:328
#: src/Admin/VersionAdminController.php:354 src/Admin/AdminController.php:173
#: src/Admin/AdminController.php:213 src/Admin/AdminController.php:249
#: src/Admin/AdminController.php:301 src/Admin/AdminController.php:339
#: src/Admin/AdminController.php:369 src/Admin/VersionAdminController.php:259
#: src/Admin/VersionAdminController.php:328
#: src/Admin/VersionAdminController.php:354
#: src/Admin/AdminController.php:369
msgid "Permission denied."
msgstr "Zugriff verweigert."
@@ -201,7 +205,7 @@ msgid "Domain cannot be empty."
msgstr "Domain darf nicht leer sein."
#: src/Admin/OrderLicenseController.php:363
#: src/Frontend/DownloadController.php:105
#: src/Frontend/DownloadController.php:117
#: src/Frontend/AccountController.php:352
msgid "License not found."
msgstr "Lizenz nicht gefunden."
@@ -310,13 +314,15 @@ msgstr ""
msgid "Expiration Warning Schedule"
msgstr "Ablaufwarnung Zeitplan"
#. translators: %s: URL to WooCommerce email settings
#: src/Admin/SettingsController.php:223
#, php-format
msgid ""
"Configure when expiration warning emails are sent. To customize the email "
"template, enable/disable, or change the subject, go to %s."
msgstr ""
"Konfigurieren Sie, wann Ablaufwarnungs-E-Mails gesendet werden. Um die "
"E-Mail-Vorlage anzupassen, zu aktivieren/deaktivieren oder den Betreff zu "
"ändern, gehen Sie zu %s."
"Konfigurieren Sie, wann Ablaufwarnungs-E-Mails gesendet werden. Um die E-"
"Mail-Vorlage anzupassen, zu aktivieren/deaktivieren oder den Betreff zu "
"ändern, gehen Sie zu %s."
@@ -389,6 +395,242 @@ msgstr "Lizenz erfolgreich überprüft!"
msgid "License validation failed."
msgstr "Lizenzvalidierung fehlgeschlagen."
#: src/Admin/VersionAdminController.php:58
msgid "Product Versions"
msgstr "Produktversionen"
#: src/Admin/VersionAdminController.php:78
msgid "Add New Version"
msgstr "Neue Version hinzufügen"
#: src/Admin/VersionAdminController.php:81
#: src/Admin/VersionAdminController.php:136 src/Admin/AdminController.php:1609
msgid "Version"
msgstr "Version"
#: src/Admin/VersionAdminController.php:84
msgid "Use semantic versioning (e.g., 1.0.0)"
msgstr "Verwenden Sie semantische Versionierung (z.B. 1.0.0)"
#: src/Admin/VersionAdminController.php:88
#: src/Admin/VersionAdminController.php:137
msgid "Download File"
msgstr "Download-Datei"
#: src/Admin/VersionAdminController.php:93
msgid "Select File"
msgstr "Datei auswählen"
#: src/Admin/VersionAdminController.php:96
#: src/Admin/VersionAdminController.php:110
msgid "Remove"
msgstr "Entfernen"
#: src/Admin/VersionAdminController.php:98
msgid ""
"Upload or select a file from the media library. Version will be auto-"
"detected from filename (e.g., plugin-v1.2.3.zip)."
msgstr ""
"Laden Sie eine Datei hoch oder wählen Sie eine aus der Mediathek. Die "
"Version wird automatisch aus dem Dateinamen erkannt (z.B. plugin-v1.2.3.zip)."
#: src/Admin/VersionAdminController.php:102
msgid "Checksum File"
msgstr "Prüfsummendatei"
#: src/Admin/VersionAdminController.php:107
msgid "Select Checksum File"
msgstr "Prüfsummendatei auswählen"
#: src/Admin/VersionAdminController.php:112
msgid ""
"Upload a SHA256 checksum file (.sha256 or .txt) to verify file integrity."
msgstr ""
"Laden Sie eine SHA256-Prüfsummendatei (.sha256 oder .txt) hoch, um die "
"Dateiintegrität zu überprüfen."
#: src/Admin/VersionAdminController.php:116
#: src/Admin/VersionAdminController.php:139
msgid "Release Notes"
msgstr "Versionshinweise"
#: src/Admin/VersionAdminController.php:124
msgid "Add Version"
msgstr "Version hinzufügen"
#: src/Admin/VersionAdminController.php:132
msgid "Existing Versions"
msgstr "Vorhandene Versionen"
#: src/Admin/VersionAdminController.php:138
msgid "SHA256"
msgstr "SHA256"
#: src/Admin/VersionAdminController.php:141
msgid "Released"
msgstr "Veröffentlicht"
#: src/Admin/VersionAdminController.php:148
msgid "No versions found. Add your first version above."
msgstr "Keine Versionen gefunden. Fügen Sie Ihre erste Version oben hinzu."
#: src/Admin/VersionAdminController.php:165
#: src/Admin/VersionAdminController.php:396
msgid "Uploaded file"
msgstr "Hochgeladene Datei"
#: src/Admin/VersionAdminController.php:169
#: src/Admin/VersionAdminController.php:400
msgid "No download file"
msgstr "Keine Download-Datei"
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413
#: src/Admin/DashboardWidgetController.php:117
#: src/Admin/AdminController.php:156 src/Admin/AdminController.php:907
#: src/Admin/AdminController.php:1232 src/Admin/AdminController.php:1355
msgid "Active"
msgstr "Aktiv"
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413 src/Admin/AdminController.php:157
#: src/Admin/AdminController.php:914 src/Admin/AdminController.php:1233
#: src/Admin/AdminController.php:1356
msgid "Inactive"
msgstr "Inaktiv"
#: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:419 src/Admin/AdminController.php:1274
#: src/Admin/AdminController.php:1459
msgid "Deactivate"
msgstr "Deaktivieren"
#: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:419 src/Admin/AdminController.php:1273
#: src/Admin/AdminController.php:1458
msgid "Activate"
msgstr "Aktivieren"
#: src/Admin/VersionAdminController.php:191
#: src/Admin/VersionAdminController.php:422 src/Admin/AdminController.php:1279
#: src/Admin/AdminController.php:1428 src/Admin/AdminController.php:1464
msgid "Delete"
msgstr "Löschen"
#: src/Admin/VersionAdminController.php:232
msgid "Are you sure you want to delete this version?"
msgstr "Sind Sie sicher, dass Sie diese Version löschen möchten?"
#: src/Admin/VersionAdminController.php:233
msgid "Please enter a version number."
msgstr "Bitte geben Sie eine Versionsnummer ein."
#: src/Admin/VersionAdminController.php:234
msgid "Please enter a valid version number (e.g., 1.0.0)."
msgstr "Bitte geben Sie eine gültige Versionsnummer ein (z.B. 1.0.0)."
#: src/Admin/VersionAdminController.php:235
msgid "An error occurred. Please try again."
msgstr "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut."
#: src/Admin/VersionAdminController.php:236
msgid "Select Download File"
msgstr "Download-Datei auswählen"
#: src/Admin/VersionAdminController.php:237
msgid "Use this file"
msgstr "Diese Datei verwenden"
#: src/Admin/VersionAdminController.php:238
msgid ""
"Invalid checksum file format. File must contain a 64-character SHA256 hash."
msgstr ""
"Ungültiges Prüfsummendateiformat. Die Datei muss einen 64-stelligen SHA256-"
"Hash enthalten."
#: src/Admin/VersionAdminController.php:239
msgid "Failed to read checksum file."
msgstr "Prüfsummendatei konnte nicht gelesen werden."
#: src/Admin/VersionAdminController.php:269
msgid "Product ID and version are required."
msgstr "Produkt-ID und Version sind erforderlich."
#: src/Admin/VersionAdminController.php:274
msgid "Invalid version format. Use semantic versioning (e.g., 1.0.0)."
msgstr ""
"Ungültiges Versionsformat. Verwenden Sie semantische Versionierung (z.B. "
"1.0.0)."
#: src/Admin/VersionAdminController.php:279
msgid "This version already exists."
msgstr "Diese Version existiert bereits."
#: src/Admin/VersionAdminController.php:285
msgid "Product not found."
msgstr "Produkt nicht gefunden."
#: src/Admin/VersionAdminController.php:289
msgid "This product is not a licensed product."
msgstr "Dieses Produkt ist kein lizensiertes Produkt."
#: src/Admin/VersionAdminController.php:306
msgid "Failed to create version."
msgstr "Version konnte nicht erstellt werden."
#: src/Admin/VersionAdminController.php:314
msgid "Version added successfully."
msgstr "Version erfolgreich hinzugefügt."
#: src/Admin/VersionAdminController.php:334
#: src/Admin/VersionAdminController.php:361
msgid "Version ID is required."
msgstr "Versions-ID ist erforderlich."
#: src/Admin/VersionAdminController.php:340
msgid "Failed to delete version."
msgstr "Version konnte nicht gelöscht werden."
#: src/Admin/VersionAdminController.php:343
msgid "Version deleted successfully."
msgstr "Version erfolgreich gelöscht."
#: src/Admin/VersionAdminController.php:367
msgid "Failed to update version."
msgstr "Version konnte nicht aktualisiert werden."
#: src/Admin/VersionAdminController.php:371
msgid "Version updated successfully."
msgstr "Version erfolgreich aktualisiert."
#: src/Admin/DashboardWidgetController.php:47
msgid "License Statistics"
msgstr "Lizenzstatistiken"
#: src/Admin/DashboardWidgetController.php:113
#: src/Admin/AdminController.php:900
msgid "Total Licenses"
msgstr "Lizenzen insgesamt"
#: src/Admin/DashboardWidgetController.php:121
msgid "Expiring Soon"
msgstr "Bald ablaufend"
#: src/Admin/DashboardWidgetController.php:125
#: src/Admin/AdminController.php:158 src/Admin/AdminController.php:921
#: src/Admin/AdminController.php:1234 src/Admin/AdminController.php:1357
#: src/Email/LicenseExpiredEmail.php:210 src/Email/LicenseExpiredEmail.php:259
msgid "Expired"
msgstr "Abgelaufen"
#: src/Admin/DashboardWidgetController.php:132
msgid "Time-limited"
msgstr "Zeitbegrenzt"
#: src/Admin/DashboardWidgetController.php:142
msgid "View All Licenses"
msgstr "Alle Lizenzen anzeigen"
#: src/Admin/AdminController.php:93
msgid "Overview"
msgstr "Übersicht"
@@ -433,28 +675,6 @@ msgstr "Kopiert!"
msgid "Copy failed"
msgstr "Kopieren fehlgeschlagen"
#: src/Admin/AdminController.php:156 src/Admin/AdminController.php:907
#: src/Admin/AdminController.php:1232 src/Admin/AdminController.php:1355
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413
#: src/Admin/DashboardWidgetController.php:151
msgid "Active"
msgstr "Aktiv"
#: src/Admin/AdminController.php:157 src/Admin/AdminController.php:914
#: src/Admin/AdminController.php:1233 src/Admin/AdminController.php:1356
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413
msgid "Inactive"
msgstr "Inaktiv"
#: src/Admin/AdminController.php:158 src/Admin/AdminController.php:921
#: src/Admin/AdminController.php:1234 src/Admin/AdminController.php:1357
#: src/Admin/DashboardWidgetController.php:159
#: src/Email/LicenseExpiredEmail.php:210 src/Email/LicenseExpiredEmail.php:259
msgid "Expired"
msgstr "Abgelaufen"
#: src/Admin/AdminController.php:159 src/Admin/AdminController.php:928
#: src/Admin/AdminController.php:1235 src/Admin/AdminController.php:1358
msgid "Revoked"
@@ -537,11 +757,6 @@ msgstr "Import der Lizenz für Domain %s fehlgeschlagen"
msgid "License Dashboard"
msgstr "Lizenz-Dashboard"
#: src/Admin/AdminController.php:900
#: src/Admin/DashboardWidgetController.php:147
msgid "Total Licenses"
msgstr "Lizenzen insgesamt"
#: src/Admin/AdminController.php:937
msgid "Attention:"
msgstr "Achtung:"
@@ -589,7 +804,6 @@ msgstr "Lizenz erfolgreich verlängert."
msgid "License set to lifetime successfully."
msgstr "Lizenz erfolgreich auf lebenslang gesetzt."
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1106
#, php-format
msgid "%d license activated."
@@ -597,7 +811,6 @@ msgid_plural "%d licenses activated."
msgstr[0] "%d Lizenz aktiviert."
msgstr[1] "%d Lizenzen aktiviert."
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1114
#, php-format
msgid "%d license deactivated."
@@ -605,7 +818,6 @@ msgid_plural "%d licenses deactivated."
msgstr[0] "%d Lizenz deaktiviert."
msgstr[1] "%d Lizenzen deaktiviert."
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1122
#, php-format
msgid "%d license revoked."
@@ -613,7 +825,6 @@ msgid_plural "%d licenses revoked."
msgstr[0] "%d Lizenz widerrufen."
msgstr[1] "%d Lizenzen widerrufen."
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1130
#, php-format
msgid "%d license deleted."
@@ -621,7 +832,6 @@ msgid_plural "%d licenses deleted."
msgstr[0] "%d Lizenz gelöscht."
msgstr[1] "%d Lizenzen gelöscht."
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1138
#, php-format
msgid "%d license extended."
@@ -643,7 +853,6 @@ msgstr ""
msgid "No licenses to export."
msgstr "Keine Lizenzen zum Exportieren."
#. translators: %d: number of licenses imported
#: src/Admin/AdminController.php:1159
#, php-format
msgid "%d license imported."
@@ -651,7 +860,6 @@ msgid_plural "%d licenses imported."
msgstr[0] "%d Lizenz importiert."
msgstr[1] "%d Lizenzen importiert."
#. translators: %d: number of licenses updated
#: src/Admin/AdminController.php:1166
#, php-format
msgid "%d updated."
@@ -659,7 +867,6 @@ msgid_plural "%d updated."
msgstr[0] "%d aktualisiert."
msgstr[1] "%d aktualisiert."
#. translators: %d: number of licenses skipped
#: src/Admin/AdminController.php:1174
#, php-format
msgid "%d skipped."
@@ -667,7 +874,6 @@ msgid_plural "%d skipped."
msgstr[0] "%d übersprungen."
msgstr[1] "%d übersprungen."
#. translators: %d: number of errors
#: src/Admin/AdminController.php:1182
#, php-format
msgid "%d error."
@@ -743,18 +949,6 @@ msgstr "Dashboard anzeigen"
msgid "Bulk Actions"
msgstr "Massenaktionen"
#: src/Admin/AdminController.php:1273 src/Admin/AdminController.php:1458
#: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:419
msgid "Activate"
msgstr "Aktivieren"
#: src/Admin/AdminController.php:1274 src/Admin/AdminController.php:1459
#: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:419
msgid "Deactivate"
msgstr "Deaktivieren"
#: src/Admin/AdminController.php:1275 src/Admin/AdminController.php:1419
#: src/Admin/AdminController.php:1460
msgid "Revoke"
@@ -772,12 +966,6 @@ msgstr "90 Tage verlängern"
msgid "Extend 1 year"
msgstr "1 Jahr verlängern"
#: src/Admin/AdminController.php:1279 src/Admin/AdminController.php:1428
#: src/Admin/AdminController.php:1464 src/Admin/VersionAdminController.php:191
#: src/Admin/VersionAdminController.php:422
msgid "Delete"
msgstr "Löschen"
#: src/Admin/AdminController.php:1281 src/Admin/AdminController.php:1466
msgid "Apply"
msgstr "Anwenden"
@@ -872,11 +1060,6 @@ msgstr "Lizenz übertragen"
msgid "License is VALID"
msgstr "Lizenz ist GÜLTIG"
#: src/Admin/AdminController.php:1609 src/Admin/VersionAdminController.php:81
#: src/Admin/VersionAdminController.php:136
msgid "Version"
msgstr "Version"
#: src/Admin/AdminController.php:1617
msgid "License is INVALID"
msgstr "Lizenz ist UNGÜLTIG"
@@ -997,225 +1180,26 @@ msgstr "Lizenz"
msgid "No domain specified"
msgstr "Keine Domain angegeben"
#: src/Admin/VersionAdminController.php:58
msgid "Product Versions"
msgstr "Produktversionen"
#: src/Admin/DownloadWidgetController.php:46
msgid "Download Statistics"
msgstr "Download-Statistiken"
#: src/Admin/VersionAdminController.php:78
msgid "Add New Version"
msgstr "Neue Version hinzufügen"
#: src/Admin/DownloadWidgetController.php:140
msgid "Total Downloads"
msgstr "Gesamte Downloads"
#: src/Admin/VersionAdminController.php:84
msgid "Use semantic versioning (e.g., 1.0.0)"
msgstr "Verwenden Sie semantische Versionierung (z.B. 1.0.0)"
#: src/Admin/DownloadWidgetController.php:145
msgid "Top Products"
msgstr "Top-Produkte"
#: src/Admin/VersionAdminController.php:88
#: src/Admin/VersionAdminController.php:137
msgid "Download File"
msgstr "Download-Datei"
#: src/Admin/DownloadWidgetController.php:159
#: src/Admin/DownloadWidgetController.php:180
msgid "No downloads yet"
msgstr "Noch keine Downloads"
#: src/Admin/VersionAdminController.php:93
msgid "Select File"
msgstr "Datei auswählen"
#: src/Admin/VersionAdminController.php:96
#: src/Admin/VersionAdminController.php:110
msgid "Remove"
msgstr "Entfernen"
#: src/Admin/VersionAdminController.php:98
msgid ""
"Upload or select a file from the media library. Version will be auto-"
"detected from filename (e.g., plugin-v1.2.3.zip)."
msgstr ""
"Laden Sie eine Datei hoch oder wählen Sie eine aus der Mediathek. Die "
"Version wird automatisch aus dem Dateinamen erkannt (z.B. plugin-v1.2.3.zip)."
#: src/Admin/VersionAdminController.php:102
msgid "Checksum File"
msgstr "Prüfsummendatei"
#: src/Admin/VersionAdminController.php:107
msgid "Select Checksum File"
msgstr "Prüfsummendatei auswählen"
#: src/Admin/VersionAdminController.php:112
msgid ""
"Upload a SHA256 checksum file (.sha256 or .txt) to verify file integrity."
msgstr ""
"Laden Sie eine SHA256-Prüfsummendatei (.sha256 oder .txt) hoch, um die "
"Dateiintegrität zu überprüfen."
#: src/Admin/VersionAdminController.php:116
#: src/Admin/VersionAdminController.php:139
msgid "Release Notes"
msgstr "Versionshinweise"
#: src/Admin/VersionAdminController.php:124
msgid "Add Version"
msgstr "Version hinzufügen"
#: src/Admin/VersionAdminController.php:132
msgid "Existing Versions"
msgstr "Vorhandene Versionen"
#: src/Admin/VersionAdminController.php:138
msgid "SHA256"
msgstr "SHA256"
#: src/Admin/VersionAdminController.php:141
msgid "Released"
msgstr "Veröffentlicht"
#: src/Admin/VersionAdminController.php:148
msgid "No versions found. Add your first version above."
msgstr "Keine Versionen gefunden. Fügen Sie Ihre erste Version oben hinzu."
#: src/Admin/VersionAdminController.php:165
#: src/Admin/VersionAdminController.php:396
msgid "Uploaded file"
msgstr "Hochgeladene Datei"
#: src/Admin/VersionAdminController.php:169
#: src/Admin/VersionAdminController.php:400
msgid "No download file"
msgstr "Keine Download-Datei"
#: src/Admin/VersionAdminController.php:232
msgid "Are you sure you want to delete this version?"
msgstr "Sind Sie sicher, dass Sie diese Version löschen möchten?"
#: src/Admin/VersionAdminController.php:233
msgid "Please enter a version number."
msgstr "Bitte geben Sie eine Versionsnummer ein."
#: src/Admin/VersionAdminController.php:234
msgid "Please enter a valid version number (e.g., 1.0.0)."
msgstr "Bitte geben Sie eine gültige Versionsnummer ein (z.B. 1.0.0)."
#: src/Admin/VersionAdminController.php:235
msgid "An error occurred. Please try again."
msgstr "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut."
#: src/Admin/VersionAdminController.php:236
msgid "Select Download File"
msgstr "Download-Datei auswählen"
#: src/Admin/VersionAdminController.php:237
msgid "Use this file"
msgstr "Diese Datei verwenden"
#: src/Admin/VersionAdminController.php:238
msgid ""
"Invalid checksum file format. File must contain a 64-character SHA256 hash."
msgstr ""
"Ungültiges Prüfsummendateiformat. Die Datei muss einen 64-stelligen SHA256-"
"Hash enthalten."
#: src/Admin/VersionAdminController.php:239
msgid "Failed to read checksum file."
msgstr "Prüfsummendatei konnte nicht gelesen werden."
#: src/Admin/VersionAdminController.php:269
msgid "Product ID and version are required."
msgstr "Produkt-ID und Version sind erforderlich."
#: src/Admin/VersionAdminController.php:274
msgid "Invalid version format. Use semantic versioning (e.g., 1.0.0)."
msgstr ""
"Ungültiges Versionsformat. Verwenden Sie semantische Versionierung (z.B. "
"1.0.0)."
#: src/Admin/VersionAdminController.php:279
msgid "This version already exists."
msgstr "Diese Version existiert bereits."
#: src/Admin/VersionAdminController.php:285
msgid "Product not found."
msgstr "Produkt nicht gefunden."
#: src/Admin/VersionAdminController.php:289
msgid "This product is not a licensed product."
msgstr "Dieses Produkt ist kein lizensiertes Produkt."
#: src/Admin/VersionAdminController.php:306
msgid "Failed to create version."
msgstr "Version konnte nicht erstellt werden."
#: src/Admin/VersionAdminController.php:314
msgid "Version added successfully."
msgstr "Version erfolgreich hinzugefügt."
#: src/Admin/VersionAdminController.php:334
#: src/Admin/VersionAdminController.php:361
msgid "Version ID is required."
msgstr "Versions-ID ist erforderlich."
#: src/Admin/VersionAdminController.php:340
msgid "Failed to delete version."
msgstr "Version konnte nicht gelöscht werden."
#: src/Admin/VersionAdminController.php:343
msgid "Version deleted successfully."
msgstr "Version erfolgreich gelöscht."
#: src/Admin/VersionAdminController.php:367
msgid "Failed to update version."
msgstr "Version konnte nicht aktualisiert werden."
#: src/Admin/VersionAdminController.php:371
msgid "Version updated successfully."
msgstr "Version erfolgreich aktualisiert."
#: src/Admin/DashboardWidgetController.php:47
msgid "License Statistics"
msgstr "Lizenzstatistiken"
#: src/Admin/DashboardWidgetController.php:155
msgid "Expiring Soon"
msgstr "Bald ablaufend"
#: src/Admin/DashboardWidgetController.php:166
msgid "Status Breakdown"
msgstr "Statusübersicht"
#: src/Admin/DashboardWidgetController.php:172
#, php-format
msgid "Active: %d"
msgstr "Aktiv: %d"
#: src/Admin/DashboardWidgetController.php:179
#, php-format
msgid "Inactive: %d"
msgstr "Inaktiv: %d"
#: src/Admin/DashboardWidgetController.php:186
#, php-format
msgid "Expired: %d"
msgstr "Abgelaufen: %d"
#: src/Admin/DashboardWidgetController.php:193
#, php-format
msgid "Revoked: %d"
msgstr "Widerrufen: %d"
#: src/Admin/DashboardWidgetController.php:202
msgid "License Types"
msgstr "Lizenztypen"
#: src/Admin/DashboardWidgetController.php:207
#, php-format
msgid "Time-limited: %d"
msgstr "Zeitlich begrenzt: %d"
#: src/Admin/DashboardWidgetController.php:213
#, php-format
msgid "Lifetime: %d"
msgstr "Lebenslang: %d"
#: src/Admin/DashboardWidgetController.php:220
msgid "View All Licenses"
msgstr "Alle Lizenzen anzeigen"
#: src/Admin/DownloadWidgetController.php:163
msgid "Top Versions"
msgstr "Top-Versionen"
#: src/Api/RestApiController.php:84
msgid "Too many requests. Please try again later."
@@ -1314,7 +1298,8 @@ msgstr "Diese Lizenz ist inaktiv."
msgid "This license is not valid for this domain."
msgstr "Diese Lizenz ist für diese Domain nicht gültig."
#: src/License/LicenseManager.php:760 src/Frontend/AccountController.php:140
#: src/License/LicenseManager.php:760 src/Product/VersionManager.php:349
#: src/Product/VersionManager.php:361 src/Frontend/AccountController.php:140
#: src/Email/LicenseExpirationEmail.php:107
#: src/Email/LicenseExpiredEmail.php:99
msgid "Unknown Product"
@@ -1324,7 +1309,6 @@ msgstr "Unbekanntes Produkt"
msgid "Attachment file not found."
msgstr "Anhangs-Datei nicht gefunden."
#. translators: 1: provided hash, 2: calculated hash
#: src/Product/VersionManager.php:177
#, php-format
msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
@@ -1343,7 +1327,6 @@ msgstr "Lizenz-Einstellungen"
msgid "%d days"
msgstr "%d Tage"
#. translators: %s: URL to settings page
#: src/Product/LicensedProductType.php:119
#, php-format
msgid "Leave fields empty to use default settings from %s."
@@ -1357,7 +1340,6 @@ msgstr "WooCommerce > Einstellungen > Lizensierte Produkte"
msgid "Max Activations"
msgstr "Max. Aktivierungen"
#. translators: %d: default max activations value
#: src/Product/LicensedProductType.php:131
#, php-format
msgid "Maximum number of domain activations per license. Default: %d"
@@ -1367,7 +1349,6 @@ msgstr "Maximale Anzahl der Domain-Aktivierungen pro Lizenz. Standard: %d"
msgid "License Validity (Days)"
msgstr "Lizenz-Gültigkeit (Tage)"
#. translators: %s: default validity value
#: src/Product/LicensedProductType.php:149
#, php-format
msgid "Number of days the license is valid. Leave empty for default (%s)."
@@ -1377,7 +1358,6 @@ msgstr "Anzahl Tage, die die Lizenz gültig ist. Leer lassen für Standard (%s).
msgid "Bind to Major Version"
msgstr "An Hauptversion binden"
#. translators: %s: default bind to version value (Yes/No)
#: src/Product/LicensedProductType.php:167
#, php-format
msgid ""
@@ -1399,54 +1379,54 @@ msgstr "Nein"
msgid "Version:"
msgstr "Version:"
#: src/Frontend/DownloadController.php:65
#: src/Frontend/DownloadController.php:89
#: src/Frontend/DownloadController.php:77
#: src/Frontend/DownloadController.php:101
msgid "Invalid download link."
msgstr "Ungültiger Download-Link."
#: src/Frontend/DownloadController.php:66
#: src/Frontend/DownloadController.php:76
#: src/Frontend/DownloadController.php:90
#: src/Frontend/DownloadController.php:106
#: src/Frontend/DownloadController.php:116
#: src/Frontend/DownloadController.php:125
#: src/Frontend/DownloadController.php:135
#: src/Frontend/DownloadController.php:144
#: src/Frontend/DownloadController.php:153
#: src/Frontend/DownloadController.php:171
#: src/Frontend/DownloadController.php:78
#: src/Frontend/DownloadController.php:88
#: src/Frontend/DownloadController.php:102
#: src/Frontend/DownloadController.php:118
#: src/Frontend/DownloadController.php:128
#: src/Frontend/DownloadController.php:137
#: src/Frontend/DownloadController.php:147
#: src/Frontend/DownloadController.php:156
#: src/Frontend/DownloadController.php:165
#: src/Frontend/DownloadController.php:187
#: src/Frontend/DownloadController.php:203
msgid "Download Error"
msgstr "Download-Fehler"
#: src/Frontend/DownloadController.php:75
#: src/Frontend/DownloadController.php:87
msgid "Invalid download link format."
msgstr "Ungültiges Download-Link-Format."
#: src/Frontend/DownloadController.php:115
#: src/Frontend/DownloadController.php:127
msgid "You do not have permission to download this file."
msgstr "Sie haben keine Berechtigung, diese Datei herunterzuladen."
#: src/Frontend/DownloadController.php:124
#: src/Frontend/DownloadController.php:136
msgid "Your license is not active. Please contact support."
msgstr "Ihre Lizenz ist nicht aktiv. Bitte kontaktieren Sie den Support."
#: src/Frontend/DownloadController.php:134
#: src/Frontend/DownloadController.php:146
msgid "Version not found."
msgstr "Version nicht gefunden."
#: src/Frontend/DownloadController.php:143
#: src/Frontend/DownloadController.php:155
msgid "Version does not match your licensed product."
msgstr "Version stimmt nicht mit Ihrem lizensierten Produkt überein."
#: src/Frontend/DownloadController.php:152
#: src/Frontend/DownloadController.php:164
msgid "This version is no longer available for download."
msgstr "Diese Version ist nicht mehr zum Download verfügbar."
#: src/Frontend/DownloadController.php:170
#: src/Frontend/DownloadController.php:186
msgid "No download file available for this version."
msgstr "Keine Download-Datei für diese Version verfügbar."
#: src/Frontend/DownloadController.php:186
#: src/Frontend/DownloadController.php:202
msgid "Download file not found."
msgstr "Download-Datei nicht gefunden."
@@ -1610,7 +1590,6 @@ msgstr ""
"Um dieses Produkt weiterhin zu nutzen, verlängern Sie bitte Ihre Lizenz vor "
"dem Ablaufdatum."
#. translators: %s: list of placeholders
#: src/Email/LicenseExpirationEmail.php:301
#: src/Email/LicenseExpiredEmail.php:288
#, php-format
@@ -1722,22 +1701,21 @@ msgid "To continue using this product, please renew your license."
msgstr ""
"Um dieses Produkt weiterhin zu nutzen, verlängern Sie bitte Ihre Lizenz."
#: src/Plugin.php:258
#: src/Plugin.php:260
msgid "WC Licensed Product"
msgstr "WC Licensed Product"
#: src/Plugin.php:259
#: src/Plugin.php:261
msgid ""
"Plugin license is not configured or invalid. Frontend features are disabled."
msgstr ""
"Plugin-Lizenz ist nicht konfiguriert oder ungültig. Frontend-Funktionen sind "
"deaktiviert."
#: src/Plugin.php:260
#: src/Plugin.php:262
msgid "Configure License"
msgstr "Lizenz konfigurieren"
#. translators: %s: WooCommerce plugin name
#: wc-licensed-product.php:61
#, php-format
msgid "%s requires WooCommerce to be installed and active."
@@ -1749,6 +1727,36 @@ msgstr ""
"WC Licensed Product benötigt WooCommerce als installierte und aktivierte "
"Erweiterung."
#~ msgid "Status Breakdown"
#~ msgstr "Statusübersicht"
#, php-format
#~ msgid "Active: %d"
#~ msgstr "Aktiv: %d"
#, php-format
#~ msgid "Inactive: %d"
#~ msgstr "Inaktiv: %d"
#, php-format
#~ msgid "Expired: %d"
#~ msgstr "Abgelaufen: %d"
#, php-format
#~ msgid "Revoked: %d"
#~ msgstr "Widerrufen: %d"
#~ msgid "License Types"
#~ msgstr "Lizenztypen"
#, php-format
#~ msgid "Time-limited: %d"
#~ msgstr "Zeitlich begrenzt: %d"
#, php-format
#~ msgid "Lifetime: %d"
#~ msgstr "Lebenslang: %d"
#~ msgid "Or External URL"
#~ msgstr "Oder externe URL"

View File

@@ -1,14 +1,14 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the wc-licensed-product package.
# This file is distributed under the same license as the WC Licensed Product package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: wc-licensed-product 0.3.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-23 21:09+0100\n"
"Project-Id-Version: WC Licensed Product 0.3.7\n"
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
"POT-Creation-Date: 2026-01-24 10:11+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -93,8 +93,9 @@ msgstr ""
msgid "Domain"
msgstr ""
#: src/Admin/OrderLicenseController.php:147 src/Admin/AdminController.php:1295
#: src/Admin/AdminController.php:1446 src/Admin/VersionAdminController.php:140
#: src/Admin/OrderLicenseController.php:147
#: src/Admin/VersionAdminController.php:140 src/Admin/AdminController.php:1295
#: src/Admin/AdminController.php:1446
msgid "Status"
msgstr ""
@@ -104,8 +105,9 @@ msgstr ""
msgid "Expires"
msgstr ""
#: src/Admin/OrderLicenseController.php:149 src/Admin/AdminController.php:1298
#: src/Admin/AdminController.php:1449 src/Admin/VersionAdminController.php:142
#: src/Admin/OrderLicenseController.php:149
#: src/Admin/VersionAdminController.php:142 src/Admin/AdminController.php:1298
#: src/Admin/AdminController.php:1449
msgid "Actions"
msgstr ""
@@ -126,9 +128,11 @@ msgid "Cancel"
msgstr ""
#: src/Admin/OrderLicenseController.php:201
#: src/Admin/SettingsController.php:192 src/Admin/AdminController.php:151
#: src/Admin/AdminController.php:266 src/Admin/AdminController.php:1373
#: src/Admin/AdminController.php:1613 src/Product/LicensedProductType.php:110
#: src/Admin/SettingsController.php:192
#: src/Admin/DashboardWidgetController.php:136
#: src/Admin/AdminController.php:151 src/Admin/AdminController.php:266
#: src/Admin/AdminController.php:1373 src/Admin/AdminController.php:1613
#: src/Product/LicensedProductType.php:110
#: src/Product/LicensedProductType.php:158
msgid "Lifetime"
msgstr ""
@@ -137,7 +141,6 @@ msgstr ""
msgid "View in Licenses"
msgstr ""
#. translators: %s: Link to licenses page
#: src/Admin/OrderLicenseController.php:221
#, php-format
msgid "For more actions (revoke, extend, delete), go to the %s page."
@@ -162,12 +165,13 @@ msgid "Please enter a valid domain."
msgstr ""
#: src/Admin/OrderLicenseController.php:301
#: src/Admin/OrderLicenseController.php:340 src/Admin/AdminController.php:173
#: src/Admin/OrderLicenseController.php:340
#: src/Admin/VersionAdminController.php:259
#: src/Admin/VersionAdminController.php:328
#: src/Admin/VersionAdminController.php:354 src/Admin/AdminController.php:173
#: src/Admin/AdminController.php:213 src/Admin/AdminController.php:249
#: src/Admin/AdminController.php:301 src/Admin/AdminController.php:339
#: src/Admin/AdminController.php:369 src/Admin/VersionAdminController.php:259
#: src/Admin/VersionAdminController.php:328
#: src/Admin/VersionAdminController.php:354
#: src/Admin/AdminController.php:369
msgid "Permission denied."
msgstr ""
@@ -195,7 +199,7 @@ msgid "Domain cannot be empty."
msgstr ""
#: src/Admin/OrderLicenseController.php:363
#: src/Frontend/DownloadController.php:105
#: src/Frontend/DownloadController.php:117
#: src/Frontend/AccountController.php:352
msgid "License not found."
msgstr ""
@@ -294,7 +298,6 @@ msgstr ""
msgid "Expiration Warning Schedule"
msgstr ""
#. translators: %s: URL to WooCommerce email settings
#: src/Admin/SettingsController.php:223
#, php-format
msgid ""
@@ -368,6 +371,234 @@ msgstr ""
msgid "License validation failed."
msgstr ""
#: src/Admin/VersionAdminController.php:58
msgid "Product Versions"
msgstr ""
#: src/Admin/VersionAdminController.php:78
msgid "Add New Version"
msgstr ""
#: src/Admin/VersionAdminController.php:81
#: src/Admin/VersionAdminController.php:136 src/Admin/AdminController.php:1609
msgid "Version"
msgstr ""
#: src/Admin/VersionAdminController.php:84
msgid "Use semantic versioning (e.g., 1.0.0)"
msgstr ""
#: src/Admin/VersionAdminController.php:88
#: src/Admin/VersionAdminController.php:137
msgid "Download File"
msgstr ""
#: src/Admin/VersionAdminController.php:93
msgid "Select File"
msgstr ""
#: src/Admin/VersionAdminController.php:96
#: src/Admin/VersionAdminController.php:110
msgid "Remove"
msgstr ""
#: src/Admin/VersionAdminController.php:98
msgid ""
"Upload or select a file from the media library. Version will be auto-"
"detected from filename (e.g., plugin-v1.2.3.zip)."
msgstr ""
#: src/Admin/VersionAdminController.php:102
msgid "Checksum File"
msgstr ""
#: src/Admin/VersionAdminController.php:107
msgid "Select Checksum File"
msgstr ""
#: src/Admin/VersionAdminController.php:112
msgid ""
"Upload a SHA256 checksum file (.sha256 or .txt) to verify file integrity."
msgstr ""
#: src/Admin/VersionAdminController.php:116
#: src/Admin/VersionAdminController.php:139
msgid "Release Notes"
msgstr ""
#: src/Admin/VersionAdminController.php:124
msgid "Add Version"
msgstr ""
#: src/Admin/VersionAdminController.php:132
msgid "Existing Versions"
msgstr ""
#: src/Admin/VersionAdminController.php:138
msgid "SHA256"
msgstr ""
#: src/Admin/VersionAdminController.php:141
msgid "Released"
msgstr ""
#: src/Admin/VersionAdminController.php:148
msgid "No versions found. Add your first version above."
msgstr ""
#: src/Admin/VersionAdminController.php:165
#: src/Admin/VersionAdminController.php:396
msgid "Uploaded file"
msgstr ""
#: src/Admin/VersionAdminController.php:169
#: src/Admin/VersionAdminController.php:400
msgid "No download file"
msgstr ""
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413
#: src/Admin/DashboardWidgetController.php:117
#: src/Admin/AdminController.php:156 src/Admin/AdminController.php:907
#: src/Admin/AdminController.php:1232 src/Admin/AdminController.php:1355
msgid "Active"
msgstr ""
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413 src/Admin/AdminController.php:157
#: src/Admin/AdminController.php:914 src/Admin/AdminController.php:1233
#: src/Admin/AdminController.php:1356
msgid "Inactive"
msgstr ""
#: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:419 src/Admin/AdminController.php:1274
#: src/Admin/AdminController.php:1459
msgid "Deactivate"
msgstr ""
#: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:419 src/Admin/AdminController.php:1273
#: src/Admin/AdminController.php:1458
msgid "Activate"
msgstr ""
#: src/Admin/VersionAdminController.php:191
#: src/Admin/VersionAdminController.php:422 src/Admin/AdminController.php:1279
#: src/Admin/AdminController.php:1428 src/Admin/AdminController.php:1464
msgid "Delete"
msgstr ""
#: src/Admin/VersionAdminController.php:232
msgid "Are you sure you want to delete this version?"
msgstr ""
#: src/Admin/VersionAdminController.php:233
msgid "Please enter a version number."
msgstr ""
#: src/Admin/VersionAdminController.php:234
msgid "Please enter a valid version number (e.g., 1.0.0)."
msgstr ""
#: src/Admin/VersionAdminController.php:235
msgid "An error occurred. Please try again."
msgstr ""
#: src/Admin/VersionAdminController.php:236
msgid "Select Download File"
msgstr ""
#: src/Admin/VersionAdminController.php:237
msgid "Use this file"
msgstr ""
#: src/Admin/VersionAdminController.php:238
msgid ""
"Invalid checksum file format. File must contain a 64-character SHA256 hash."
msgstr ""
#: src/Admin/VersionAdminController.php:239
msgid "Failed to read checksum file."
msgstr ""
#: src/Admin/VersionAdminController.php:269
msgid "Product ID and version are required."
msgstr ""
#: src/Admin/VersionAdminController.php:274
msgid "Invalid version format. Use semantic versioning (e.g., 1.0.0)."
msgstr ""
#: src/Admin/VersionAdminController.php:279
msgid "This version already exists."
msgstr ""
#: src/Admin/VersionAdminController.php:285
msgid "Product not found."
msgstr ""
#: src/Admin/VersionAdminController.php:289
msgid "This product is not a licensed product."
msgstr ""
#: src/Admin/VersionAdminController.php:306
msgid "Failed to create version."
msgstr ""
#: src/Admin/VersionAdminController.php:314
msgid "Version added successfully."
msgstr ""
#: src/Admin/VersionAdminController.php:334
#: src/Admin/VersionAdminController.php:361
msgid "Version ID is required."
msgstr ""
#: src/Admin/VersionAdminController.php:340
msgid "Failed to delete version."
msgstr ""
#: src/Admin/VersionAdminController.php:343
msgid "Version deleted successfully."
msgstr ""
#: src/Admin/VersionAdminController.php:367
msgid "Failed to update version."
msgstr ""
#: src/Admin/VersionAdminController.php:371
msgid "Version updated successfully."
msgstr ""
#: src/Admin/DashboardWidgetController.php:47
msgid "License Statistics"
msgstr ""
#: src/Admin/DashboardWidgetController.php:113
#: src/Admin/AdminController.php:900
msgid "Total Licenses"
msgstr ""
#: src/Admin/DashboardWidgetController.php:121
msgid "Expiring Soon"
msgstr ""
#: src/Admin/DashboardWidgetController.php:125
#: src/Admin/AdminController.php:158 src/Admin/AdminController.php:921
#: src/Admin/AdminController.php:1234 src/Admin/AdminController.php:1357
#: src/Email/LicenseExpiredEmail.php:210 src/Email/LicenseExpiredEmail.php:259
msgid "Expired"
msgstr ""
#: src/Admin/DashboardWidgetController.php:132
msgid "Time-limited"
msgstr ""
#: src/Admin/DashboardWidgetController.php:142
msgid "View All Licenses"
msgstr ""
#: src/Admin/AdminController.php:93
msgid "Overview"
msgstr ""
@@ -410,28 +641,6 @@ msgstr ""
msgid "Copy failed"
msgstr ""
#: src/Admin/AdminController.php:156 src/Admin/AdminController.php:907
#: src/Admin/AdminController.php:1232 src/Admin/AdminController.php:1355
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413
#: src/Admin/DashboardWidgetController.php:151
msgid "Active"
msgstr ""
#: src/Admin/AdminController.php:157 src/Admin/AdminController.php:914
#: src/Admin/AdminController.php:1233 src/Admin/AdminController.php:1356
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413
msgid "Inactive"
msgstr ""
#: src/Admin/AdminController.php:158 src/Admin/AdminController.php:921
#: src/Admin/AdminController.php:1234 src/Admin/AdminController.php:1357
#: src/Admin/DashboardWidgetController.php:159
#: src/Email/LicenseExpiredEmail.php:210 src/Email/LicenseExpiredEmail.php:259
msgid "Expired"
msgstr ""
#: src/Admin/AdminController.php:159 src/Admin/AdminController.php:928
#: src/Admin/AdminController.php:1235 src/Admin/AdminController.php:1358
msgid "Revoked"
@@ -514,11 +723,6 @@ msgstr ""
msgid "License Dashboard"
msgstr ""
#: src/Admin/AdminController.php:900
#: src/Admin/DashboardWidgetController.php:147
msgid "Total Licenses"
msgstr ""
#: src/Admin/AdminController.php:937
msgid "Attention:"
msgstr ""
@@ -566,7 +770,6 @@ msgstr ""
msgid "License set to lifetime successfully."
msgstr ""
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1106
#, php-format
msgid "%d license activated."
@@ -574,7 +777,6 @@ msgid_plural "%d licenses activated."
msgstr[0] ""
msgstr[1] ""
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1114
#, php-format
msgid "%d license deactivated."
@@ -582,7 +784,6 @@ msgid_plural "%d licenses deactivated."
msgstr[0] ""
msgstr[1] ""
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1122
#, php-format
msgid "%d license revoked."
@@ -590,7 +791,6 @@ msgid_plural "%d licenses revoked."
msgstr[0] ""
msgstr[1] ""
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1130
#, php-format
msgid "%d license deleted."
@@ -598,7 +798,6 @@ msgid_plural "%d licenses deleted."
msgstr[0] ""
msgstr[1] ""
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1138
#, php-format
msgid "%d license extended."
@@ -618,7 +817,6 @@ msgstr ""
msgid "No licenses to export."
msgstr ""
#. translators: %d: number of licenses imported
#: src/Admin/AdminController.php:1159
#, php-format
msgid "%d license imported."
@@ -626,7 +824,6 @@ msgid_plural "%d licenses imported."
msgstr[0] ""
msgstr[1] ""
#. translators: %d: number of licenses updated
#: src/Admin/AdminController.php:1166
#, php-format
msgid "%d updated."
@@ -634,7 +831,6 @@ msgid_plural "%d updated."
msgstr[0] ""
msgstr[1] ""
#. translators: %d: number of licenses skipped
#: src/Admin/AdminController.php:1174
#, php-format
msgid "%d skipped."
@@ -642,7 +838,6 @@ msgid_plural "%d skipped."
msgstr[0] ""
msgstr[1] ""
#. translators: %d: number of errors
#: src/Admin/AdminController.php:1182
#, php-format
msgid "%d error."
@@ -718,18 +913,6 @@ msgstr ""
msgid "Bulk Actions"
msgstr ""
#: src/Admin/AdminController.php:1273 src/Admin/AdminController.php:1458
#: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:419
msgid "Activate"
msgstr ""
#: src/Admin/AdminController.php:1274 src/Admin/AdminController.php:1459
#: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:419
msgid "Deactivate"
msgstr ""
#: src/Admin/AdminController.php:1275 src/Admin/AdminController.php:1419
#: src/Admin/AdminController.php:1460
msgid "Revoke"
@@ -747,12 +930,6 @@ msgstr ""
msgid "Extend 1 year"
msgstr ""
#: src/Admin/AdminController.php:1279 src/Admin/AdminController.php:1428
#: src/Admin/AdminController.php:1464 src/Admin/VersionAdminController.php:191
#: src/Admin/VersionAdminController.php:422
msgid "Delete"
msgstr ""
#: src/Admin/AdminController.php:1281 src/Admin/AdminController.php:1466
msgid "Apply"
msgstr ""
@@ -847,11 +1024,6 @@ msgstr ""
msgid "License is VALID"
msgstr ""
#: src/Admin/AdminController.php:1609 src/Admin/VersionAdminController.php:81
#: src/Admin/VersionAdminController.php:136
msgid "Version"
msgstr ""
#: src/Admin/AdminController.php:1617
msgid "License is INVALID"
msgstr ""
@@ -967,216 +1139,25 @@ msgstr ""
msgid "No domain specified"
msgstr ""
#: src/Admin/VersionAdminController.php:58
msgid "Product Versions"
#: src/Admin/DownloadWidgetController.php:46
msgid "Download Statistics"
msgstr ""
#: src/Admin/VersionAdminController.php:78
msgid "Add New Version"
#: src/Admin/DownloadWidgetController.php:140
msgid "Total Downloads"
msgstr ""
#: src/Admin/VersionAdminController.php:84
msgid "Use semantic versioning (e.g., 1.0.0)"
#: src/Admin/DownloadWidgetController.php:145
msgid "Top Products"
msgstr ""
#: src/Admin/VersionAdminController.php:88
#: src/Admin/VersionAdminController.php:137
msgid "Download File"
#: src/Admin/DownloadWidgetController.php:159
#: src/Admin/DownloadWidgetController.php:180
msgid "No downloads yet"
msgstr ""
#: src/Admin/VersionAdminController.php:93
msgid "Select File"
msgstr ""
#: src/Admin/VersionAdminController.php:96
#: src/Admin/VersionAdminController.php:110
msgid "Remove"
msgstr ""
#: src/Admin/VersionAdminController.php:98
msgid ""
"Upload or select a file from the media library. Version will be auto-"
"detected from filename (e.g., plugin-v1.2.3.zip)."
msgstr ""
#: src/Admin/VersionAdminController.php:102
msgid "Checksum File"
msgstr ""
#: src/Admin/VersionAdminController.php:107
msgid "Select Checksum File"
msgstr ""
#: src/Admin/VersionAdminController.php:112
msgid ""
"Upload a SHA256 checksum file (.sha256 or .txt) to verify file integrity."
msgstr ""
#: src/Admin/VersionAdminController.php:116
#: src/Admin/VersionAdminController.php:139
msgid "Release Notes"
msgstr ""
#: src/Admin/VersionAdminController.php:124
msgid "Add Version"
msgstr ""
#: src/Admin/VersionAdminController.php:132
msgid "Existing Versions"
msgstr ""
#: src/Admin/VersionAdminController.php:138
msgid "SHA256"
msgstr ""
#: src/Admin/VersionAdminController.php:141
msgid "Released"
msgstr ""
#: src/Admin/VersionAdminController.php:148
msgid "No versions found. Add your first version above."
msgstr ""
#: src/Admin/VersionAdminController.php:165
#: src/Admin/VersionAdminController.php:396
msgid "Uploaded file"
msgstr ""
#: src/Admin/VersionAdminController.php:169
#: src/Admin/VersionAdminController.php:400
msgid "No download file"
msgstr ""
#: src/Admin/VersionAdminController.php:232
msgid "Are you sure you want to delete this version?"
msgstr ""
#: src/Admin/VersionAdminController.php:233
msgid "Please enter a version number."
msgstr ""
#: src/Admin/VersionAdminController.php:234
msgid "Please enter a valid version number (e.g., 1.0.0)."
msgstr ""
#: src/Admin/VersionAdminController.php:235
msgid "An error occurred. Please try again."
msgstr ""
#: src/Admin/VersionAdminController.php:236
msgid "Select Download File"
msgstr ""
#: src/Admin/VersionAdminController.php:237
msgid "Use this file"
msgstr ""
#: src/Admin/VersionAdminController.php:238
msgid ""
"Invalid checksum file format. File must contain a 64-character SHA256 hash."
msgstr ""
#: src/Admin/VersionAdminController.php:239
msgid "Failed to read checksum file."
msgstr ""
#: src/Admin/VersionAdminController.php:269
msgid "Product ID and version are required."
msgstr ""
#: src/Admin/VersionAdminController.php:274
msgid "Invalid version format. Use semantic versioning (e.g., 1.0.0)."
msgstr ""
#: src/Admin/VersionAdminController.php:279
msgid "This version already exists."
msgstr ""
#: src/Admin/VersionAdminController.php:285
msgid "Product not found."
msgstr ""
#: src/Admin/VersionAdminController.php:289
msgid "This product is not a licensed product."
msgstr ""
#: src/Admin/VersionAdminController.php:306
msgid "Failed to create version."
msgstr ""
#: src/Admin/VersionAdminController.php:314
msgid "Version added successfully."
msgstr ""
#: src/Admin/VersionAdminController.php:334
#: src/Admin/VersionAdminController.php:361
msgid "Version ID is required."
msgstr ""
#: src/Admin/VersionAdminController.php:340
msgid "Failed to delete version."
msgstr ""
#: src/Admin/VersionAdminController.php:343
msgid "Version deleted successfully."
msgstr ""
#: src/Admin/VersionAdminController.php:367
msgid "Failed to update version."
msgstr ""
#: src/Admin/VersionAdminController.php:371
msgid "Version updated successfully."
msgstr ""
#: src/Admin/DashboardWidgetController.php:47
msgid "License Statistics"
msgstr ""
#: src/Admin/DashboardWidgetController.php:155
msgid "Expiring Soon"
msgstr ""
#: src/Admin/DashboardWidgetController.php:166
msgid "Status Breakdown"
msgstr ""
#: src/Admin/DashboardWidgetController.php:172
#, php-format
msgid "Active: %d"
msgstr ""
#: src/Admin/DashboardWidgetController.php:179
#, php-format
msgid "Inactive: %d"
msgstr ""
#: src/Admin/DashboardWidgetController.php:186
#, php-format
msgid "Expired: %d"
msgstr ""
#: src/Admin/DashboardWidgetController.php:193
#, php-format
msgid "Revoked: %d"
msgstr ""
#: src/Admin/DashboardWidgetController.php:202
msgid "License Types"
msgstr ""
#: src/Admin/DashboardWidgetController.php:207
#, php-format
msgid "Time-limited: %d"
msgstr ""
#: src/Admin/DashboardWidgetController.php:213
#, php-format
msgid "Lifetime: %d"
msgstr ""
#: src/Admin/DashboardWidgetController.php:220
msgid "View All Licenses"
#: src/Admin/DownloadWidgetController.php:163
msgid "Top Versions"
msgstr ""
#: src/Api/RestApiController.php:84
@@ -1274,7 +1255,8 @@ msgstr ""
msgid "This license is not valid for this domain."
msgstr ""
#: src/License/LicenseManager.php:760 src/Frontend/AccountController.php:140
#: src/License/LicenseManager.php:760 src/Product/VersionManager.php:349
#: src/Product/VersionManager.php:361 src/Frontend/AccountController.php:140
#: src/Email/LicenseExpirationEmail.php:107
#: src/Email/LicenseExpiredEmail.php:99
msgid "Unknown Product"
@@ -1284,7 +1266,6 @@ msgstr ""
msgid "Attachment file not found."
msgstr ""
#. translators: 1: provided hash, 2: calculated hash
#: src/Product/VersionManager.php:177
#, php-format
msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
@@ -1303,7 +1284,6 @@ msgstr ""
msgid "%d days"
msgstr ""
#. translators: %s: URL to settings page
#: src/Product/LicensedProductType.php:119
#, php-format
msgid "Leave fields empty to use default settings from %s."
@@ -1317,7 +1297,6 @@ msgstr ""
msgid "Max Activations"
msgstr ""
#. translators: %d: default max activations value
#: src/Product/LicensedProductType.php:131
#, php-format
msgid "Maximum number of domain activations per license. Default: %d"
@@ -1327,7 +1306,6 @@ msgstr ""
msgid "License Validity (Days)"
msgstr ""
#. translators: %s: default validity value
#: src/Product/LicensedProductType.php:149
#, php-format
msgid "Number of days the license is valid. Leave empty for default (%s)."
@@ -1337,7 +1315,6 @@ msgstr ""
msgid "Bind to Major Version"
msgstr ""
#. translators: %s: default bind to version value (Yes/No)
#: src/Product/LicensedProductType.php:167
#, php-format
msgid ""
@@ -1357,54 +1334,54 @@ msgstr ""
msgid "Version:"
msgstr ""
#: src/Frontend/DownloadController.php:65
#: src/Frontend/DownloadController.php:89
#: src/Frontend/DownloadController.php:77
#: src/Frontend/DownloadController.php:101
msgid "Invalid download link."
msgstr ""
#: src/Frontend/DownloadController.php:66
#: src/Frontend/DownloadController.php:76
#: src/Frontend/DownloadController.php:90
#: src/Frontend/DownloadController.php:106
#: src/Frontend/DownloadController.php:116
#: src/Frontend/DownloadController.php:125
#: src/Frontend/DownloadController.php:135
#: src/Frontend/DownloadController.php:144
#: src/Frontend/DownloadController.php:153
#: src/Frontend/DownloadController.php:171
#: src/Frontend/DownloadController.php:78
#: src/Frontend/DownloadController.php:88
#: src/Frontend/DownloadController.php:102
#: src/Frontend/DownloadController.php:118
#: src/Frontend/DownloadController.php:128
#: src/Frontend/DownloadController.php:137
#: src/Frontend/DownloadController.php:147
#: src/Frontend/DownloadController.php:156
#: src/Frontend/DownloadController.php:165
#: src/Frontend/DownloadController.php:187
#: src/Frontend/DownloadController.php:203
msgid "Download Error"
msgstr ""
#: src/Frontend/DownloadController.php:75
#: src/Frontend/DownloadController.php:87
msgid "Invalid download link format."
msgstr ""
#: src/Frontend/DownloadController.php:115
#: src/Frontend/DownloadController.php:127
msgid "You do not have permission to download this file."
msgstr ""
#: src/Frontend/DownloadController.php:124
#: src/Frontend/DownloadController.php:136
msgid "Your license is not active. Please contact support."
msgstr ""
#: src/Frontend/DownloadController.php:134
#: src/Frontend/DownloadController.php:146
msgid "Version not found."
msgstr ""
#: src/Frontend/DownloadController.php:143
#: src/Frontend/DownloadController.php:155
msgid "Version does not match your licensed product."
msgstr ""
#: src/Frontend/DownloadController.php:152
#: src/Frontend/DownloadController.php:164
msgid "This version is no longer available for download."
msgstr ""
#: src/Frontend/DownloadController.php:170
#: src/Frontend/DownloadController.php:186
msgid "No download file available for this version."
msgstr ""
#: src/Frontend/DownloadController.php:186
#: src/Frontend/DownloadController.php:202
msgid "Download file not found."
msgstr ""
@@ -1560,7 +1537,6 @@ msgid ""
"expiration date."
msgstr ""
#. translators: %s: list of placeholders
#: src/Email/LicenseExpirationEmail.php:301
#: src/Email/LicenseExpiredEmail.php:288
#, php-format
@@ -1666,20 +1642,19 @@ msgstr ""
msgid "To continue using this product, please renew your license."
msgstr ""
#: src/Plugin.php:258
#: src/Plugin.php:260
msgid "WC Licensed Product"
msgstr ""
#: src/Plugin.php:259
#: src/Plugin.php:261
msgid ""
"Plugin license is not configured or invalid. Frontend features are disabled."
msgstr ""
#: src/Plugin.php:260
#: src/Plugin.php:262
msgid "Configure License"
msgstr ""
#. translators: %s: WooCommerce plugin name
#: wc-licensed-product.php:61
#, php-format
msgid "%s requires WooCommerce to be installed and active."

View File

@@ -55,7 +55,7 @@ final class DashboardWidgetController
public function renderWidget(): void
{
$stats = $this->licenseManager->getStatistics();
$licensesUrl = admin_url('admin.php?page=wc-licensed-product-licenses');
$licensesUrl = admin_url('admin.php?page=wc-licenses');
?>
<style>
.wclp-widget-stats {
@@ -96,40 +96,6 @@ final class DashboardWidgetController
letter-spacing: 0.5px;
margin-top: 4px;
}
.wclp-widget-divider {
border-top: 1px solid #e2e4e7;
margin: 16px 0;
}
.wclp-status-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.wclp-status-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.wclp-status-badge.active {
background: #d4edda;
color: #155724;
}
.wclp-status-badge.inactive {
background: #e2e3e5;
color: #383d41;
}
.wclp-status-badge.expired {
background: #f8d7da;
color: #721c24;
}
.wclp-status-badge.revoked {
background: #d6d8db;
color: #1b1e21;
}
.wclp-widget-footer {
margin-top: 16px;
padding-top: 12px;
@@ -160,60 +126,16 @@ final class DashboardWidgetController
</div>
</div>
<div class="wclp-widget-divider"></div>
<h4 style="margin: 0 0 8px 0; font-size: 13px; color: #1d2327;">
<?php esc_html_e('Status Breakdown', 'wc-licensed-product'); ?>
</h4>
<div class="wclp-status-list">
<span class="wclp-status-badge active">
<span class="dashicons dashicons-yes-alt" style="font-size: 14px; width: 14px; height: 14px;"></span>
<?php printf(
esc_html__('Active: %d', 'wc-licensed-product'),
$stats['by_status'][License::STATUS_ACTIVE]
); ?>
</span>
<span class="wclp-status-badge inactive">
<span class="dashicons dashicons-marker" style="font-size: 14px; width: 14px; height: 14px;"></span>
<?php printf(
esc_html__('Inactive: %d', 'wc-licensed-product'),
$stats['by_status'][License::STATUS_INACTIVE]
); ?>
</span>
<span class="wclp-status-badge expired">
<span class="dashicons dashicons-clock" style="font-size: 14px; width: 14px; height: 14px;"></span>
<?php printf(
esc_html__('Expired: %d', 'wc-licensed-product'),
$stats['by_status'][License::STATUS_EXPIRED]
); ?>
</span>
<span class="wclp-status-badge revoked">
<span class="dashicons dashicons-dismiss" style="font-size: 14px; width: 14px; height: 14px;"></span>
<?php printf(
esc_html__('Revoked: %d', 'wc-licensed-product'),
$stats['by_status'][License::STATUS_REVOKED]
); ?>
</span>
<div class="wclp-widget-stats">
<div class="wclp-stat-card">
<div class="wclp-stat-number"><?php echo esc_html(number_format_i18n($stats['expiring'])); ?></div>
<div class="wclp-stat-label"><?php esc_html_e('Time-limited', 'wc-licensed-product'); ?></div>
</div>
<div class="wclp-stat-card">
<div class="wclp-stat-number"><?php echo esc_html(number_format_i18n($stats['lifetime'])); ?></div>
<div class="wclp-stat-label"><?php esc_html_e('Lifetime', 'wc-licensed-product'); ?></div>
</div>
</div>
<div class="wclp-widget-divider"></div>
<h4 style="margin: 0 0 8px 0; font-size: 13px; color: #1d2327;">
<?php esc_html_e('License Types', 'wc-licensed-product'); ?>
</h4>
<p style="margin: 0; font-size: 13px; color: #646970;">
<span class="dashicons dashicons-calendar-alt" style="font-size: 14px; width: 14px; height: 14px; vertical-align: text-bottom;"></span>
<?php printf(
esc_html__('Time-limited: %d', 'wc-licensed-product'),
$stats['expiring']
); ?>
&nbsp;&nbsp;|&nbsp;&nbsp;
<span class="dashicons dashicons-infinity" style="font-size: 14px; width: 14px; height: 14px; vertical-align: text-bottom;"></span>
<?php printf(
esc_html__('Lifetime: %d', 'wc-licensed-product'),
$stats['lifetime']
); ?>
</p>
<div class="wclp-widget-footer">
<a href="<?php echo esc_url($licensesUrl); ?>" class="button button-secondary">

View File

@@ -0,0 +1,184 @@
<?php
/**
* Download Statistics Widget Controller
*
* @package Jeremias\WcLicensedProduct\Admin
*/
declare(strict_types=1);
namespace Jeremias\WcLicensedProduct\Admin;
use Jeremias\WcLicensedProduct\Product\VersionManager;
/**
* Handles the WordPress admin dashboard widget for download statistics
*/
final class DownloadWidgetController
{
private VersionManager $versionManager;
public function __construct(VersionManager $versionManager)
{
$this->versionManager = $versionManager;
$this->registerHooks();
}
/**
* Register WordPress hooks
*/
private function registerHooks(): void
{
add_action('wp_dashboard_setup', [$this, 'registerDashboardWidget']);
}
/**
* Register the dashboard widget
*/
public function registerDashboardWidget(): void
{
if (!current_user_can('manage_woocommerce')) {
return;
}
wp_add_dashboard_widget(
'wclp_download_statistics',
__('Download Statistics', 'wc-licensed-product'),
[$this, 'renderWidget']
);
}
/**
* Render the dashboard widget content
*/
public function renderWidget(): void
{
$stats = $this->versionManager->getDownloadStatistics();
?>
<style>
.wclp-download-widget-stats {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
margin-bottom: 16px;
}
.wclp-download-stat-card {
background: #f8f9fa;
border: 1px solid #e2e4e7;
border-radius: 4px;
padding: 12px;
text-align: center;
border-left: 3px solid #2271b1;
}
.wclp-download-stat-number {
font-size: 32px;
font-weight: 600;
color: #1d2327;
line-height: 1.2;
}
.wclp-download-stat-label {
font-size: 12px;
color: #646970;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 4px;
}
.wclp-download-list {
margin: 0;
padding: 0;
list-style: none;
}
.wclp-download-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #e2e4e7;
}
.wclp-download-list li:last-child {
border-bottom: none;
}
.wclp-download-list .product-name {
font-weight: 500;
color: #1d2327;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 12px;
}
.wclp-download-list .version-info {
font-size: 12px;
color: #646970;
}
.wclp-download-list .download-count {
background: #e7f5ff;
color: #0a4b78;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
.wclp-download-section-title {
margin: 16px 0 8px 0;
font-size: 13px;
color: #1d2327;
font-weight: 600;
}
.wclp-no-downloads {
color: #646970;
font-style: italic;
text-align: center;
padding: 12px 0;
}
</style>
<div class="wclp-download-widget-stats">
<div class="wclp-download-stat-card">
<div class="wclp-download-stat-number"><?php echo esc_html(number_format_i18n($stats['total'])); ?></div>
<div class="wclp-download-stat-label"><?php esc_html_e('Total Downloads', 'wc-licensed-product'); ?></div>
</div>
</div>
<h4 class="wclp-download-section-title">
<?php esc_html_e('Top Products', 'wc-licensed-product'); ?>
</h4>
<?php if (!empty($stats['by_product'])): ?>
<ul class="wclp-download-list">
<?php foreach (array_slice($stats['by_product'], 0, 5) as $product): ?>
<li>
<span class="product-name"><?php echo esc_html($product['product_name']); ?></span>
<span class="download-count">
<?php echo esc_html(number_format_i18n($product['downloads'])); ?>
</span>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p class="wclp-no-downloads"><?php esc_html_e('No downloads yet', 'wc-licensed-product'); ?></p>
<?php endif; ?>
<h4 class="wclp-download-section-title">
<?php esc_html_e('Top Versions', 'wc-licensed-product'); ?>
</h4>
<?php if (!empty($stats['by_version'])): ?>
<ul class="wclp-download-list">
<?php foreach (array_slice($stats['by_version'], 0, 5) as $version): ?>
<li>
<span class="product-name">
<?php echo esc_html($version['product_name']); ?>
<span class="version-info">v<?php echo esc_html($version['version']); ?></span>
</span>
<span class="download-count">
<?php echo esc_html(number_format_i18n($version['downloads'])); ?>
</span>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p class="wclp-no-downloads"><?php esc_html_e('No downloads yet', 'wc-licensed-product'); ?></p>
<?php endif; ?>
<?php
}
}

View File

@@ -35,6 +35,9 @@ final class DownloadController
// Add download endpoint
add_action('init', [$this, 'addDownloadEndpoint']);
// Register query var for the endpoint
add_filter('query_vars', [$this, 'addDownloadQueryVar']);
// Handle download requests
add_action('template_redirect', [$this, 'handleDownloadRequest']);
}
@@ -47,6 +50,15 @@ final class DownloadController
add_rewrite_endpoint('license-download', EP_ROOT | EP_PAGES);
}
/**
* Register the download query var
*/
public function addDownloadQueryVar(array $vars): array
{
$vars[] = 'license-download';
return $vars;
}
/**
* Handle download request
*/
@@ -160,8 +172,12 @@ final class DownloadController
$downloadUrl = $version->getDownloadUrl();
if ($attachmentId) {
// Increment download count before serving
$this->versionManager->incrementDownloadCount($versionId);
$this->serveAttachment($attachmentId, $version->getVersion());
} elseif ($downloadUrl) {
// Increment download count before redirect
$this->versionManager->incrementDownloadCount($versionId);
// Redirect to external URL
wp_redirect($downloadUrl);
exit;

View File

@@ -35,8 +35,9 @@ final class Installer
// Set version in options
update_option('wc_licensed_product_version', WC_LICENSED_PRODUCT_VERSION);
// Register the licenses endpoint before flushing rewrite rules
// Register endpoints before flushing rewrite rules
add_rewrite_endpoint('licenses', EP_ROOT | EP_PAGES);
add_rewrite_endpoint('license-download', EP_ROOT | EP_PAGES);
// Flush rewrite rules for REST API and My Account endpoints
flush_rewrite_rules();
@@ -103,6 +104,7 @@ final class Installer
download_url VARCHAR(512) DEFAULT NULL,
attachment_id BIGINT UNSIGNED DEFAULT NULL,
file_hash VARCHAR(64) DEFAULT NULL,
download_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
is_active TINYINT(1) NOT NULL DEFAULT 1,
released_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

View File

@@ -11,6 +11,7 @@ namespace Jeremias\WcLicensedProduct;
use Jeremias\WcLicensedProduct\Admin\AdminController;
use Jeremias\WcLicensedProduct\Admin\DashboardWidgetController;
use Jeremias\WcLicensedProduct\Admin\DownloadWidgetController;
use Jeremias\WcLicensedProduct\Admin\OrderLicenseController;
use Jeremias\WcLicensedProduct\Admin\SettingsController;
use Jeremias\WcLicensedProduct\Admin\VersionAdminController;
@@ -154,6 +155,7 @@ final class Plugin
new OrderLicenseController($this->licenseManager);
new SettingsController();
new DashboardWidgetController($this->licenseManager);
new DownloadWidgetController($this->versionManager);
// Show admin notice if unlicensed and not on localhost
if (!$isLicensed && !$licenseChecker->isLocalhost()) {

View File

@@ -24,6 +24,7 @@ class ProductVersion
private ?string $downloadUrl;
private ?int $attachmentId;
private ?string $fileHash;
private int $downloadCount;
private bool $isActive;
private \DateTimeInterface $releasedAt;
private \DateTimeInterface $createdAt;
@@ -44,6 +45,7 @@ class ProductVersion
$version->downloadUrl = $data['download_url'] ?: null;
$version->attachmentId = !empty($data['attachment_id']) ? (int) $data['attachment_id'] : null;
$version->fileHash = $data['file_hash'] ?? null;
$version->downloadCount = (int) ($data['download_count'] ?? 0);
$version->isActive = (bool) $data['is_active'];
$version->releasedAt = new \DateTimeImmutable($data['released_at']);
$version->createdAt = new \DateTimeImmutable($data['created_at']);
@@ -144,6 +146,11 @@ class ProductVersion
return $this->fileHash;
}
public function getDownloadCount(): int
{
return $this->downloadCount;
}
/**
* Get the download URL from attachment
*/
@@ -197,6 +204,7 @@ class ProductVersion
'download_url' => $this->downloadUrl,
'attachment_id' => $this->attachmentId,
'file_hash' => $this->fileHash,
'download_count' => $this->downloadCount,
'is_active' => $this->isActive,
'released_at' => $this->releasedAt->format('Y-m-d H:i:s'),
'created_at' => $this->createdAt->format('Y-m-d H:i:s'),

View File

@@ -276,4 +276,98 @@ class VersionManager
return (int) $count > 0;
}
/**
* Increment download count for a version
*/
public function incrementDownloadCount(int $versionId): bool
{
global $wpdb;
$tableName = Installer::getVersionsTable();
$result = $wpdb->query(
$wpdb->prepare(
"UPDATE {$tableName} SET download_count = download_count + 1 WHERE id = %d",
$versionId
)
);
return $result !== false;
}
/**
* Get total download count across all versions
*/
public function getTotalDownloadCount(): int
{
global $wpdb;
$tableName = Installer::getVersionsTable();
$count = $wpdb->get_var("SELECT COALESCE(SUM(download_count), 0) FROM {$tableName}");
return (int) $count;
}
/**
* Get download statistics per product
*/
public function getDownloadStatistics(): array
{
global $wpdb;
$tableName = Installer::getVersionsTable();
// Get total downloads
$totalDownloads = $this->getTotalDownloadCount();
// Get downloads per product (top 10)
$byProduct = $wpdb->get_results(
"SELECT product_id, SUM(download_count) as downloads
FROM {$tableName}
GROUP BY product_id
ORDER BY downloads DESC
LIMIT 10",
ARRAY_A
);
// Get downloads per version (top 10)
$byVersion = $wpdb->get_results(
"SELECT id, product_id, version, download_count
FROM {$tableName}
WHERE download_count > 0
ORDER BY download_count DESC
LIMIT 10",
ARRAY_A
);
// Enrich product data with names
$productsWithNames = [];
foreach ($byProduct ?: [] as $row) {
$product = wc_get_product((int) $row['product_id']);
$productsWithNames[] = [
'product_id' => (int) $row['product_id'],
'product_name' => $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product'),
'downloads' => (int) $row['downloads'],
];
}
// Enrich version data with product names
$versionsWithNames = [];
foreach ($byVersion ?: [] as $row) {
$product = wc_get_product((int) $row['product_id']);
$versionsWithNames[] = [
'version_id' => (int) $row['id'],
'product_id' => (int) $row['product_id'],
'product_name' => $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product'),
'version' => $row['version'],
'downloads' => (int) $row['download_count'],
];
}
return [
'total' => $totalDownloads,
'by_product' => $productsWithNames,
'by_version' => $versionsWithNames,
];
}
}

View File

@@ -3,7 +3,7 @@
* Plugin Name: WooCommerce Licensed Product
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-licensed-product
* Description: WooCommerce plugin to sell software products using license keys with domain-based validation.
* Version: 0.3.6
* Version: 0.3.7
* Author: Marco Graetsch
* Author URI: https://src.bundespruefstelle.ch/magdev
* License: GPL-2.0-or-later
@@ -28,7 +28,7 @@ if (!defined('ABSPATH')) {
}
// Plugin constants
define('WC_LICENSED_PRODUCT_VERSION', '0.3.6');
define('WC_LICENSED_PRODUCT_VERSION', '0.3.7');
define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__);
define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__));