diff --git a/CLAUDE.md b/CLAUDE.md index 247f876..eb13b68 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,10 +36,6 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w _No known bugs at this time._ -### Version 0.0.8 (Next) - -_No planned features yet. Add items as needed._ - ## Technical Stack - **Language:** PHP 8.3.x @@ -429,3 +425,38 @@ Full API documentation available in `openapi.json` (OpenAPI 3.1 specification). - Expiration warnings use WordPress cron with daily schedule - CSV import supports both exported format and simplified format - User meta tracks expiration notifications to prevent duplicates + +### 2026-01-21 - Version 0.0.8 Features + +**Implemented:** + +- Removed "Current Version" field from product license settings +- Current version now automatically derived from latest product version in database +- Refactored email system to use WooCommerce email notification classes +- License Expiration Warning email is now configurable via WooCommerce > Settings > Emails +- Email templates can be overridden in themes (woocommerce/emails/license-expiration.php) +- Configurable warning days for expiration notifications (first and second warning) + +**New classes:** + +- `LicenseExpirationEmail` - WooCommerce WC_Email subclass for license expiration warnings + +**New files:** + +- `templates/emails/license-expiration.php` - HTML email template +- `templates/emails/plain/license-expiration.php` - Plain text email template + +**Modified classes:** + +- `LicensedProduct` - `get_current_version()` and `get_major_version()` now query VersionManager +- `LicensedProductType` - Removed Current Version field from product data panel +- `VersionAdminController` - Removed automatic update of `_licensed_current_version` meta +- `LicenseEmailController` - Registers WooCommerce email class, uses WC email system for sending +- `SettingsController` - Updated email section to link to WooCommerce email settings + +**Technical notes:** + +- WooCommerce email system allows admins to customize subject, heading, content, and enable/disable +- Email templates follow WooCommerce conventions and can be overridden in themes +- Expiration warning schedule (days before) remains in plugin settings +- Email enable/disable is controlled through WooCommerce email settings diff --git a/languages/wc-licensed-product-de_CH.mo b/languages/wc-licensed-product-de_CH.mo index 803a5e3..1a2b486 100644 Binary files a/languages/wc-licensed-product-de_CH.mo and b/languages/wc-licensed-product-de_CH.mo differ diff --git a/languages/wc-licensed-product-de_CH.po b/languages/wc-licensed-product-de_CH.po index d73d6be..de41c00 100644 --- a/languages/wc-licensed-product-de_CH.po +++ b/languages/wc-licensed-product-de_CH.po @@ -3,7 +3,7 @@ # This file is distributed under the GPL-2.0-or-later. msgid "" msgstr "" -"Project-Id-Version: WC Licensed Product 0.0.7\n" +"Project-Id-Version: WC Licensed Product 0.0.8\n" "Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/issues\n" "POT-Creation-Date: 2026-01-21T00:00:00+00:00\n" "PO-Revision-Date: 2026-01-21T00:00:00+00:00\n" @@ -63,12 +63,6 @@ msgstr "Falls aktiviert, werden Lizenzen an die Hauptversion zum Kaufzeitpunkt g msgid "If enabled, licenses are bound to the major version at purchase time by default." msgstr "Falls aktiviert, werden Lizenzen standardmässig an die Hauptversion zum Kaufzeitpunkt gebunden." -msgid "Current Version" -msgstr "Aktuelle Version" - -msgid "Current software version (e.g., 1.0.0)" -msgstr "Aktuelle Software-Version (z.B. 1.0.0)" - #. Global settings msgid "Default License Settings" msgstr "Standard Lizenz-Einstellungen" @@ -818,3 +812,56 @@ msgstr "Ja" msgid "No" msgstr "Nein" + +#. Email settings +msgid "Expiration Warning Schedule" +msgstr "Ablaufwarnung Zeitplan" + +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." + +msgid "WooCommerce > Settings > Emails > License Expiration Warning" +msgstr "WooCommerce > Einstellungen > E-Mails > Lizenzablauf-Warnung" + +msgid "First Warning (Days Before)" +msgstr "Erste Warnung (Tage vorher)" + +msgid "Days before expiration to send the first warning email." +msgstr "Tage vor Ablauf, um die erste Warn-E-Mail zu senden." + +msgid "Second Warning (Days Before)" +msgstr "Zweite Warnung (Tage vorher)" + +msgid "Days before expiration to send the second warning email. Set to 0 to disable." +msgstr "Tage vor Ablauf, um die zweite Warn-E-Mail zu senden. Setzen Sie auf 0, um sie zu deaktivieren." + +#. WooCommerce Email Class +msgid "License Expiration Warning" +msgstr "Lizenzablauf-Warnung" + +msgid "License expiration warning emails are sent to customers when their licenses are about to expire." +msgstr "Lizenzablauf-Warnungs-E-Mails werden an Kunden gesendet, wenn ihre Lizenzen bald ablaufen." + +msgid "[{site_title}] Your license for {product_name} expires in {days_remaining} days" +msgstr "[{site_title}] Ihre Lizenz für {product_name} läuft in {days_remaining} Tagen ab" + +msgid "Available placeholders: %s" +msgstr "Verfügbare Platzhalter: %s" + +msgid "Enable this email notification" +msgstr "Diese E-Mail-Benachrichtigung aktivieren" + +msgid "Email heading" +msgstr "E-Mail-Überschrift" + +msgid "Additional content" +msgstr "Zusätzlicher Inhalt" + +msgid "Text to appear below the main email content." +msgstr "Text, der unter dem Haupt-E-Mail-Inhalt erscheinen soll." + +msgid "Email type" +msgstr "E-Mail-Typ" + +msgid "Choose which format of email to send." +msgstr "Wählen Sie, welches E-Mail-Format gesendet werden soll." diff --git a/languages/wc-licensed-product.pot b/languages/wc-licensed-product.pot index 81b58d0..30fe460 100644 --- a/languages/wc-licensed-product.pot +++ b/languages/wc-licensed-product.pot @@ -2,7 +2,7 @@ # This file is distributed under the GPL-2.0-or-later. msgid "" msgstr "" -"Project-Id-Version: WC Licensed Product 0.0.7\n" +"Project-Id-Version: WC Licensed Product 0.0.8\n" "Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/issues\n" "POT-Creation-Date: 2026-01-21T00:00:00+00:00\n" "MIME-Version: 1.0\n" @@ -60,12 +60,6 @@ msgstr "" msgid "If enabled, licenses are bound to the major version at purchase time by default." msgstr "" -msgid "Current Version" -msgstr "" - -msgid "Current software version (e.g., 1.0.0)" -msgstr "" - #. Global settings msgid "Default License Settings" msgstr "" @@ -815,3 +809,59 @@ msgstr "" msgid "No" msgstr "" + +#. Email settings +msgid "Expiration Warning Schedule" +msgstr "" + +msgid "Configure when expiration warning emails are sent. To customize the email template, enable/disable, or change the subject, go to %s." +msgstr "" + +msgid "WooCommerce > Settings > Emails > License Expiration Warning" +msgstr "" + +msgid "First Warning (Days Before)" +msgstr "" + +msgid "Days before expiration to send the first warning email." +msgstr "" + +msgid "Second Warning (Days Before)" +msgstr "" + +msgid "Days before expiration to send the second warning email. Set to 0 to disable." +msgstr "" + +#. WooCommerce Email Class +msgid "License Expiration Warning" +msgstr "" + +msgid "License expiration warning emails are sent to customers when their licenses are about to expire." +msgstr "" + +msgid "[{site_title}] Your license for {product_name} expires in {days_remaining} days" +msgstr "" + +msgid "Available placeholders: %s" +msgstr "" + +msgid "Enable this email notification" +msgstr "" + +msgid "Email heading" +msgstr "" + +msgid "Additional content" +msgstr "" + +msgid "Text to appear below the main email content." +msgstr "" + +msgid "Email type" +msgstr "" + +msgid "Choose which format of email to send." +msgstr "" + +msgid "Customer" +msgstr "" diff --git a/src/Admin/SettingsController.php b/src/Admin/SettingsController.php index d9e4f29..258017c 100644 --- a/src/Admin/SettingsController.php +++ b/src/Admin/SettingsController.php @@ -92,6 +92,44 @@ final class SettingsController 'type' => 'sectionend', 'id' => 'wc_licensed_product_section_defaults_end', ], + // Email settings section + 'email_section_title' => [ + 'name' => __('Expiration Warning Schedule', 'wc-licensed-product'), + 'type' => 'title', + 'desc' => sprintf( + /* translators: %s: URL to WooCommerce email settings */ + __('Configure when expiration warning emails are sent. To customize the email template, enable/disable, or change the subject, go to %s.', 'wc-licensed-product'), + '' . + __('WooCommerce > Settings > Emails > License Expiration Warning', 'wc-licensed-product') . '' + ), + 'id' => 'wc_licensed_product_section_email', + ], + 'expiration_warning_days_first' => [ + 'name' => __('First Warning (Days Before)', 'wc-licensed-product'), + 'type' => 'number', + 'desc' => __('Days before expiration to send the first warning email.', 'wc-licensed-product'), + 'id' => 'wc_licensed_product_expiration_warning_days_first', + 'default' => '7', + 'custom_attributes' => [ + 'min' => '1', + 'step' => '1', + ], + ], + 'expiration_warning_days_second' => [ + 'name' => __('Second Warning (Days Before)', 'wc-licensed-product'), + 'type' => 'number', + 'desc' => __('Days before expiration to send the second warning email. Set to 0 to disable.', 'wc-licensed-product'), + 'id' => 'wc_licensed_product_expiration_warning_days_second', + 'default' => '1', + 'custom_attributes' => [ + 'min' => '0', + 'step' => '1', + ], + ], + 'email_section_end' => [ + 'type' => 'sectionend', + 'id' => 'wc_licensed_product_section_email_end', + ], ]; } @@ -139,4 +177,37 @@ final class SettingsController { return get_option('wc_licensed_product_default_bind_to_version', 'no') === 'yes'; } + + /** + * Check if expiration warning emails are enabled + * This checks both the WooCommerce email setting and the old setting for backwards compatibility + */ + public static function isExpirationEmailsEnabled(): bool + { + // Check WooCommerce email enabled status + $emailEnabled = get_option('woocommerce_wclp_license_expiration_settings'); + if (is_array($emailEnabled) && isset($emailEnabled['enabled'])) { + return $emailEnabled['enabled'] === 'yes'; + } + // Default to enabled if not yet configured + return true; + } + + /** + * Get first warning days before expiration + */ + public static function getFirstWarningDays(): int + { + $value = get_option('wc_licensed_product_expiration_warning_days_first', 7); + return max(1, (int) $value); + } + + /** + * Get second warning days before expiration + */ + public static function getSecondWarningDays(): int + { + $value = get_option('wc_licensed_product_expiration_warning_days_second', 1); + return max(0, (int) $value); + } } diff --git a/src/Admin/VersionAdminController.php b/src/Admin/VersionAdminController.php index 2c3de78..c38ca5a 100644 --- a/src/Admin/VersionAdminController.php +++ b/src/Admin/VersionAdminController.php @@ -263,9 +263,6 @@ final class VersionAdminController wp_send_json_error(['message' => __('Failed to create version.', 'wc-licensed-product')]); } - // Also update the product's current version meta - update_post_meta($productId, '_licensed_current_version', $version); - wp_send_json_success([ 'message' => __('Version added successfully.', 'wc-licensed-product'), 'version' => $newVersion->toArray(), diff --git a/src/Email/LicenseEmailController.php b/src/Email/LicenseEmailController.php index 915a2f7..930d39c 100644 --- a/src/Email/LicenseEmailController.php +++ b/src/Email/LicenseEmailController.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace Jeremias\WcLicensedProduct\Email; use Jeremias\WcLicensedProduct\License\LicenseManager; +use Jeremias\WcLicensedProduct\Admin\SettingsController; /** * Handles email notifications for licenses @@ -29,6 +30,9 @@ final class LicenseEmailController */ private function registerHooks(): void { + // Register custom WooCommerce email classes + add_filter('woocommerce_email_classes', [$this, 'registerEmailClasses']); + // Add license info to order completed email add_action('woocommerce_email_after_order_table', [$this, 'addLicenseInfoToEmail'], 20, 4); @@ -40,6 +44,45 @@ final class LicenseEmailController // Cron action for checking expiring licenses add_action('wclp_check_expiring_licenses', [$this, 'sendExpirationWarnings']); + + // Add email templates location for theme overrides + add_filter('woocommerce_locate_template', [$this, 'locateTemplate'], 10, 3); + } + + /** + * Register custom email classes with WooCommerce + * + * @param array $email_classes Existing email classes + * @return array Modified email classes + */ + public function registerEmailClasses(array $email_classes): array + { + $email_classes['WCLP_License_Expiration'] = new LicenseExpirationEmail(); + return $email_classes; + } + + /** + * Locate templates in plugin directory + * + * @param string $template Template path + * @param string $template_name Template name + * @param string $template_path Template path prefix + * @return string Modified template path + */ + public function locateTemplate(string $template, string $template_name, string $template_path): string + { + // Only handle our email templates + if (strpos($template_name, 'emails/license-') !== 0) { + return $template; + } + + $plugin_template = WC_LICENSED_PRODUCT_PLUGIN_DIR . 'templates/' . $template_name; + + if (file_exists($plugin_template)) { + return $plugin_template; + } + + return $template; } /** @@ -57,20 +100,48 @@ final class LicenseEmailController */ public function sendExpirationWarnings(): void { - // Check for licenses expiring in 7 days - $this->processExpirationWarnings(7, 'expiring_7_days'); + // Check if expiration emails are enabled in settings + if (!SettingsController::isExpirationEmailsEnabled()) { + return; + } - // Check for licenses expiring in 1 day - $this->processExpirationWarnings(1, 'expiring_1_day'); + // Get the WooCommerce email instance + $mailer = WC()->mailer(); + $emails = $mailer->get_emails(); + + if (!isset($emails['WCLP_License_Expiration'])) { + return; + } + + /** @var LicenseExpirationEmail $expirationEmail */ + $expirationEmail = $emails['WCLP_License_Expiration']; + + // Check if the email is enabled + if (!$expirationEmail->is_enabled()) { + return; + } + + // Get configurable warning days + $firstWarningDays = SettingsController::getFirstWarningDays(); + $secondWarningDays = SettingsController::getSecondWarningDays(); + + // Check for licenses expiring at first warning threshold + $this->processExpirationWarnings($expirationEmail, $firstWarningDays, 'expiring_first_warning'); + + // Check for licenses expiring at second warning threshold (if enabled) + if ($secondWarningDays > 0 && $secondWarningDays < $firstWarningDays) { + $this->processExpirationWarnings($expirationEmail, $secondWarningDays, 'expiring_second_warning'); + } } /** * Process and send expiration warnings for a specific time frame * + * @param LicenseExpirationEmail $email Email instance * @param int $days Days until expiration * @param string $notificationType Notification type identifier */ - private function processExpirationWarnings(int $days, string $notificationType): void + private function processExpirationWarnings(LicenseExpirationEmail $email, int $days, string $notificationType): void { $licenses = $this->licenseManager->getLicensesExpiringSoon($days); @@ -80,166 +151,14 @@ final class LicenseEmailController continue; } - // Send the warning email - if ($this->sendExpirationWarningEmail($license, $days)) { + // Send the warning email using WooCommerce email system + if ($email->trigger($license, $days)) { // Mark as notified $this->licenseManager->markExpirationNotified($license->getId(), $notificationType); } } } - /** - * Send expiration warning email to customer - * - * @param \Jeremias\WcLicensedProduct\License\License $license License object - * @param int $daysRemaining Days until expiration - * @return bool Whether email was sent successfully - */ - private function sendExpirationWarningEmail($license, int $daysRemaining): bool - { - $customer = get_userdata($license->getCustomerId()); - if (!$customer || !$customer->user_email) { - return false; - } - - $product = wc_get_product($license->getProductId()); - $productName = $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product'); - - $siteName = get_bloginfo('name'); - $expiresAt = $license->getExpiresAt(); - $expirationDate = $expiresAt ? $expiresAt->format(get_option('date_format')) : ''; - - // Email subject - if ($daysRemaining === 1) { - $subject = sprintf( - /* translators: 1: Product name, 2: Site name */ - __('[%2$s] Your license for %1$s expires tomorrow', 'wc-licensed-product'), - $productName, - $siteName - ); - } else { - $subject = sprintf( - /* translators: 1: Product name, 2: Number of days, 3: Site name */ - __('[%3$s] Your license for %1$s expires in %2$d days', 'wc-licensed-product'), - $productName, - $daysRemaining, - $siteName - ); - } - - // Email content - $message = $this->buildExpirationWarningHtml($license, $customer, $productName, $daysRemaining, $expirationDate); - - // Send email - $headers = [ - 'Content-Type: text/html; charset=UTF-8', - 'From: ' . $siteName . ' <' . get_option('admin_email') . '>', - ]; - - return wp_mail($customer->user_email, $subject, $message, $headers); - } - - /** - * Build HTML content for expiration warning email - * - * @param \Jeremias\WcLicensedProduct\License\License $license License object - * @param \WP_User $customer Customer user object - * @param string $productName Product name - * @param int $daysRemaining Days until expiration - * @param string $expirationDate Formatted expiration date - * @return string HTML email content - */ - private function buildExpirationWarningHtml($license, $customer, string $productName, int $daysRemaining, string $expirationDate): string - { - $siteName = get_bloginfo('name'); - $siteUrl = home_url(); - $accountUrl = wc_get_account_endpoint_url('licenses'); - - ob_start(); - ?> - - -
- - - - -display_name)); ?>
- - -- -
- -- -
- - -| - | - |
| - |
-
- getLicenseKey()); ?>
-
- |
-
| - | getDomain()); ?> | -
| - | - |
- - - -
- -- ' . esc_html($siteName) . '' - ); ?> -
-
getLicenseKey()); ?>
@@ -324,7 +243,7 @@ final class LicenseEmailController
-
+
@@ -340,7 +259,7 @@ final class LicenseEmailController
-
+
diff --git a/src/Email/LicenseExpirationEmail.php b/src/Email/LicenseExpirationEmail.php
new file mode 100644
index 0000000..d695c4b
--- /dev/null
+++ b/src/Email/LicenseExpirationEmail.php
@@ -0,0 +1,240 @@
+id = 'wclp_license_expiration';
+ $this->customer_email = true;
+ $this->title = __('License Expiration Warning', 'wc-licensed-product');
+ $this->description = __('License expiration warning emails are sent to customers when their licenses are about to expire.', 'wc-licensed-product');
+
+ $this->template_html = 'emails/license-expiration.php';
+ $this->template_plain = 'emails/plain/license-expiration.php';
+ $this->template_base = WC_LICENSED_PRODUCT_PLUGIN_DIR . 'templates/';
+
+ $this->placeholders = [
+ '{site_title}' => $this->get_blogname(),
+ '{product_name}' => '',
+ '{days_remaining}' => '',
+ '{expiration_date}' => '',
+ ];
+
+ // Call parent constructor
+ parent::__construct();
+ }
+
+ /**
+ * Get email subject
+ */
+ public function get_default_subject(): string
+ {
+ return __('[{site_title}] Your license for {product_name} expires in {days_remaining} days', 'wc-licensed-product');
+ }
+
+ /**
+ * Get email heading
+ */
+ public function get_default_heading(): string
+ {
+ return __('License Expiration Notice', 'wc-licensed-product');
+ }
+
+ /**
+ * Trigger the email
+ *
+ * @param License $license License object
+ * @param int $days_remaining Days until expiration
+ */
+ public function trigger(License $license, int $days_remaining): bool
+ {
+ $this->setup_locale();
+
+ $customer = get_userdata($license->getCustomerId());
+ if (!$customer || !$customer->user_email) {
+ $this->restore_locale();
+ return false;
+ }
+
+ $this->license = $license;
+ $this->days_remaining = $days_remaining;
+ $this->recipient = $customer->user_email;
+
+ $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['{days_remaining}'] = (string) $days_remaining;
+ $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
+ {
+ return wc_get_template_html(
+ $this->template_html,
+ [
+ 'license' => $this->license,
+ 'days_remaining' => $this->days_remaining,
+ 'product_name' => $this->product_name,
+ 'expiration_date' => $this->expiration_date,
+ 'email_heading' => $this->get_heading(),
+ 'additional_content' => $this->get_additional_content(),
+ 'sent_to_admin' => false,
+ 'plain_text' => false,
+ 'email' => $this,
+ ],
+ '',
+ $this->template_base
+ );
+ }
+
+ /**
+ * Get content plain text
+ */
+ public function get_content_plain(): string
+ {
+ return wc_get_template_html(
+ $this->template_plain,
+ [
+ 'license' => $this->license,
+ 'days_remaining' => $this->days_remaining,
+ 'product_name' => $this->product_name,
+ 'expiration_date' => $this->expiration_date,
+ 'email_heading' => $this->get_heading(),
+ 'additional_content' => $this->get_additional_content(),
+ 'sent_to_admin' => false,
+ 'plain_text' => true,
+ 'email' => $this,
+ ],
+ '',
+ $this->template_base
+ );
+ }
+
+ /**
+ * 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 before the expiration date.', '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'),
+ '{site_title}, {product_name}, {days_remaining}, {expiration_date}'
+ );
+
+ $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,
+ ],
+ ];
+ }
+}
diff --git a/src/Product/LicensedProduct.php b/src/Product/LicensedProduct.php
index 17ae0f4..d628f80 100644
--- a/src/Product/LicensedProduct.php
+++ b/src/Product/LicensedProduct.php
@@ -11,6 +11,7 @@ namespace Jeremias\WcLicensedProduct\Product;
use Jeremias\WcLicensedProduct\Admin\SettingsController;
use WC_Product;
+use Jeremias\WcLicensedProduct\Product\VersionManager;
/**
* Licensed Product type extending WooCommerce Product
@@ -121,11 +122,14 @@ class LicensedProduct extends WC_Product
}
/**
- * Get current software version
+ * Get current software version (derived from latest product version)
*/
public function get_current_version(): string
{
- return $this->get_meta('_licensed_current_version', true) ?: '';
+ $versionManager = new VersionManager();
+ $latestVersion = $versionManager->getLatestVersion($this->get_id());
+
+ return $latestVersion ? $latestVersion->getVersion() : '';
}
/**
@@ -133,12 +137,13 @@ class LicensedProduct extends WC_Product
*/
public function get_major_version(): int
{
- $version = $this->get_current_version();
- if (empty($version)) {
- return 1;
+ $versionManager = new VersionManager();
+ $latestVersion = $versionManager->getLatestVersion($this->get_id());
+
+ if ($latestVersion) {
+ return $latestVersion->getMajorVersion();
}
- $parts = explode('.', $version);
- return (int) ($parts[0] ?? 1);
+ return 1;
}
}
diff --git a/src/Product/LicensedProductType.php b/src/Product/LicensedProductType.php
index 78b3bef..3b30e28 100644
--- a/src/Product/LicensedProductType.php
+++ b/src/Product/LicensedProductType.php
@@ -164,14 +164,6 @@ final class LicensedProductType
'value' => $currentBindToVersion ?: ($defaultBindToVersion ? 'yes' : 'no'),
'cbvalue' => 'yes',
]);
-
- woocommerce_wp_text_input([
- 'id' => '_licensed_current_version',
- 'label' => __('Current Version', 'wc-licensed-product'),
- 'description' => __('Current software version (e.g., 1.0.0)', 'wc-licensed-product'),
- 'desc_tip' => true,
- 'placeholder' => '1.0.0',
- ]);
?>
@@ -223,12 +215,6 @@ final class LicensedProductType
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$bindToVersion = isset($_POST['_licensed_bind_to_version']) ? 'yes' : 'no';
update_post_meta($postId, '_licensed_bind_to_version', $bindToVersion);
-
- // phpcs:ignore WordPress.Security.NonceVerification.Missing
- $currentVersion = isset($_POST['_licensed_current_version'])
- ? sanitize_text_field($_POST['_licensed_current_version'])
- : '';
- update_post_meta($postId, '_licensed_current_version', $currentVersion);
}
/**
diff --git a/templates/emails/license-expiration.php b/templates/emails/license-expiration.php
new file mode 100644
index 0000000..461ca7b
--- /dev/null
+++ b/templates/emails/license-expiration.php
@@ -0,0 +1,97 @@
+getCustomerId());
+$customer_name = $customer ? $customer->display_name : __('Customer', 'wc-licensed-product');
+$account_url = wc_get_account_endpoint_url('licenses');
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getLicenseKey()); ?>
+
+
+
+
+
+ getDomain()); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+getCustomerId());
+$customer_name = $customer ? $customer->display_name : __('Customer', 'wc-licensed-product');
+
+echo sprintf(esc_html__('Hello %s,', 'wc-licensed-product'), esc_html($customer_name)) . "\n\n";
+
+if ($days_remaining === 1) {
+ echo sprintf(
+ esc_html__('Your license for %s will expire tomorrow (%s).', 'wc-licensed-product'),
+ esc_html($product_name),
+ esc_html($expiration_date)
+ ) . "\n\n";
+} else {
+ echo sprintf(
+ esc_html__('Your license for %1$s will expire in %2$d days (%3$s).', 'wc-licensed-product'),
+ esc_html($product_name),
+ $days_remaining,
+ esc_html($expiration_date)
+ ) . "\n\n";
+}
+
+echo "----------\n";
+echo esc_html__('License Details', 'wc-licensed-product') . "\n";
+echo "----------\n\n";
+
+echo esc_html__('Product:', 'wc-licensed-product') . ' ' . esc_html($product_name) . "\n";
+echo esc_html__('License Key:', 'wc-licensed-product') . ' ' . esc_html($license->getLicenseKey()) . "\n";
+echo esc_html__('Domain:', 'wc-licensed-product') . ' ' . esc_html($license->getDomain()) . "\n";
+echo esc_html__('Expires:', 'wc-licensed-product') . ' ' . esc_html($expiration_date) . "\n\n";
+
+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";
diff --git a/wc-licensed-product.php b/wc-licensed-product.php
index d63c429..e8cb07d 100644
--- a/wc-licensed-product.php
+++ b/wc-licensed-product.php
@@ -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.0.7
+ * Version: 0.0.8
* 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.0.7');
+define('WC_LICENSED_PRODUCT_VERSION', '0.0.8');
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__));