You've already forked wc-licensed-product
v0.0.4: - Add WooCommerce settings tab for default license settings - Per-product settings override global defaults v0.0.5: - Add bulk license operations (activate, deactivate, revoke, extend, delete) - Add license renewal/extension and lifetime functionality - Add quick action buttons per license row v0.0.6: - Add license dashboard with statistics and analytics - Add license transfer functionality (admin) - Add CSV export for licenses - Add OpenAPI 3.1 specification - Remove /deactivate API endpoint v0.0.7: - Move license dashboard to WooCommerce Reports section - Add license search and filtering in admin - Add customer-facing license transfer with AJAX modal - Add email notifications for license expiration warnings - Add bulk import licenses from CSV - Update README with comprehensive documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
403 lines
16 KiB
PHP
403 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* License Email Controller
|
|
*
|
|
* @package Jeremias\WcLicensedProduct\Email
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Jeremias\WcLicensedProduct\Email;
|
|
|
|
use Jeremias\WcLicensedProduct\License\LicenseManager;
|
|
|
|
/**
|
|
* Handles email notifications for licenses
|
|
*/
|
|
final class LicenseEmailController
|
|
{
|
|
private LicenseManager $licenseManager;
|
|
|
|
public function __construct(LicenseManager $licenseManager)
|
|
{
|
|
$this->licenseManager = $licenseManager;
|
|
$this->registerHooks();
|
|
}
|
|
|
|
/**
|
|
* Register WordPress hooks
|
|
*/
|
|
private function registerHooks(): void
|
|
{
|
|
// Add license info to order completed email
|
|
add_action('woocommerce_email_after_order_table', [$this, 'addLicenseInfoToEmail'], 20, 4);
|
|
|
|
// Add license info to order details in emails
|
|
add_action('woocommerce_order_item_meta_end', [$this, 'addLicenseToOrderItem'], 10, 4);
|
|
|
|
// Schedule cron job for expiration warnings
|
|
add_action('init', [$this, 'scheduleExpirationCheck']);
|
|
|
|
// Cron action for checking expiring licenses
|
|
add_action('wclp_check_expiring_licenses', [$this, 'sendExpirationWarnings']);
|
|
}
|
|
|
|
/**
|
|
* Schedule the expiration check cron job
|
|
*/
|
|
public function scheduleExpirationCheck(): void
|
|
{
|
|
if (!wp_next_scheduled('wclp_check_expiring_licenses')) {
|
|
wp_schedule_event(time(), 'daily', 'wclp_check_expiring_licenses');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send expiration warning emails
|
|
*/
|
|
public function sendExpirationWarnings(): void
|
|
{
|
|
// Check for licenses expiring in 7 days
|
|
$this->processExpirationWarnings(7, 'expiring_7_days');
|
|
|
|
// Check for licenses expiring in 1 day
|
|
$this->processExpirationWarnings(1, 'expiring_1_day');
|
|
}
|
|
|
|
/**
|
|
* Process and send expiration warnings for a specific time frame
|
|
*
|
|
* @param int $days Days until expiration
|
|
* @param string $notificationType Notification type identifier
|
|
*/
|
|
private function processExpirationWarnings(int $days, string $notificationType): void
|
|
{
|
|
$licenses = $this->licenseManager->getLicensesExpiringSoon($days);
|
|
|
|
foreach ($licenses as $license) {
|
|
// Skip if already notified
|
|
if ($this->licenseManager->wasExpirationNotified($license->getId(), $notificationType)) {
|
|
continue;
|
|
}
|
|
|
|
// Send the warning email
|
|
if ($this->sendExpirationWarningEmail($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
|
|
*/
|
|
public function addLicenseInfoToEmail(\WC_Order $order, bool $sentToAdmin, bool $plainText, $email): void
|
|
{
|
|
// Only add to completed order email sent to customer
|
|
if ($sentToAdmin || !$email || $email->id !== 'customer_completed_order') {
|
|
return;
|
|
}
|
|
|
|
$licenses = $this->getLicensesForOrder($order);
|
|
if (empty($licenses)) {
|
|
return;
|
|
}
|
|
|
|
if ($plainText) {
|
|
$this->renderPlainTextLicenseInfo($licenses, $order);
|
|
} else {
|
|
$this->renderHtmlLicenseInfo($licenses, $order);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add license key to order item in email
|
|
*/
|
|
public function addLicenseToOrderItem(int $itemId, \WC_Order_Item $item, \WC_Order $order, bool $plainText): void
|
|
{
|
|
$product = $item->get_product();
|
|
if (!$product || !$product->is_type('licensed')) {
|
|
return;
|
|
}
|
|
|
|
$license = $this->licenseManager->getLicenseByOrderAndProduct($order->get_id(), $product->get_id());
|
|
if (!$license) {
|
|
return;
|
|
}
|
|
|
|
if ($plainText) {
|
|
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;">
|
|
<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()); ?>
|
|
</code>
|
|
</div>
|
|
<?php
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all licenses for an order
|
|
*/
|
|
private function getLicensesForOrder(\WC_Order $order): array
|
|
{
|
|
$licenses = [];
|
|
|
|
foreach ($order->get_items() as $item) {
|
|
$product = $item->get_product();
|
|
if ($product && $product->is_type('licensed')) {
|
|
$license = $this->licenseManager->getLicenseByOrderAndProduct($order->get_id(), $product->get_id());
|
|
if ($license) {
|
|
$licenses[] = [
|
|
'license' => $license,
|
|
'product_name' => $product->get_name(),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $licenses;
|
|
}
|
|
|
|
/**
|
|
* Render license info in HTML format
|
|
*/
|
|
private function renderHtmlLicenseInfo(array $licenses, \WC_Order $order): void
|
|
{
|
|
$domain = $order->get_meta('_licensed_product_domain');
|
|
?>
|
|
<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): ?>
|
|
<p style="margin-bottom: 15px;">
|
|
<strong><?php esc_html_e('Licensed Domain:', 'wc-licensed-product'); ?></strong>
|
|
<?php echo esc_html($domain); ?>
|
|
</p>
|
|
<?php endif; ?>
|
|
|
|
<table style="width: 100%; border-collapse: collapse;">
|
|
<thead>
|
|
<tr>
|
|
<th style="text-align: left; padding: 10px; border-bottom: 2px solid #ddd;"><?php esc_html_e('Product', 'wc-licensed-product'); ?></th>
|
|
<th style="text-align: left; padding: 10px; border-bottom: 2px solid #ddd;"><?php esc_html_e('License Key', 'wc-licensed-product'); ?></th>
|
|
<th style="text-align: left; padding: 10px; border-bottom: 2px solid #ddd;"><?php esc_html_e('Expires', 'wc-licensed-product'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?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;">
|
|
<code style="background: #fff; padding: 3px 6px; font-family: monospace;">
|
|
<?php echo esc_html($item['license']->getLicenseKey()); ?>
|
|
</code>
|
|
</td>
|
|
<td style="padding: 10px; border-bottom: 1px solid #eee;">
|
|
<?php
|
|
$expiresAt = $item['license']->getExpiresAt();
|
|
echo $expiresAt
|
|
? esc_html($expiresAt->format(get_option('date_format')))
|
|
: esc_html__('Never', 'wc-licensed-product');
|
|
?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
|
|
<p style="margin-top: 15px; margin-bottom: 0; font-size: 0.9em; color: #666;">
|
|
<?php esc_html_e('You can also view your licenses in your account under "Licenses".', 'wc-licensed-product'); ?>
|
|
</p>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render license info in plain text format
|
|
*/
|
|
private function renderPlainTextLicenseInfo(array $licenses, \WC_Order $order): void
|
|
{
|
|
$domain = $order->get_meta('_licensed_product_domain');
|
|
|
|
echo "\n\n";
|
|
echo "==========================================================\n";
|
|
echo esc_html__('YOUR LICENSE KEYS', 'wc-licensed-product') . "\n";
|
|
echo "==========================================================\n\n";
|
|
|
|
if ($domain) {
|
|
echo esc_html__('Licensed Domain:', 'wc-licensed-product') . ' ' . esc_html($domain) . "\n\n";
|
|
}
|
|
|
|
foreach ($licenses as $item) {
|
|
echo esc_html($item['product_name']) . "\n";
|
|
echo esc_html__('License Key:', 'wc-licensed-product') . ' ' . esc_html($item['license']->getLicenseKey()) . "\n";
|
|
|
|
$expiresAt = $item['license']->getExpiresAt();
|
|
echo esc_html__('Expires:', 'wc-licensed-product') . ' ';
|
|
echo $expiresAt
|
|
? esc_html($expiresAt->format(get_option('date_format')))
|
|
: esc_html__('Never', 'wc-licensed-product');
|
|
echo "\n\n";
|
|
}
|
|
|
|
echo esc_html__('You can also view your licenses in your account under "Licenses".', 'wc-licensed-product') . "\n";
|
|
echo "==========================================================\n\n";
|
|
}
|
|
}
|