Refactor email to use WooCommerce transactional email system

- Remove legacy email template files
- Use WooCommerce's email-header.php and email-footer.php templates
- Render email content inline for consistent WooCommerce styling
- Remove template location filter from LicenseEmailController

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 21:16:04 +01:00
parent c6726703e4
commit 6378063180
5 changed files with 156 additions and 234 deletions

View File

@@ -36,6 +36,12 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
_No known bugs at this time._ _No known bugs at this time._
### Version 0.0.9
**Note for AI Assistants:** Cleanup the versions in this section after implementation, but keep this notice!
## Technical Stack ## Technical Stack
- **Language:** PHP 8.3.x - **Language:** PHP 8.3.x
@@ -432,20 +438,15 @@ Full API documentation available in `openapi.json` (OpenAPI 3.1 specification).
- Removed "Current Version" field from product license settings - Removed "Current Version" field from product license settings
- Current version now automatically derived from latest product version in database - Current version now automatically derived from latest product version in database
- Refactored email system to use WooCommerce email notification classes - Refactored email system to use WooCommerce transactional email system
- License Expiration Warning email is now configurable via WooCommerce > Settings > Emails - License Expiration Warning email configurable via WooCommerce > Settings > Emails
- Email templates can be overridden in themes (woocommerce/emails/license-expiration.php) - Uses WooCommerce's email header/footer templates for consistent styling
- Configurable warning days for expiration notifications (first and second warning) - Configurable warning days for expiration notifications (first and second warning)
**New classes:** **New classes:**
- `LicenseExpirationEmail` - WooCommerce WC_Email subclass for license expiration warnings - `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:** **Modified classes:**
- `LicensedProduct` - `get_current_version()` and `get_major_version()` now query VersionManager - `LicensedProduct` - `get_current_version()` and `get_major_version()` now query VersionManager
@@ -456,7 +457,8 @@ Full API documentation available in `openapi.json` (OpenAPI 3.1 specification).
**Technical notes:** **Technical notes:**
- WooCommerce email system allows admins to customize subject, heading, content, and enable/disable - Email uses WooCommerce's `emails/email-header.php` and `emails/email-footer.php` templates
- Email templates follow WooCommerce conventions and can be overridden in themes - Email content is rendered inline, inheriting WooCommerce's styling and customizations
- Admins can customize subject, heading, additional content, and enable/disable via WC settings
- Expiration warning schedule (days before) remains in plugin settings - Expiration warning schedule (days before) remains in plugin settings
- Email enable/disable is controlled through WooCommerce email settings - Email enable/disable is controlled through WooCommerce email settings

View File

@@ -44,9 +44,6 @@ final class LicenseEmailController
// Cron action for checking expiring licenses // Cron action for checking expiring licenses
add_action('wclp_check_expiring_licenses', [$this, 'sendExpirationWarnings']); add_action('wclp_check_expiring_licenses', [$this, 'sendExpirationWarnings']);
// Add email templates location for theme overrides
add_filter('woocommerce_locate_template', [$this, 'locateTemplate'], 10, 3);
} }
/** /**
@@ -61,30 +58,6 @@ final class LicenseEmailController
return $email_classes; 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;
}
/** /**
* Schedule the expiration check cron job * Schedule the expiration check cron job
*/ */

View File

@@ -16,6 +16,7 @@ use WC_Email;
* License Expiration Warning Email class * License Expiration Warning Email class
* *
* Sends email notifications to customers when their licenses are about to expire. * Sends email notifications to customers when their licenses are about to expire.
* Uses WooCommerce's transactional email system for consistent styling and customization.
*/ */
class LicenseExpirationEmail extends WC_Email class LicenseExpirationEmail extends WC_Email
{ {
@@ -39,6 +40,11 @@ class LicenseExpirationEmail extends WC_Email
*/ */
public string $expiration_date = ''; public string $expiration_date = '';
/**
* Customer display name
*/
public string $customer_name = '';
/** /**
* Constructor * Constructor
*/ */
@@ -49,10 +55,6 @@ class LicenseExpirationEmail extends WC_Email
$this->title = __('License Expiration Warning', 'wc-licensed-product'); $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->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 = [ $this->placeholders = [
'{site_title}' => $this->get_blogname(), '{site_title}' => $this->get_blogname(),
'{product_name}' => '', '{product_name}' => '',
@@ -99,6 +101,7 @@ class LicenseExpirationEmail extends WC_Email
$this->license = $license; $this->license = $license;
$this->days_remaining = $days_remaining; $this->days_remaining = $days_remaining;
$this->recipient = $customer->user_email; $this->recipient = $customer->user_email;
$this->customer_name = $customer->display_name ?: __('Customer', 'wc-licensed-product');
$product = wc_get_product($license->getProductId()); $product = wc_get_product($license->getProductId());
$this->product_name = $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product'); $this->product_name = $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product');
@@ -134,22 +137,17 @@ class LicenseExpirationEmail extends WC_Email
*/ */
public function get_content_html(): string public function get_content_html(): string
{ {
return wc_get_template_html( ob_start();
$this->template_html,
[ // Use WooCommerce's email header
'license' => $this->license, wc_get_template('emails/email-header.php', ['email_heading' => $this->get_heading()]);
'days_remaining' => $this->days_remaining,
'product_name' => $this->product_name, $this->render_email_body_html();
'expiration_date' => $this->expiration_date,
'email_heading' => $this->get_heading(), // Use WooCommerce's email footer
'additional_content' => $this->get_additional_content(), wc_get_template('emails/email-footer.php', ['email' => $this]);
'sent_to_admin' => false,
'plain_text' => false, return ob_get_clean();
'email' => $this,
],
'',
$this->template_base
);
} }
/** /**
@@ -157,22 +155,132 @@ class LicenseExpirationEmail extends WC_Email
*/ */
public function get_content_plain(): string public function get_content_plain(): string
{ {
return wc_get_template_html( ob_start();
$this->template_plain,
[ echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
'license' => $this->license, echo esc_html(wp_strip_all_tags($this->get_heading()));
'days_remaining' => $this->days_remaining, echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
'product_name' => $this->product_name,
'expiration_date' => $this->expiration_date, $this->render_email_body_plain();
'email_heading' => $this->get_heading(),
'additional_content' => $this->get_additional_content(), return ob_get_clean();
'sent_to_admin' => false, }
'plain_text' => true,
'email' => $this, /**
], * Render HTML email body content
'', */
$this->template_base 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>
<?php if ($this->days_remaining === 1) : ?>
<p style="color: #e2401c; font-weight: 600;">
<?php printf(
esc_html__('Your license for %s will expire tomorrow (%s).', 'wc-licensed-product'),
'<strong>' . esc_html($this->product_name) . '</strong>',
esc_html($this->expiration_date)
); ?>
</p>
<?php else : ?>
<p style="color: #94660c; font-weight: 600;">
<?php printf(
esc_html__('Your license for %1$s will expire in %2$d days (%3$s).', 'wc-licensed-product'),
'<strong>' . esc_html($this->product_name) . '</strong>',
$this->days_remaining,
esc_html($this->expiration_date)
); ?>
</p>
<?php endif; ?>
<h2><?php esc_html_e('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('Expires:', 'wc-licensed-product'); ?></th>
<td class="td" style="text-align:<?php echo is_rtl() ? 'right' : 'left'; ?>; color: #e2401c; font-weight: 600;"><?php echo esc_html($this->expiration_date); ?></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";
if ($this->days_remaining === 1) {
printf(
esc_html__('Your license for %s will expire tomorrow (%s).', 'wc-licensed-product'),
esc_html($this->product_name),
esc_html($this->expiration_date)
);
} else {
printf(
esc_html__('Your license for %1$s will expire in %2$d days (%3$s).', 'wc-licensed-product'),
esc_html($this->product_name),
$this->days_remaining,
esc_html($this->expiration_date)
);
}
echo "\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($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__('Expires:', 'wc-licensed-product') . ' ' . esc_html($this->expiration_date) . "\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";
} }
/** /**

View File

@@ -1,97 +0,0 @@
<?php
/**
* License Expiration Warning Email (HTML)
*
* This template can be overridden by copying it to yourtheme/woocommerce/emails/license-expiration.php.
*
* @package Jeremias\WcLicensedProduct\Templates\Emails
* @version 0.0.8
*
* @var \Jeremias\WcLicensedProduct\License\License $license
* @var int $days_remaining
* @var string $product_name
* @var string $expiration_date
* @var string $email_heading
* @var string $additional_content
* @var bool $sent_to_admin
* @var bool $plain_text
* @var \Jeremias\WcLicensedProduct\Email\LicenseExpirationEmail $email
*/
defined('ABSPATH') || exit;
/*
* @hooked WC_Emails::email_header() Output the email header
*/
do_action('woocommerce_email_header', $email_heading, $email);
$customer = get_userdata($license->getCustomerId());
$customer_name = $customer ? $customer->display_name : __('Customer', 'wc-licensed-product');
$account_url = wc_get_account_endpoint_url('licenses');
?>
<p><?php printf(esc_html__('Hello %s,', 'wc-licensed-product'), esc_html($customer_name)); ?></p>
<?php if ($days_remaining === 1) : ?>
<p style="color: #dc3545; font-weight: 600;">
<?php printf(
esc_html__('Your license for %s will expire tomorrow (%s).', 'wc-licensed-product'),
esc_html($product_name),
esc_html($expiration_date)
); ?>
</p>
<?php else : ?>
<p style="color: #856404; font-weight: 600;">
<?php printf(
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)
); ?>
</p>
<?php endif; ?>
<h2><?php esc_html_e('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:left;"><?php esc_html_e('Product:', 'wc-licensed-product'); ?></th>
<td class="td" style="text-align:left;"><?php echo esc_html($product_name); ?></td>
</tr>
<tr>
<th class="td" scope="row" style="text-align:left;"><?php esc_html_e('License Key:', 'wc-licensed-product'); ?></th>
<td class="td" style="text-align:left;">
<code style="background: #f5f5f5; padding: 3px 8px; border-radius: 3px; font-family: monospace;">
<?php echo esc_html($license->getLicenseKey()); ?>
</code>
</td>
</tr>
<tr>
<th class="td" scope="row" style="text-align:left;"><?php esc_html_e('Domain:', 'wc-licensed-product'); ?></th>
<td class="td" style="text-align:left;"><?php echo esc_html($license->getDomain()); ?></td>
</tr>
<tr>
<th class="td" scope="row" style="text-align:left;"><?php esc_html_e('Expires:', 'wc-licensed-product'); ?></th>
<td class="td" style="text-align:left; color: #dc3545; font-weight: 600;"><?php echo esc_html($expiration_date); ?></td>
</tr>
</tbody>
</table>
</div>
<?php 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); ?>" style="display: inline-block; background: #7f54b3; color: #fff; 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
/*
* @hooked WC_Emails::email_footer() Output the email footer
*/
do_action('woocommerce_email_footer', $email);

View File

@@ -1,64 +0,0 @@
<?php
/**
* License Expiration Warning Email (Plain Text)
*
* This template can be overridden by copying it to yourtheme/woocommerce/emails/plain/license-expiration.php.
*
* @package Jeremias\WcLicensedProduct\Templates\Emails
* @version 0.0.8
*
* @var \Jeremias\WcLicensedProduct\License\License $license
* @var int $days_remaining
* @var string $product_name
* @var string $expiration_date
* @var string $email_heading
* @var string $additional_content
* @var bool $sent_to_admin
* @var bool $plain_text
* @var \Jeremias\WcLicensedProduct\Email\LicenseExpirationEmail $email
*/
defined('ABSPATH') || exit;
echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
echo esc_html(wp_strip_all_tags($email_heading));
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
$customer = get_userdata($license->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";