You've already forked wc-licensed-product
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:
22
CLAUDE.md
22
CLAUDE.md
@@ -36,6 +36,12 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
|
||||
|
||||
_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
|
||||
|
||||
- **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
|
||||
- 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)
|
||||
- Refactored email system to use WooCommerce transactional email system
|
||||
- License Expiration Warning email configurable via WooCommerce > Settings > Emails
|
||||
- Uses WooCommerce's email header/footer templates for consistent styling
|
||||
- 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
|
||||
@@ -456,7 +457,8 @@ Full API documentation available in `openapi.json` (OpenAPI 3.1 specification).
|
||||
|
||||
**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
|
||||
- Email uses WooCommerce's `emails/email-header.php` and `emails/email-footer.php` templates
|
||||
- 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
|
||||
- Email enable/disable is controlled through WooCommerce email settings
|
||||
|
||||
@@ -44,9 +44,6 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,30 +58,6 @@ final class LicenseEmailController
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,7 @@ use WC_Email;
|
||||
* License Expiration Warning Email class
|
||||
*
|
||||
* 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
|
||||
{
|
||||
@@ -39,6 +40,11 @@ class LicenseExpirationEmail extends WC_Email
|
||||
*/
|
||||
public string $expiration_date = '';
|
||||
|
||||
/**
|
||||
* Customer display name
|
||||
*/
|
||||
public string $customer_name = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
@@ -49,10 +55,6 @@ class LicenseExpirationEmail extends WC_Email
|
||||
$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}' => '',
|
||||
@@ -99,6 +101,7 @@ class LicenseExpirationEmail extends WC_Email
|
||||
$this->license = $license;
|
||||
$this->days_remaining = $days_remaining;
|
||||
$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');
|
||||
@@ -134,22 +137,17 @@ class LicenseExpirationEmail extends WC_Email
|
||||
*/
|
||||
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
|
||||
);
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,22 +155,132 @@ class LicenseExpirationEmail extends WC_Email
|
||||
*/
|
||||
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
|
||||
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>
|
||||
|
||||
<?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";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
@@ -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";
|
||||
Reference in New Issue
Block a user