5 Commits

Author SHA1 Message Date
c7967f71ab Update translations for v0.3.5
- Added translations for dashboard widget strings
- Added translations for license expired email strings
- Updated fuzzy translations with proper German text
- Compiled .mo file for production use

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 16:10:51 +01:00
1de8257527 Add dashboard widget and auto-expire license cron (v0.3.5)
- Add admin dashboard widget with license statistics
- Add daily wp-cron to auto-expire licenses past expiration date
- Add LicenseExpiredEmail notification for expired licenses
- Add getExpiredActiveLicenses() and autoExpireLicense() to LicenseManager

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 16:05:52 +01:00
26245c0c57 Remove redundant version badge from download list
Version is already shown in the download filename

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 12:10:50 +01:00
a6c6d247aa Improve download list layout in customer account (v0.3.5)
- Downloads now displayed in two-row format per entry
- First row: file download link
- Second row: metadata (version, date, checksum)
- Better visual separation and readability

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 12:07:49 +01:00
fba8bf2352 Add release package for v0.3.4
- wc-licensed-product-0.3.4.zip (784 KB)
- SHA256: 36a81c00eb03adf5dfa633891664d44b7e5225bf1ee594904f8acc9adec6bb47

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 12:02:28 +01:00
15 changed files with 1042 additions and 95 deletions

View File

@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.3.5] - 2026-01-23
### Added
- Admin dashboard widget showing license statistics on WordPress dashboard
- Automatic license expiration via daily wp-cron job
- License expired email notification sent when license auto-expires
- New `LicenseExpiredEmail` WooCommerce email class (configurable via WooCommerce > Settings > Emails)
### Changed
- Improved download list layout in customer account licenses page
- Downloads now displayed in two-row format: file link on first row, metadata on second row
- Better visual separation between download link and version/date/checksum information
### Technical Details
- New `DashboardWidgetController` class in `src/Admin/` for WordPress dashboard widget
- Widget displays: total licenses, active, expiring soon, expired counts, status breakdown, license types
- New `LicenseExpiredEmail` class in `src/Email/` for expired license notifications
- Added `getExpiredActiveLicenses()` and `autoExpireLicense()` methods to `LicenseManager`
- Daily cron now auto-expires licenses with past expiration date and sends notification emails
- Updated `templates/frontend/licenses.html.twig` with new two-row structure
- Added `.download-item`, `.download-row-file`, `.download-row-meta` CSS classes
- Improved responsive behavior for download metadata
## [0.3.4] - 2026-01-23
### Added

View File

@@ -36,6 +36,10 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
No known bugs at the moment.
### 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.
## Technical Stack
- **Language:** PHP 8.3.x
@@ -932,3 +936,41 @@ Added current version display on single product pages for licensed products.
- Only displays for licensed product type
- Only displays if product has at least one version defined
- Uses `LicensedProduct::get_current_version()` which queries `VersionManager::getLatestVersion()`
### 2026-01-23 - Version 0.3.5 - Dashboard Widget & Auto-Expire
**Overview:**
Added admin dashboard widget for license statistics and automatic license expiration via daily cron job.
**Implemented:**
- Admin dashboard widget showing license statistics (total, active, expiring soon, expired)
- Status breakdown display with color-coded badges
- License type breakdown (time-limited vs lifetime)
- Daily wp-cron job to auto-expire licenses past their expiration date
- License expired email notification sent when license auto-expires
- Downloads in customer account now displayed in two-row format
**New files:**
- `src/Admin/DashboardWidgetController.php` - WordPress dashboard widget controller
- `src/Email/LicenseExpiredEmail.php` - WooCommerce email for expired license notifications
**Modified files:**
- `src/Plugin.php` - Added DashboardWidgetController instantiation
- `src/License/LicenseManager.php` - Added `getExpiredActiveLicenses()` and `autoExpireLicense()` methods
- `src/Email/LicenseEmailController.php` - Added auto-expire logic and LicenseExpiredEmail registration
- `templates/frontend/licenses.html.twig` - Restructured download list with two-row layout
- `assets/css/frontend.css` - Added dashboard widget and download list styles
**Technical notes:**
- Dashboard widget uses `wp_add_dashboard_widget()` hook, requires `manage_woocommerce` capability
- Widget displays statistics from existing `LicenseManager::getStatistics()` method
- Auto-expire runs during daily `wclp_check_expiring_licenses` cron event
- `getExpiredActiveLicenses()` finds licenses with past expiration date but still active status
- `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

View File

@@ -202,18 +202,30 @@
padding: 0;
}
.download-list li {
.download-list li.download-item {
display: flex;
align-items: center;
gap: 1em;
padding: 0.5em 0;
flex-direction: column;
gap: 0.35em;
padding: 0.75em 0;
border-bottom: 1px solid #eee;
}
.download-list li:last-child {
.download-list li.download-item:last-child {
border-bottom: none;
}
.download-row-file {
display: flex;
align-items: center;
}
.download-row-meta {
display: flex;
align-items: center;
gap: 1em;
padding-left: 1.5em;
}
.download-link {
display: inline-flex;
align-items: center;
@@ -244,7 +256,6 @@
.download-date {
color: #999;
font-size: 0.85em;
margin-left: auto;
}
.download-hash {
@@ -338,15 +349,11 @@
gap: 0.5em;
}
.download-list li {
.download-row-meta {
padding-left: 0;
flex-wrap: wrap;
}
.download-date {
margin-left: 0;
width: 100%;
}
.woocommerce-licenses-table,
.woocommerce-licenses-table thead,
.woocommerce-licenses-table tbody,

View File

@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: WC Licensed Product 0.3.1\n"
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
"POT-Creation-Date: 2026-01-23 11:54+0100\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-23 16:05+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"
@@ -82,13 +82,13 @@ msgstr ""
#: src/Admin/OrderLicenseController.php:144
#: src/Admin/SettingsController.php:142 src/Admin/AdminController.php:1280
#: src/Admin/AdminController.php:1431 src/Admin/AdminController.php:1480
#: src/Email/LicenseEmailController.php:230
#: src/Email/LicenseEmailController.php:269
msgid "License Key"
msgstr "Lizenzschlüssel"
#: src/Admin/OrderLicenseController.php:145 src/Admin/AdminController.php:1281
#: src/Admin/AdminController.php:1432 src/Admin/AdminController.php:1597
#: src/Email/LicenseEmailController.php:229
#: src/Email/LicenseEmailController.php:268
msgid "Product"
msgstr "Produkt"
@@ -104,7 +104,7 @@ msgstr "Status"
#: src/Admin/OrderLicenseController.php:148 src/Admin/AdminController.php:1286
#: src/Admin/AdminController.php:1437 src/Admin/AdminController.php:1600
#: src/Admin/AdminController.php:1602 src/Email/LicenseEmailController.php:231
#: src/Admin/AdminController.php:1602 src/Email/LicenseEmailController.php:270
msgid "Expires"
msgstr "Läuft ab"
@@ -434,6 +434,7 @@ msgstr "Kopieren fehlgeschlagen"
#: src/Admin/AdminController.php:1221 src/Admin/AdminController.php:1344
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413
#: src/Admin/DashboardWidgetController.php:151
msgid "Active"
msgstr "Aktiv"
@@ -446,6 +447,8 @@ msgstr "Inaktiv"
#: src/Admin/AdminController.php:158 src/Admin/AdminController.php:916
#: src/Admin/AdminController.php:1223 src/Admin/AdminController.php:1346
#: src/Admin/DashboardWidgetController.php:159
#: src/Email/LicenseExpiredEmail.php:210 src/Email/LicenseExpiredEmail.php:259
msgid "Expired"
msgstr "Abgelaufen"
@@ -532,6 +535,7 @@ msgid "License Dashboard"
msgstr "Lizenz-Dashboard"
#: src/Admin/AdminController.php:895
#: src/Admin/DashboardWidgetController.php:147
msgid "Total Licenses"
msgstr "Lizenzen insgesamt"
@@ -768,6 +772,7 @@ msgstr "Anwenden"
#: src/Admin/AdminController.php:1282 src/Admin/AdminController.php:1433
#: src/Email/LicenseExpirationEmail.php:104
#: src/Email/LicenseExpiredEmail.php:96
msgid "Customer"
msgstr "Kunde"
@@ -1150,6 +1155,56 @@ msgstr "Version konnte nicht aktualisiert werden."
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/Api/RestApiController.php:84
msgid "Too many requests. Please try again later."
msgstr "Zu viele Anfragen. Bitte versuchen Sie es später erneut."
@@ -1241,6 +1296,7 @@ msgstr "Diese Lizenz ist für diese Domain nicht gültig."
#: src/License/LicenseManager.php:760 src/Frontend/AccountController.php:140
#: src/Email/LicenseExpirationEmail.php:107
#: src/Email/LicenseExpiredEmail.php:99
msgid "Unknown Product"
msgstr "Unbekanntes Produkt"
@@ -1252,6 +1308,15 @@ msgstr "Lizenzeinstellungen nicht konfiguriert."
msgid "Could not connect to license server."
msgstr "Verbindung zum Lizenzserver konnte nicht hergestellt werden."
#: src/Product/VersionManager.php:166
msgid "Attachment file not found."
msgstr "Anhangs-Datei nicht gefunden."
#: src/Product/VersionManager.php:177
#, php-format
msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
msgstr "Datei-Prüfsumme stimmt nicht überein. Erwartet: %1$s, Erhalten: %2$s"
#: src/Product/LicensedProductType.php:61
msgid "Licensed Product"
msgstr "Lizensiertes Produkt"
@@ -1317,15 +1382,6 @@ msgstr "Nein"
msgid "Version:"
msgstr "Version:"
#: src/Product/VersionManager.php:166
msgid "Attachment file not found."
msgstr "Anhangs-Datei nicht gefunden."
#: src/Product/VersionManager.php:177
#, php-format
msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
msgstr "Datei-Prüfsumme stimmt nicht überein. Erwartet: %1$s, Erhalten: %2$s"
#: src/Frontend/DownloadController.php:65
#: src/Frontend/DownloadController.php:89
msgid "Invalid download link."
@@ -1386,30 +1442,32 @@ msgid "You have no licenses yet."
msgstr "Sie haben noch keine Lizenzen."
#: src/Frontend/AccountController.php:190
#: src/Email/LicenseEmailController.php:173
#: src/Email/LicenseEmailController.php:177
#: src/Email/LicenseEmailController.php:281
#: src/Email/LicenseEmailController.php:212
#: src/Email/LicenseEmailController.php:216
#: src/Email/LicenseEmailController.php:320
#: src/Email/LicenseExpirationEmail.php:207
#: src/Email/LicenseExpirationEmail.php:270
#: src/Email/LicenseExpiredEmail.php:191 src/Email/LicenseExpiredEmail.php:256
msgid "License Key:"
msgstr "Lizenzschlüssel:"
#: src/Frontend/AccountController.php:201
#: src/Email/LicenseExpirationEmail.php:215
#: src/Email/LicenseExpirationEmail.php:271
#: src/Email/LicenseExpiredEmail.php:199 src/Email/LicenseExpiredEmail.php:257
msgid "Domain:"
msgstr "Domain:"
#: src/Frontend/AccountController.php:213
#: src/Email/LicenseEmailController.php:284
#: src/Email/LicenseEmailController.php:323
#: src/Email/LicenseExpirationEmail.php:219
#: src/Email/LicenseExpirationEmail.php:272
msgid "Expires:"
msgstr "Läuft ab:"
#: src/Frontend/AccountController.php:218
#: src/Email/LicenseEmailController.php:248
#: src/Email/LicenseEmailController.php:287
#: src/Email/LicenseEmailController.php:326
msgid "Never"
msgstr "Nie"
@@ -1467,22 +1525,22 @@ msgstr "Die neue Domain ist dieselbe wie die aktuelle Domain."
msgid "Failed to transfer license. Please try again."
msgstr "Lizenzübertragung fehlgeschlagen. Bitte versuchen Sie es erneut."
#: src/Email/LicenseEmailController.php:217
#: src/Email/LicenseEmailController.php:256
msgid "Your License Keys"
msgstr "Ihre Lizenzschlüssel"
#: src/Email/LicenseEmailController.php:221
#: src/Email/LicenseEmailController.php:276
#: src/Email/LicenseEmailController.php:260
#: src/Email/LicenseEmailController.php:315
msgid "Licensed Domain:"
msgstr "Lizensierte Domain:"
#: src/Email/LicenseEmailController.php:257
#: src/Email/LicenseEmailController.php:291
#: src/Email/LicenseEmailController.php:296
#: src/Email/LicenseEmailController.php:330
msgid "You can also view your licenses in your account under \"Licenses\"."
msgstr ""
"Sie können Ihre Lizenzen auch in Ihrem Konto unter \"Lizenzen\" einsehen."
#: src/Email/LicenseEmailController.php:272
#: src/Email/LicenseEmailController.php:311
msgid "YOUR LICENSE KEYS"
msgstr "IHRE LIZENZSCHLÜSSEL"
@@ -1512,6 +1570,7 @@ msgstr "Lizenzablauf-Benachrichtigung"
#: src/Email/LicenseExpirationEmail.php:176
#: src/Email/LicenseExpirationEmail.php:246
#: src/Email/LicenseExpiredEmail.php:167 src/Email/LicenseExpiredEmail.php:238
#, php-format
msgid "Hello %s,"
msgstr "Guten Tag %s,"
@@ -1535,11 +1594,13 @@ msgstr "Lizenzdetails"
#: src/Email/LicenseExpirationEmail.php:203
#: src/Email/LicenseExpirationEmail.php:269
#: src/Email/LicenseExpiredEmail.php:187 src/Email/LicenseExpiredEmail.php:255
msgid "Product:"
msgstr "Produkt:"
#: src/Email/LicenseExpirationEmail.php:235
#: src/Email/LicenseExpirationEmail.php:281
#: src/Email/LicenseExpiredEmail.php:227 src/Email/LicenseExpiredEmail.php:268
msgid "View My Licenses"
msgstr "Meine Lizenzen anzeigen"
@@ -1552,54 +1613,108 @@ msgstr ""
"dem Ablaufdatum."
#: src/Email/LicenseExpirationEmail.php:301
#: src/Email/LicenseExpiredEmail.php:288
#, php-format
msgid "Available placeholders: %s"
msgstr "Verfügbare Platzhalter: %s"
#: src/Email/LicenseExpirationEmail.php:307
#: src/Email/LicenseExpiredEmail.php:294
msgid "Enable/Disable"
msgstr "Aktivieren/Deaktivieren"
#: src/Email/LicenseExpirationEmail.php:309
#: src/Email/LicenseExpiredEmail.php:296
msgid "Enable this email notification"
msgstr "Diese E-Mail-Benachrichtigung aktivieren"
#: src/Email/LicenseExpirationEmail.php:313
#: src/Email/LicenseExpiredEmail.php:300
msgid "Subject"
msgstr "Betreff"
#: src/Email/LicenseExpirationEmail.php:321
#: src/Email/LicenseExpiredEmail.php:308
msgid "Email heading"
msgstr "E-Mail-Überschrift"
#: src/Email/LicenseExpirationEmail.php:329
#: src/Email/LicenseExpiredEmail.php:316
msgid "Additional content"
msgstr "Zusätzlicher Inhalt"
#: src/Email/LicenseExpirationEmail.php:330
#: src/Email/LicenseExpiredEmail.php:317
msgid "Text to appear below the main email content."
msgstr "Text, der unter dem Haupt-E-Mail-Inhalt erscheinen soll."
#: src/Email/LicenseExpirationEmail.php:338
#: src/Email/LicenseExpiredEmail.php:325
msgid "Email type"
msgstr "E-Mail-Typ"
#: src/Email/LicenseExpirationEmail.php:340
#: src/Email/LicenseExpiredEmail.php:327
msgid "Choose which format of email to send."
msgstr "Wählen Sie, welches E-Mail-Format gesendet werden soll."
#: src/Plugin.php:255
#: src/Email/LicenseExpiredEmail.php:50 src/Email/LicenseExpiredEmail.php:76
msgid "License Expired"
msgstr "Lizenz abgelaufen"
#: src/Email/LicenseExpiredEmail.php:51
msgid ""
"License expired emails are sent to customers when their licenses have "
"expired."
msgstr ""
"Lizenzablauf-E-Mails werden an Kunden gesendet, wenn ihre Lizenzen "
"abgelaufen sind."
#: src/Email/LicenseExpiredEmail.php:68
msgid "[{site_title}] Your license for {product_name} has expired"
msgstr "[{site_title}] Ihre Lizenz für {product_name} ist abgelaufen"
#: src/Email/LicenseExpiredEmail.php:171 src/Email/LicenseExpiredEmail.php:242
#, php-format
msgid "Your license for %1$s has expired on %2$s."
msgstr "Ihre Lizenz für %1$s ist am %2$s abgelaufen."
#: src/Email/LicenseExpiredEmail.php:178 src/Email/LicenseExpiredEmail.php:248
msgid ""
"Your license is no longer valid and the product will stop working until you "
"renew."
msgstr ""
"Ihre Lizenz ist nicht mehr gültig und das Produkt wird nicht mehr "
"funktionieren, bis Sie verlängern."
#: src/Email/LicenseExpiredEmail.php:181 src/Email/LicenseExpiredEmail.php:252
msgid "Expired License Details"
msgstr "Details der abgelaufenen Lizenz"
#: src/Email/LicenseExpiredEmail.php:203 src/Email/LicenseExpiredEmail.php:258
msgid "Expired on:"
msgstr "Abgelaufen am:"
#: src/Email/LicenseExpiredEmail.php:207 src/Email/LicenseExpiredEmail.php:259
msgid "Status:"
msgstr "Status:"
#: src/Email/LicenseExpiredEmail.php:278
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:257
msgid "WC Licensed Product"
msgstr "WC Licensed Product"
#: src/Plugin.php:256
#: src/Plugin.php:258
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:257
#: src/Plugin.php:259
msgid "Configure License"
msgstr "Lizenz konfigurieren"
@@ -1663,15 +1778,6 @@ msgstr ""
#~ msgid "of"
#~ msgstr "von"
#~ msgid "Expiring Soon (30 days)"
#~ msgstr "Bald ablaufend (30 Tage)"
#~ msgid "License Types"
#~ msgstr "Lizenztypen"
#~ msgid "Time-limited Licenses"
#~ msgstr "Zeitlich begrenzte Lizenzen"
#~ msgid "Lifetime Licenses"
#~ msgstr "Lebenslange Lizenzen"

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 PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: WC Licensed Product 0.3.4\n"
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
"POT-Creation-Date: 2026-01-23 11:54+0100\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-23 16:05+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"
@@ -78,13 +78,13 @@ msgstr ""
#: src/Admin/OrderLicenseController.php:144
#: src/Admin/SettingsController.php:142 src/Admin/AdminController.php:1280
#: src/Admin/AdminController.php:1431 src/Admin/AdminController.php:1480
#: src/Email/LicenseEmailController.php:230
#: src/Email/LicenseEmailController.php:269
msgid "License Key"
msgstr ""
#: src/Admin/OrderLicenseController.php:145 src/Admin/AdminController.php:1281
#: src/Admin/AdminController.php:1432 src/Admin/AdminController.php:1597
#: src/Email/LicenseEmailController.php:229
#: src/Email/LicenseEmailController.php:268
msgid "Product"
msgstr ""
@@ -100,7 +100,7 @@ msgstr ""
#: src/Admin/OrderLicenseController.php:148 src/Admin/AdminController.php:1286
#: src/Admin/AdminController.php:1437 src/Admin/AdminController.php:1600
#: src/Admin/AdminController.php:1602 src/Email/LicenseEmailController.php:231
#: src/Admin/AdminController.php:1602 src/Email/LicenseEmailController.php:270
msgid "Expires"
msgstr ""
@@ -411,6 +411,7 @@ msgstr ""
#: src/Admin/AdminController.php:1221 src/Admin/AdminController.php:1344
#: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:413
#: src/Admin/DashboardWidgetController.php:151
msgid "Active"
msgstr ""
@@ -423,6 +424,8 @@ msgstr ""
#: src/Admin/AdminController.php:158 src/Admin/AdminController.php:916
#: src/Admin/AdminController.php:1223 src/Admin/AdminController.php:1346
#: src/Admin/DashboardWidgetController.php:159
#: src/Email/LicenseExpiredEmail.php:210 src/Email/LicenseExpiredEmail.php:259
msgid "Expired"
msgstr ""
@@ -509,6 +512,7 @@ msgid "License Dashboard"
msgstr ""
#: src/Admin/AdminController.php:895
#: src/Admin/DashboardWidgetController.php:147
msgid "Total Licenses"
msgstr ""
@@ -743,6 +747,7 @@ msgstr ""
#: src/Admin/AdminController.php:1282 src/Admin/AdminController.php:1433
#: src/Email/LicenseExpirationEmail.php:104
#: src/Email/LicenseExpiredEmail.php:96
msgid "Customer"
msgstr ""
@@ -1112,6 +1117,56 @@ msgstr ""
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"
msgstr ""
#: src/Api/RestApiController.php:84
msgid "Too many requests. Please try again later."
msgstr ""
@@ -1201,6 +1256,7 @@ msgstr ""
#: src/License/LicenseManager.php:760 src/Frontend/AccountController.php:140
#: src/Email/LicenseExpirationEmail.php:107
#: src/Email/LicenseExpiredEmail.php:99
msgid "Unknown Product"
msgstr ""
@@ -1212,6 +1268,15 @@ msgstr ""
msgid "Could not connect to license server."
msgstr ""
#: src/Product/VersionManager.php:166
msgid "Attachment file not found."
msgstr ""
#: src/Product/VersionManager.php:177
#, php-format
msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
msgstr ""
#: src/Product/LicensedProductType.php:61
msgid "Licensed Product"
msgstr ""
@@ -1275,15 +1340,6 @@ msgstr ""
msgid "Version:"
msgstr ""
#: src/Product/VersionManager.php:166
msgid "Attachment file not found."
msgstr ""
#: src/Product/VersionManager.php:177
#, php-format
msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
msgstr ""
#: src/Frontend/DownloadController.php:65
#: src/Frontend/DownloadController.php:89
msgid "Invalid download link."
@@ -1344,30 +1400,32 @@ msgid "You have no licenses yet."
msgstr ""
#: src/Frontend/AccountController.php:190
#: src/Email/LicenseEmailController.php:173
#: src/Email/LicenseEmailController.php:177
#: src/Email/LicenseEmailController.php:281
#: src/Email/LicenseEmailController.php:212
#: src/Email/LicenseEmailController.php:216
#: src/Email/LicenseEmailController.php:320
#: src/Email/LicenseExpirationEmail.php:207
#: src/Email/LicenseExpirationEmail.php:270
#: src/Email/LicenseExpiredEmail.php:191 src/Email/LicenseExpiredEmail.php:256
msgid "License Key:"
msgstr ""
#: src/Frontend/AccountController.php:201
#: src/Email/LicenseExpirationEmail.php:215
#: src/Email/LicenseExpirationEmail.php:271
#: src/Email/LicenseExpiredEmail.php:199 src/Email/LicenseExpiredEmail.php:257
msgid "Domain:"
msgstr ""
#: src/Frontend/AccountController.php:213
#: src/Email/LicenseEmailController.php:284
#: src/Email/LicenseEmailController.php:323
#: src/Email/LicenseExpirationEmail.php:219
#: src/Email/LicenseExpirationEmail.php:272
msgid "Expires:"
msgstr ""
#: src/Frontend/AccountController.php:218
#: src/Email/LicenseEmailController.php:248
#: src/Email/LicenseEmailController.php:287
#: src/Email/LicenseEmailController.php:326
msgid "Never"
msgstr ""
@@ -1423,21 +1481,21 @@ msgstr ""
msgid "Failed to transfer license. Please try again."
msgstr ""
#: src/Email/LicenseEmailController.php:217
#: src/Email/LicenseEmailController.php:256
msgid "Your License Keys"
msgstr ""
#: src/Email/LicenseEmailController.php:221
#: src/Email/LicenseEmailController.php:276
#: src/Email/LicenseEmailController.php:260
#: src/Email/LicenseEmailController.php:315
msgid "Licensed Domain:"
msgstr ""
#: src/Email/LicenseEmailController.php:257
#: src/Email/LicenseEmailController.php:291
#: src/Email/LicenseEmailController.php:296
#: src/Email/LicenseEmailController.php:330
msgid "You can also view your licenses in your account under \"Licenses\"."
msgstr ""
#: src/Email/LicenseEmailController.php:272
#: src/Email/LicenseEmailController.php:311
msgid "YOUR LICENSE KEYS"
msgstr ""
@@ -1463,6 +1521,7 @@ msgstr ""
#: src/Email/LicenseExpirationEmail.php:176
#: src/Email/LicenseExpirationEmail.php:246
#: src/Email/LicenseExpiredEmail.php:167 src/Email/LicenseExpiredEmail.php:238
#, php-format
msgid "Hello %s,"
msgstr ""
@@ -1486,11 +1545,13 @@ msgstr ""
#: src/Email/LicenseExpirationEmail.php:203
#: src/Email/LicenseExpirationEmail.php:269
#: src/Email/LicenseExpiredEmail.php:187 src/Email/LicenseExpiredEmail.php:255
msgid "Product:"
msgstr ""
#: src/Email/LicenseExpirationEmail.php:235
#: src/Email/LicenseExpirationEmail.php:281
#: src/Email/LicenseExpiredEmail.php:227 src/Email/LicenseExpiredEmail.php:268
msgid "View My Licenses"
msgstr ""
@@ -1501,52 +1562,102 @@ msgid ""
msgstr ""
#: src/Email/LicenseExpirationEmail.php:301
#: src/Email/LicenseExpiredEmail.php:288
#, php-format
msgid "Available placeholders: %s"
msgstr ""
#: src/Email/LicenseExpirationEmail.php:307
#: src/Email/LicenseExpiredEmail.php:294
msgid "Enable/Disable"
msgstr ""
#: src/Email/LicenseExpirationEmail.php:309
#: src/Email/LicenseExpiredEmail.php:296
msgid "Enable this email notification"
msgstr ""
#: src/Email/LicenseExpirationEmail.php:313
#: src/Email/LicenseExpiredEmail.php:300
msgid "Subject"
msgstr ""
#: src/Email/LicenseExpirationEmail.php:321
#: src/Email/LicenseExpiredEmail.php:308
msgid "Email heading"
msgstr ""
#: src/Email/LicenseExpirationEmail.php:329
#: src/Email/LicenseExpiredEmail.php:316
msgid "Additional content"
msgstr ""
#: src/Email/LicenseExpirationEmail.php:330
#: src/Email/LicenseExpiredEmail.php:317
msgid "Text to appear below the main email content."
msgstr ""
#: src/Email/LicenseExpirationEmail.php:338
#: src/Email/LicenseExpiredEmail.php:325
msgid "Email type"
msgstr ""
#: src/Email/LicenseExpirationEmail.php:340
#: src/Email/LicenseExpiredEmail.php:327
msgid "Choose which format of email to send."
msgstr ""
#: src/Plugin.php:255
#: src/Email/LicenseExpiredEmail.php:50 src/Email/LicenseExpiredEmail.php:76
msgid "License Expired"
msgstr ""
#: src/Email/LicenseExpiredEmail.php:51
msgid ""
"License expired emails are sent to customers when their licenses have "
"expired."
msgstr ""
#: src/Email/LicenseExpiredEmail.php:68
msgid "[{site_title}] Your license for {product_name} has expired"
msgstr ""
#: src/Email/LicenseExpiredEmail.php:171 src/Email/LicenseExpiredEmail.php:242
#, php-format
msgid "Your license for %1$s has expired on %2$s."
msgstr ""
#: src/Email/LicenseExpiredEmail.php:178 src/Email/LicenseExpiredEmail.php:248
msgid ""
"Your license is no longer valid and the product will stop working until you "
"renew."
msgstr ""
#: src/Email/LicenseExpiredEmail.php:181 src/Email/LicenseExpiredEmail.php:252
msgid "Expired License Details"
msgstr ""
#: src/Email/LicenseExpiredEmail.php:203 src/Email/LicenseExpiredEmail.php:258
msgid "Expired on:"
msgstr ""
#: src/Email/LicenseExpiredEmail.php:207 src/Email/LicenseExpiredEmail.php:259
msgid "Status:"
msgstr ""
#: src/Email/LicenseExpiredEmail.php:278
msgid "To continue using this product, please renew your license."
msgstr ""
#: src/Plugin.php:257
msgid "WC Licensed Product"
msgstr ""
#: src/Plugin.php:256
#: src/Plugin.php:258
msgid ""
"Plugin license is not configured or invalid. Frontend features are disabled."
msgstr ""
#: src/Plugin.php:257
#: src/Plugin.php:259
msgid "Configure License"
msgstr ""

View File

@@ -0,0 +1 @@
36a81c00eb03adf5dfa633891664d44b7e5225bf1ee594904f8acc9adec6bb47 releases/wc-licensed-product-0.3.4.zip

Binary file not shown.

View File

@@ -0,0 +1,225 @@
<?php
/**
* Dashboard Widget Controller
*
* @package Jeremias\WcLicensedProduct\Admin
*/
declare(strict_types=1);
namespace Jeremias\WcLicensedProduct\Admin;
use Jeremias\WcLicensedProduct\License\License;
use Jeremias\WcLicensedProduct\License\LicenseManager;
/**
* Handles the WordPress admin dashboard widget for license statistics
*/
final class DashboardWidgetController
{
private LicenseManager $licenseManager;
public function __construct(LicenseManager $licenseManager)
{
$this->licenseManager = $licenseManager;
$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_license_statistics',
__('License Statistics', 'wc-licensed-product'),
[$this, 'renderWidget']
);
}
/**
* Render the dashboard widget content
*/
public function renderWidget(): void
{
$stats = $this->licenseManager->getStatistics();
$licensesUrl = admin_url('admin.php?page=wc-licensed-product-licenses');
?>
<style>
.wclp-widget-stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.wclp-stat-card {
background: #f8f9fa;
border: 1px solid #e2e4e7;
border-radius: 4px;
padding: 12px;
text-align: center;
}
.wclp-stat-card.highlight {
border-left: 3px solid #7f54b3;
}
.wclp-stat-card.warning {
border-left: 3px solid #f0b849;
}
.wclp-stat-card.danger {
border-left: 3px solid #dc3232;
}
.wclp-stat-card.success {
border-left: 3px solid #46b450;
}
.wclp-stat-number {
font-size: 28px;
font-weight: 600;
color: #1d2327;
line-height: 1.2;
}
.wclp-stat-label {
font-size: 12px;
color: #646970;
text-transform: uppercase;
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;
border-top: 1px solid #e2e4e7;
text-align: center;
}
.wclp-widget-footer a {
text-decoration: none;
}
</style>
<div class="wclp-widget-stats">
<div class="wclp-stat-card highlight">
<div class="wclp-stat-number"><?php echo esc_html(number_format_i18n($stats['total'])); ?></div>
<div class="wclp-stat-label"><?php esc_html_e('Total Licenses', 'wc-licensed-product'); ?></div>
</div>
<div class="wclp-stat-card success">
<div class="wclp-stat-number"><?php echo esc_html(number_format_i18n($stats['by_status'][License::STATUS_ACTIVE])); ?></div>
<div class="wclp-stat-label"><?php esc_html_e('Active', 'wc-licensed-product'); ?></div>
</div>
<div class="wclp-stat-card warning">
<div class="wclp-stat-number"><?php echo esc_html(number_format_i18n($stats['expiring_soon'])); ?></div>
<div class="wclp-stat-label"><?php esc_html_e('Expiring Soon', 'wc-licensed-product'); ?></div>
</div>
<div class="wclp-stat-card danger">
<div class="wclp-stat-number"><?php echo esc_html(number_format_i18n($stats['by_status'][License::STATUS_EXPIRED])); ?></div>
<div class="wclp-stat-label"><?php esc_html_e('Expired', '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('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>
<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">
<?php esc_html_e('View All Licenses', 'wc-licensed-product'); ?>
</a>
</div>
<?php
}
}

View File

@@ -55,6 +55,7 @@ final class LicenseEmailController
public function registerEmailClasses(array $email_classes): array
{
$email_classes['WCLP_License_Expiration'] = new LicenseExpirationEmail();
$email_classes['WCLP_License_Expired'] = new LicenseExpiredEmail();
return $email_classes;
}
@@ -69,10 +70,13 @@ final class LicenseEmailController
}
/**
* Send expiration warning emails
* Send expiration warning emails and auto-expire licenses
*/
public function sendExpirationWarnings(): void
{
// First, auto-expire licenses that have passed their expiration date
$this->autoExpireAndNotify();
// Check if expiration emails are enabled in settings
if (!SettingsController::isExpirationEmailsEnabled()) {
return;
@@ -107,6 +111,41 @@ final class LicenseEmailController
}
}
/**
* Auto-expire licenses and send expired notifications
*/
private function autoExpireAndNotify(): void
{
// Get licenses that should be auto-expired
$expiredActiveLicenses = $this->licenseManager->getExpiredActiveLicenses();
if (empty($expiredActiveLicenses)) {
return;
}
// Get the WooCommerce email instance for expired notifications
$mailer = WC()->mailer();
$emails = $mailer->get_emails();
/** @var LicenseExpiredEmail|null $expiredEmail */
$expiredEmail = $emails['WCLP_License_Expired'] ?? null;
foreach ($expiredActiveLicenses as $license) {
// Auto-expire the license
$wasExpired = $this->licenseManager->autoExpireLicense($license->getId());
if ($wasExpired && $expiredEmail && $expiredEmail->is_enabled()) {
// Check if we haven't already sent an expired notification
if (!$this->licenseManager->wasExpirationNotified($license->getId(), 'license_expired')) {
// Send expired notification email
if ($expiredEmail->trigger($license)) {
$this->licenseManager->markExpirationNotified($license->getId(), 'license_expired');
}
}
}
}
}
/**
* Process and send expiration warnings for a specific time frame
*

View File

@@ -0,0 +1,335 @@
<?php
/**
* License Expired Email
*
* @package Jeremias\WcLicensedProduct\Email
*/
declare(strict_types=1);
namespace Jeremias\WcLicensedProduct\Email;
use Jeremias\WcLicensedProduct\License\License;
use WC_Email;
/**
* License Expired Email class
*
* Sends email notifications to customers when their licenses have expired.
* Uses WooCommerce's transactional email system for consistent styling and customization.
*/
class LicenseExpiredEmail extends WC_Email
{
/**
* License object
*/
public ?License $license = null;
/**
* Product name
*/
public string $product_name = '';
/**
* Expiration date formatted
*/
public string $expiration_date = '';
/**
* Customer display name
*/
public string $customer_name = '';
/**
* Constructor
*/
public function __construct()
{
$this->id = 'wclp_license_expired';
$this->customer_email = true;
$this->title = __('License Expired', 'wc-licensed-product');
$this->description = __('License expired emails are sent to customers when their licenses have expired.', 'wc-licensed-product');
$this->placeholders = [
'{site_title}' => $this->get_blogname(),
'{product_name}' => '',
'{expiration_date}' => '',
];
// Call parent constructor
parent::__construct();
}
/**
* Get email subject
*/
public function get_default_subject(): string
{
return __('[{site_title}] Your license for {product_name} has expired', 'wc-licensed-product');
}
/**
* Get email heading
*/
public function get_default_heading(): string
{
return __('License Expired', 'wc-licensed-product');
}
/**
* Trigger the email
*
* @param License $license License object
*/
public function trigger(License $license): bool
{
$this->setup_locale();
$customer = get_userdata($license->getCustomerId());
if (!$customer || !$customer->user_email) {
$this->restore_locale();
return false;
}
$this->license = $license;
$this->recipient = $customer->user_email;
$this->customer_name = $customer->display_name ?: __('Customer', 'wc-licensed-product');
$product = wc_get_product($license->getProductId());
$this->product_name = $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product');
$expiresAt = $license->getExpiresAt();
$this->expiration_date = $expiresAt ? $expiresAt->format(get_option('date_format')) : '';
// Update placeholders
$this->placeholders['{product_name}'] = $this->product_name;
$this->placeholders['{expiration_date}'] = $this->expiration_date;
if (!$this->is_enabled() || !$this->get_recipient()) {
$this->restore_locale();
return false;
}
$result = $this->send(
$this->get_recipient(),
$this->get_subject(),
$this->get_content(),
$this->get_headers(),
$this->get_attachments()
);
$this->restore_locale();
return $result;
}
/**
* Get content HTML
*/
public function get_content_html(): string
{
ob_start();
// Use WooCommerce's email header
wc_get_template('emails/email-header.php', ['email_heading' => $this->get_heading()]);
$this->render_email_body_html();
// Use WooCommerce's email footer
wc_get_template('emails/email-footer.php', ['email' => $this]);
return ob_get_clean();
}
/**
* Get content plain text
*/
public function get_content_plain(): string
{
ob_start();
echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
echo esc_html(wp_strip_all_tags($this->get_heading()));
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
$this->render_email_body_plain();
return ob_get_clean();
}
/**
* Render HTML email body content
*/
private function render_email_body_html(): void
{
$account_url = wc_get_account_endpoint_url('licenses');
?>
<p><?php printf(esc_html__('Hello %s,', 'wc-licensed-product'), esc_html($this->customer_name)); ?></p>
<p style="color: #dc3232; font-weight: 600;">
<?php printf(
esc_html__('Your license for %1$s has expired on %2$s.', 'wc-licensed-product'),
'<strong>' . esc_html($this->product_name) . '</strong>',
esc_html($this->expiration_date)
); ?>
</p>
<p>
<?php esc_html_e('Your license is no longer valid and the product will stop working until you renew.', 'wc-licensed-product'); ?>
</p>
<h2><?php esc_html_e('Expired License Details', 'wc-licensed-product'); ?></h2>
<div style="margin-bottom: 40px;">
<table class="td" cellspacing="0" cellpadding="6" style="width: 100%; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif;" border="1">
<tbody>
<tr>
<th class="td" scope="row" style="text-align:<?php echo is_rtl() ? 'right' : 'left'; ?>;"><?php esc_html_e('Product:', 'wc-licensed-product'); ?></th>
<td class="td" style="text-align:<?php echo is_rtl() ? 'right' : 'left'; ?>;"><?php echo esc_html($this->product_name); ?></td>
</tr>
<tr>
<th class="td" scope="row" style="text-align:<?php echo is_rtl() ? 'right' : 'left'; ?>;"><?php esc_html_e('License Key:', 'wc-licensed-product'); ?></th>
<td class="td" style="text-align:<?php echo is_rtl() ? 'right' : 'left'; ?>;">
<code style="background: #f5f5f5; padding: 3px 8px; border-radius: 3px; font-family: monospace;">
<?php echo esc_html($this->license->getLicenseKey()); ?>
</code>
</td>
</tr>
<tr>
<th class="td" scope="row" style="text-align:<?php echo is_rtl() ? 'right' : 'left'; ?>;"><?php esc_html_e('Domain:', 'wc-licensed-product'); ?></th>
<td class="td" style="text-align:<?php echo is_rtl() ? 'right' : 'left'; ?>;"><?php echo esc_html($this->license->getDomain()); ?></td>
</tr>
<tr>
<th class="td" scope="row" style="text-align:<?php echo is_rtl() ? 'right' : 'left'; ?>;"><?php esc_html_e('Expired on:', 'wc-licensed-product'); ?></th>
<td class="td" style="text-align:<?php echo is_rtl() ? 'right' : 'left'; ?>; color: #dc3232; font-weight: 600;"><?php echo esc_html($this->expiration_date); ?></td>
</tr>
<tr>
<th class="td" scope="row" style="text-align:<?php echo is_rtl() ? 'right' : 'left'; ?>;"><?php esc_html_e('Status:', 'wc-licensed-product'); ?></th>
<td class="td" style="text-align:<?php echo is_rtl() ? 'right' : 'left'; ?>;">
<span style="background: #f8d7da; color: #721c24; padding: 3px 10px; border-radius: 12px; font-size: 12px; font-weight: 500;">
<?php esc_html_e('Expired', 'wc-licensed-product'); ?>
</span>
</td>
</tr>
</tbody>
</table>
</div>
<?php
$additional_content = $this->get_additional_content();
if ($additional_content) :
?>
<p><?php echo wp_kses_post($additional_content); ?></p>
<?php endif; ?>
<p style="margin-top: 25px;">
<a href="<?php echo esc_url($account_url); ?>" class="button" style="display: inline-block; background-color: #7f54b3; color: #ffffff; padding: 12px 24px; text-decoration: none; border-radius: 4px; font-weight: 600;">
<?php esc_html_e('View My Licenses', 'wc-licensed-product'); ?>
</a>
</p>
<?php
}
/**
* Render plain text email body content
*/
private function render_email_body_plain(): void
{
printf(esc_html__('Hello %s,', 'wc-licensed-product'), esc_html($this->customer_name));
echo "\n\n";
printf(
esc_html__('Your license for %1$s has expired on %2$s.', 'wc-licensed-product'),
esc_html($this->product_name),
esc_html($this->expiration_date)
);
echo "\n\n";
echo esc_html__('Your license is no longer valid and the product will stop working until you renew.', 'wc-licensed-product');
echo "\n\n";
echo "----------\n";
echo esc_html__('Expired License Details', 'wc-licensed-product') . "\n";
echo "----------\n\n";
echo esc_html__('Product:', 'wc-licensed-product') . ' ' . esc_html($this->product_name) . "\n";
echo esc_html__('License Key:', 'wc-licensed-product') . ' ' . esc_html($this->license->getLicenseKey()) . "\n";
echo esc_html__('Domain:', 'wc-licensed-product') . ' ' . esc_html($this->license->getDomain()) . "\n";
echo esc_html__('Expired on:', 'wc-licensed-product') . ' ' . esc_html($this->expiration_date) . "\n";
echo esc_html__('Status:', 'wc-licensed-product') . ' ' . esc_html__('Expired', 'wc-licensed-product') . "\n\n";
$additional_content = $this->get_additional_content();
if ($additional_content) {
echo "----------\n\n";
echo esc_html(wp_strip_all_tags(wptexturize($additional_content)));
echo "\n\n";
}
echo esc_html__('View My Licenses', 'wc-licensed-product') . ': ' . esc_url(wc_get_account_endpoint_url('licenses')) . "\n\n";
echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
}
/**
* Default content to show below main email content
*/
public function get_default_additional_content(): string
{
return __('To continue using this product, please renew your license.', 'wc-licensed-product');
}
/**
* Initialize settings form fields
*/
public function init_form_fields(): void
{
$placeholder_text = sprintf(
/* translators: %s: list of placeholders */
__('Available placeholders: %s', 'wc-licensed-product'),
'<code>{site_title}, {product_name}, {expiration_date}</code>'
);
$this->form_fields = [
'enabled' => [
'title' => __('Enable/Disable', 'wc-licensed-product'),
'type' => 'checkbox',
'label' => __('Enable this email notification', 'wc-licensed-product'),
'default' => 'yes',
],
'subject' => [
'title' => __('Subject', 'wc-licensed-product'),
'type' => 'text',
'desc_tip' => true,
'description' => $placeholder_text,
'placeholder' => $this->get_default_subject(),
'default' => '',
],
'heading' => [
'title' => __('Email heading', 'wc-licensed-product'),
'type' => 'text',
'desc_tip' => true,
'description' => $placeholder_text,
'placeholder' => $this->get_default_heading(),
'default' => '',
],
'additional_content' => [
'title' => __('Additional content', 'wc-licensed-product'),
'description' => __('Text to appear below the main email content.', 'wc-licensed-product') . ' ' . $placeholder_text,
'css' => 'width:400px; height: 75px;',
'placeholder' => $this->get_default_additional_content(),
'type' => 'textarea',
'default' => '',
'desc_tip' => true,
],
'email_type' => [
'title' => __('Email type', 'wc-licensed-product'),
'type' => 'select',
'description' => __('Choose which format of email to send.', 'wc-licensed-product'),
'default' => 'html',
'class' => 'email_type wc-enhanced-select',
'options' => $this->get_email_type_options(),
'desc_tip' => true,
],
];
}
}

View File

@@ -862,6 +862,56 @@ class LicenseManager
return (bool) get_user_meta($license->getCustomerId(), $metaKey, true);
}
/**
* Get licenses that have passed their expiration date but are still marked as active
*
* @return array Array of License objects that need to be auto-expired
*/
public function getExpiredActiveLicenses(): array
{
global $wpdb;
$tableName = Installer::getLicensesTable();
$now = new \DateTimeImmutable();
$sql = "SELECT * FROM {$tableName}
WHERE expires_at IS NOT NULL
AND expires_at < %s
AND status = %s";
$rows = $wpdb->get_results(
$wpdb->prepare($sql, $now->format('Y-m-d H:i:s'), License::STATUS_ACTIVE),
ARRAY_A
);
return array_map(fn(array $row) => License::fromArray($row), $rows ?: []);
}
/**
* Auto-expire a license and return true if status was changed
*
* @param int $licenseId License ID
* @return bool True if license was expired, false if already expired or error
*/
public function autoExpireLicense(int $licenseId): bool
{
$license = $this->getLicenseById($licenseId);
if (!$license) {
return false;
}
// Only expire if currently active and past expiration date
if ($license->getStatus() !== License::STATUS_ACTIVE) {
return false;
}
if (!$license->isExpired()) {
return false;
}
return $this->updateLicenseStatus($licenseId, License::STATUS_EXPIRED);
}
/**
* Import a license from CSV data
*

View File

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

View File

@@ -57,19 +57,22 @@
<h4>{{ __('Available Downloads') }}</h4>
<ul class="download-list">
{% for download in item.downloads %}
<li>
<a href="{{ esc_url(download.download_url) }}" class="download-link">
<span class="dashicons dashicons-download"></span>
{{ esc_html(download.filename ?: 'Version ' ~ download.version) }}
</a>
<span class="download-version">v{{ esc_html(download.version) }}</span>
<span class="download-date">{{ esc_html(download.released_at) }}</span>
{% if download.file_hash %}
<span class="download-hash" title="{{ esc_attr(download.file_hash) }}">
<span class="dashicons dashicons-shield"></span>
<code>{{ download.file_hash[:12] }}...</code>
</span>
{% endif %}
<li class="download-item">
<div class="download-row-file">
<a href="{{ esc_url(download.download_url) }}" class="download-link">
<span class="dashicons dashicons-download"></span>
{{ esc_html(download.filename ?: 'Version ' ~ download.version) }}
</a>
</div>
<div class="download-row-meta">
<span class="download-date">{{ esc_html(download.released_at) }}</span>
{% if download.file_hash %}
<span class="download-hash" title="{{ esc_attr(download.file_hash) }}">
<span class="dashicons dashicons-shield"></span>
<code>{{ download.file_hash[:12] }}...</code>
</span>
{% endif %}
</div>
</li>
{% endfor %}
</ul>

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.4
* Version: 0.3.5
* 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.4');
define('WC_LICENSED_PRODUCT_VERSION', '0.3.5');
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__));