You've already forked wc-licensed-product
Implement version 0.0.8 features
- Remove Current Version field from product license settings - Derive current version from latest product version in database - Refactor email system to use WooCommerce email notification classes - Add LicenseExpirationEmail WC_Email class for expiration warnings - Add customizable email templates (HTML and plain text) - Update settings to link to WooCommerce email configuration - Update translations for new email-related strings Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<div style="background: #f8f9fa; padding: 30px; border-radius: 8px;">
|
||||
<h1 style="color: #333; margin-top: 0; font-size: 24px;">
|
||||
<?php esc_html_e('License Expiration Notice', 'wc-licensed-product'); ?>
|
||||
</h1>
|
||||
|
||||
<p><?php printf(esc_html__('Hello %s,', 'wc-licensed-product'), esc_html($customer->display_name)); ?></p>
|
||||
|
||||
<?php if ($daysRemaining === 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($productName),
|
||||
esc_html($expirationDate)
|
||||
); ?>
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<p style="color: #ffc107; 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($productName),
|
||||
$daysRemaining,
|
||||
esc_html($expirationDate)
|
||||
); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="background: #fff; padding: 20px; border-radius: 4px; margin: 20px 0; border: 1px solid #e5e5e5;">
|
||||
<h3 style="margin-top: 0; font-size: 16px;"><?php esc_html_e('License Details', 'wc-licensed-product'); ?></h3>
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #666;"><?php esc_html_e('Product:', 'wc-licensed-product'); ?></td>
|
||||
<td style="padding: 8px 0; font-weight: 600;"><?php echo esc_html($productName); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #666;"><?php esc_html_e('License Key:', 'wc-licensed-product'); ?></td>
|
||||
<td style="padding: 8px 0;">
|
||||
<code style="background: #f5f5f5; padding: 3px 8px; border-radius: 3px; font-family: monospace;">
|
||||
<?php echo esc_html($license->getLicenseKey()); ?>
|
||||
</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #666;"><?php esc_html_e('Domain:', 'wc-licensed-product'); ?></td>
|
||||
<td style="padding: 8px 0;"><?php echo esc_html($license->getDomain()); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #666;"><?php esc_html_e('Expires:', 'wc-licensed-product'); ?></td>
|
||||
<td style="padding: 8px 0; color: #dc3545; font-weight: 600;"><?php echo esc_html($expirationDate); ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p><?php esc_html_e('To continue using this product, please renew your license before the expiration date.', 'wc-licensed-product'); ?></p>
|
||||
|
||||
<p style="margin-top: 25px;">
|
||||
<a href="<?php echo esc_url($accountUrl); ?>"
|
||||
style="display: inline-block; background: #2271b1; 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>
|
||||
|
||||
<hr style="border: none; border-top: 1px solid #e5e5e5; margin: 30px 0;">
|
||||
|
||||
<p style="font-size: 14px; color: #666; margin-bottom: 0;">
|
||||
<?php printf(
|
||||
esc_html__('This email was sent from %s.', 'wc-licensed-product'),
|
||||
'<a href="' . esc_url($siteUrl) . '" style="color: #2271b1;">' . esc_html($siteName) . '</a>'
|
||||
); ?>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add license information to order completed email
|
||||
*/
|
||||
@@ -281,7 +200,7 @@ final class LicenseEmailController
|
||||
echo "\n" . esc_html__('License Key:', 'wc-licensed-product') . ' ' . esc_html($license->getLicenseKey()) . "\n";
|
||||
} else {
|
||||
?>
|
||||
<div style="margin-top: 10px; padding: 10px; background-color: #f8f9fa; border-left: 3px solid #0073aa;">
|
||||
<div style="margin-top: 10px; padding: 10px; background-color: #f8f9fa; border-left: 3px solid #7f54b3;">
|
||||
<strong><?php esc_html_e('License Key:', 'wc-licensed-product'); ?></strong>
|
||||
<code style="display: block; margin-top: 5px; padding: 5px; background: #fff; font-family: monospace;">
|
||||
<?php echo esc_html($license->getLicenseKey()); ?>
|
||||
@@ -324,7 +243,7 @@ final class LicenseEmailController
|
||||
<div style="margin: 20px 0; padding: 20px; background-color: #f8f9fa; border: 1px solid #e5e5e5; border-radius: 4px;">
|
||||
<h2 style="margin-top: 0; color: #333;"><?php esc_html_e('Your License Keys', 'wc-licensed-product'); ?></h2>
|
||||
|
||||
<?php if ($domain): ?>
|
||||
<?php if ($domain) : ?>
|
||||
<p style="margin-bottom: 15px;">
|
||||
<strong><?php esc_html_e('Licensed Domain:', 'wc-licensed-product'); ?></strong>
|
||||
<?php echo esc_html($domain); ?>
|
||||
@@ -340,7 +259,7 @@ final class LicenseEmailController
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($licenses as $item): ?>
|
||||
<?php foreach ($licenses as $item) : ?>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;"><?php echo esc_html($item['product_name']); ?></td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">
|
||||
|
||||
240
src/Email/LicenseExpirationEmail.php
Normal file
240
src/Email/LicenseExpirationEmail.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
/**
|
||||
* License Expiration Warning Email
|
||||
*
|
||||
* @package Jeremias\WcLicensedProduct\Email
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Jeremias\WcLicensedProduct\Email;
|
||||
|
||||
use Jeremias\WcLicensedProduct\License\License;
|
||||
use WC_Email;
|
||||
|
||||
/**
|
||||
* License Expiration Warning Email class
|
||||
*
|
||||
* Sends email notifications to customers when their licenses are about to expire.
|
||||
*/
|
||||
class LicenseExpirationEmail extends WC_Email
|
||||
{
|
||||
/**
|
||||
* License object
|
||||
*/
|
||||
public ?License $license = null;
|
||||
|
||||
/**
|
||||
* Days remaining until expiration
|
||||
*/
|
||||
public int $days_remaining = 0;
|
||||
|
||||
/**
|
||||
* Product name
|
||||
*/
|
||||
public string $product_name = '';
|
||||
|
||||
/**
|
||||
* Expiration date formatted
|
||||
*/
|
||||
public string $expiration_date = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->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'),
|
||||
'<code>{site_title}, {product_name}, {days_remaining}, {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,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user