You've already forked wc-licensed-product
Customers can now purchase licenses with different durations (monthly, yearly, lifetime) through WooCommerce product variations. Each variation can have its own license validity settings. New features: - LicensedVariableProduct class for variable licensed products - LicensedProductVariation class for individual variations - Per-variation license duration and max activations settings - Duration labels in checkout (Monthly, Quarterly, Yearly, etc.) - Full support for WooCommerce Blocks checkout with variations - Updated translations for German (de_CH) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
642 lines
24 KiB
PHP
642 lines
24 KiB
PHP
<?php
|
||
/**
|
||
* Checkout Controller
|
||
*
|
||
* @package Jeremias\WcLicensedProduct\Checkout
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Jeremias\WcLicensedProduct\Checkout;
|
||
|
||
use Jeremias\WcLicensedProduct\License\LicenseManager;
|
||
use Jeremias\WcLicensedProduct\Admin\SettingsController;
|
||
use Jeremias\WcLicensedProduct\Product\LicensedProductVariation;
|
||
|
||
/**
|
||
* Handles checkout modifications for licensed products
|
||
*/
|
||
final class CheckoutController
|
||
{
|
||
private LicenseManager $licenseManager;
|
||
|
||
public function __construct(LicenseManager $licenseManager)
|
||
{
|
||
$this->licenseManager = $licenseManager;
|
||
$this->registerHooks();
|
||
}
|
||
|
||
/**
|
||
* Register WordPress hooks
|
||
*/
|
||
private function registerHooks(): void
|
||
{
|
||
// Add domain field to checkout
|
||
add_action('woocommerce_after_order_notes', [$this, 'addDomainField']);
|
||
|
||
// Validate domain field
|
||
add_action('woocommerce_checkout_process', [$this, 'validateDomainField']);
|
||
|
||
// Save domain field to order meta
|
||
add_action('woocommerce_checkout_update_order_meta', [$this, 'saveDomainField']);
|
||
|
||
// Display domain in order details (admin)
|
||
add_action('woocommerce_admin_order_data_after_billing_address', [$this, 'displayDomainInAdmin']);
|
||
|
||
// Display domain in order email
|
||
add_action('woocommerce_email_after_order_table', [$this, 'displayDomainInEmail'], 10, 3);
|
||
}
|
||
|
||
/**
|
||
* Check if cart contains licensed products
|
||
*/
|
||
private function cartHasLicensedProducts(): bool
|
||
{
|
||
return !empty($this->getLicensedProductsFromCart());
|
||
}
|
||
|
||
/**
|
||
* Get licensed products from cart with quantities
|
||
*
|
||
* @return array<string, array{product_id: int, variation_id: int, name: string, quantity: int, duration_label: string}>
|
||
*/
|
||
private function getLicensedProductsFromCart(): array
|
||
{
|
||
if (!WC()->cart) {
|
||
return [];
|
||
}
|
||
|
||
$licensedProducts = [];
|
||
foreach (WC()->cart->get_cart() as $cartItem) {
|
||
$product = $cartItem['data'];
|
||
if (!$product) {
|
||
continue;
|
||
}
|
||
|
||
// Check for simple licensed products
|
||
if ($product->is_type('licensed')) {
|
||
$productId = $product->get_id();
|
||
$licensedProducts[$productId] = [
|
||
'product_id' => $productId,
|
||
'variation_id' => 0,
|
||
'name' => $product->get_name(),
|
||
'quantity' => (int) $cartItem['quantity'],
|
||
'duration_label' => '',
|
||
];
|
||
continue;
|
||
}
|
||
|
||
// Check for variations of licensed-variable products
|
||
if ($product->is_type('variation')) {
|
||
$parentId = $product->get_parent_id();
|
||
$parent = wc_get_product($parentId);
|
||
|
||
if ($parent && $parent->is_type('licensed-variable')) {
|
||
$variationId = $product->get_id();
|
||
// Use combination key to allow same product with different variations
|
||
$key = "{$parentId}_{$variationId}";
|
||
|
||
// Get duration label if it's a LicensedProductVariation
|
||
$durationLabel = '';
|
||
if ($product instanceof LicensedProductVariation) {
|
||
$durationLabel = $product->get_license_duration_label();
|
||
} else {
|
||
// Try to instantiate as LicensedProductVariation
|
||
$variation = new LicensedProductVariation($variationId);
|
||
$durationLabel = $variation->get_license_duration_label();
|
||
}
|
||
|
||
$licensedProducts[$key] = [
|
||
'product_id' => $parentId,
|
||
'variation_id' => $variationId,
|
||
'name' => $product->get_name(),
|
||
'quantity' => (int) $cartItem['quantity'],
|
||
'duration_label' => $durationLabel,
|
||
];
|
||
}
|
||
}
|
||
}
|
||
|
||
return $licensedProducts;
|
||
}
|
||
|
||
/**
|
||
* Add domain fields to checkout form
|
||
* Shows multiple domain fields if multi-domain is enabled, otherwise single field
|
||
*/
|
||
public function addDomainField(): void
|
||
{
|
||
$licensedProducts = $this->getLicensedProductsFromCart();
|
||
if (empty($licensedProducts)) {
|
||
return;
|
||
}
|
||
|
||
// Check if multi-domain licensing is enabled
|
||
if (SettingsController::isMultiDomainEnabled()) {
|
||
$this->renderMultiDomainFields($licensedProducts);
|
||
} else {
|
||
$this->renderSingleDomainField();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Render single domain field (legacy mode)
|
||
*/
|
||
private function renderSingleDomainField(): void
|
||
{
|
||
$savedValue = '';
|
||
|
||
// Check POST data first (validation failure case)
|
||
if (isset($_POST['licensed_product_domain'])) {
|
||
$savedValue = sanitize_text_field($_POST['licensed_product_domain']);
|
||
} elseif (WC()->session) {
|
||
$savedValue = WC()->session->get('licensed_product_domain', '');
|
||
}
|
||
|
||
?>
|
||
<div id="licensed-product-domain-field">
|
||
<h3><?php esc_html_e('License Domain', 'wc-licensed-product'); ?></h3>
|
||
<p class="form-row form-row-wide">
|
||
<label for="licensed_product_domain">
|
||
<?php esc_html_e('Domain', 'wc-licensed-product'); ?>
|
||
<abbr class="required" title="<?php esc_attr_e('required', 'wc-licensed-product'); ?>">*</abbr>
|
||
</label>
|
||
<input
|
||
type="text"
|
||
class="input-text"
|
||
name="licensed_product_domain"
|
||
id="licensed_product_domain"
|
||
placeholder="<?php esc_attr_e('example.com', 'wc-licensed-product'); ?>"
|
||
value="<?php echo esc_attr($savedValue); ?>"
|
||
/>
|
||
<span class="description">
|
||
<?php esc_html_e('Enter the domain where you will use the license (without http:// or www).', 'wc-licensed-product'); ?>
|
||
</span>
|
||
</p>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render multi-domain fields (one per quantity)
|
||
*/
|
||
private function renderMultiDomainFields(array $licensedProducts): void
|
||
{
|
||
?>
|
||
<div id="licensed-product-domain-fields">
|
||
<h3><?php esc_html_e('License Domains', 'wc-licensed-product'); ?></h3>
|
||
<p class="wclp-domain-description">
|
||
<?php esc_html_e('Enter a unique domain for each license (without http:// or www).', 'wc-licensed-product'); ?>
|
||
</p>
|
||
|
||
<?php foreach ($licensedProducts as $key => $productData): ?>
|
||
<?php
|
||
$productId = $productData['product_id'];
|
||
$variationId = $productData['variation_id'] ?? 0;
|
||
$durationLabel = $productData['duration_label'] ?? '';
|
||
// Use key for field names to handle variations
|
||
$fieldKey = $variationId > 0 ? "{$productId}_{$variationId}" : $productId;
|
||
?>
|
||
<div class="wclp-product-domains" data-product-id="<?php echo esc_attr($productId); ?>" data-variation-id="<?php echo esc_attr($variationId); ?>">
|
||
<h4>
|
||
<?php
|
||
echo esc_html($productData['name']);
|
||
if (!empty($durationLabel)) {
|
||
echo ' <span class="wclp-duration-badge">(' . esc_html($durationLabel) . ')</span>';
|
||
}
|
||
if ($productData['quantity'] > 1) {
|
||
printf(' ×%d', $productData['quantity']);
|
||
}
|
||
?>
|
||
</h4>
|
||
|
||
<?php for ($i = 0; $i < $productData['quantity']; $i++): ?>
|
||
<?php
|
||
$fieldName = sprintf('licensed_domains[%s][%d]', $fieldKey, $i);
|
||
$fieldId = sprintf('licensed_domain_%s_%d', str_replace('_', '-', $fieldKey), $i);
|
||
$savedValue = $this->getSavedDomainValue($productId, $i, $variationId);
|
||
?>
|
||
<p class="form-row form-row-wide wclp-domain-row">
|
||
<label for="<?php echo esc_attr($fieldId); ?>">
|
||
<?php
|
||
printf(
|
||
/* translators: %d: license number */
|
||
esc_html__('License %d:', 'wc-licensed-product'),
|
||
$i + 1
|
||
);
|
||
?>
|
||
<abbr class="required" title="<?php esc_attr_e('required', 'wc-licensed-product'); ?>">*</abbr>
|
||
</label>
|
||
<input
|
||
type="text"
|
||
class="input-text wclp-domain-input"
|
||
name="<?php echo esc_attr($fieldName); ?>"
|
||
id="<?php echo esc_attr($fieldId); ?>"
|
||
placeholder="<?php esc_attr_e('example.com', 'wc-licensed-product'); ?>"
|
||
value="<?php echo esc_attr($savedValue); ?>"
|
||
/>
|
||
<?php if ($variationId > 0): ?>
|
||
<input type="hidden" name="licensed_variation_ids[<?php echo esc_attr($fieldKey); ?>]" value="<?php echo esc_attr($variationId); ?>" />
|
||
<?php endif; ?>
|
||
</p>
|
||
<?php endfor; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<style>
|
||
#licensed-product-domain-fields { margin-bottom: 20px; }
|
||
#licensed-product-domain-fields h3 { margin-bottom: 10px; }
|
||
.wclp-domain-description { margin-bottom: 15px; color: #666; }
|
||
.wclp-product-domains { margin-bottom: 20px; padding: 15px; background: #f8f8f8; border-radius: 4px; }
|
||
.wclp-product-domains h4 { margin: 0 0 10px 0; font-size: 1em; }
|
||
.wclp-duration-badge { color: #0073aa; font-weight: normal; }
|
||
.wclp-domain-row { margin-bottom: 10px; }
|
||
.wclp-domain-row:last-child { margin-bottom: 0; }
|
||
.wclp-domain-row label { display: block; margin-bottom: 5px; }
|
||
</style>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Get saved domain value from session/POST
|
||
*/
|
||
private function getSavedDomainValue(int $productId, int $index, int $variationId = 0): string
|
||
{
|
||
// Build the field key (with or without variation)
|
||
$fieldKey = $variationId > 0 ? "{$productId}_{$variationId}" : (string) $productId;
|
||
|
||
// Check POST data first (validation failure case)
|
||
if (isset($_POST['licensed_domains'][$fieldKey][$index])) {
|
||
return sanitize_text_field($_POST['licensed_domains'][$fieldKey][$index]);
|
||
}
|
||
|
||
// Also try numeric key for backward compatibility
|
||
if (isset($_POST['licensed_domains'][$productId][$index])) {
|
||
return sanitize_text_field($_POST['licensed_domains'][$productId][$index]);
|
||
}
|
||
|
||
// Check session for blocks checkout
|
||
if (WC()->session) {
|
||
$sessionDomains = WC()->session->get('licensed_product_domains', []);
|
||
foreach ($sessionDomains as $item) {
|
||
$itemProductId = (int) ($item['product_id'] ?? 0);
|
||
$itemVariationId = (int) ($item['variation_id'] ?? 0);
|
||
|
||
// Match by product and variation
|
||
if ($itemProductId === $productId && $itemVariationId === $variationId) {
|
||
if (isset($item['domains'][$index])) {
|
||
return $item['domains'][$index];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return '';
|
||
}
|
||
|
||
/**
|
||
* Validate domain fields during checkout
|
||
*/
|
||
public function validateDomainField(): void
|
||
{
|
||
$licensedProducts = $this->getLicensedProductsFromCart();
|
||
if (empty($licensedProducts)) {
|
||
return;
|
||
}
|
||
|
||
// Check if multi-domain licensing is enabled
|
||
if (SettingsController::isMultiDomainEnabled()) {
|
||
$this->validateMultiDomainFields($licensedProducts);
|
||
} else {
|
||
$this->validateSingleDomainField();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Validate single domain field
|
||
*/
|
||
private function validateSingleDomainField(): void
|
||
{
|
||
$domain = isset($_POST['licensed_product_domain']) ? sanitize_text_field($_POST['licensed_product_domain']) : '';
|
||
|
||
if (empty($domain)) {
|
||
wc_add_notice(__('Please enter a domain for your license.', 'wc-licensed-product'), 'error');
|
||
return;
|
||
}
|
||
|
||
$normalizedDomain = $this->licenseManager->normalizeDomain($domain);
|
||
if (!$this->isValidDomain($normalizedDomain)) {
|
||
wc_add_notice(__('Please enter a valid domain for your license.', 'wc-licensed-product'), 'error');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Validate multi-domain fields
|
||
*/
|
||
private function validateMultiDomainFields(array $licensedProducts): void
|
||
{
|
||
$licensedDomains = $_POST['licensed_domains'] ?? [];
|
||
|
||
foreach ($licensedProducts as $key => $productData) {
|
||
$productId = $productData['product_id'];
|
||
$variationId = $productData['variation_id'] ?? 0;
|
||
$fieldKey = $variationId > 0 ? "{$productId}_{$variationId}" : (string) $productId;
|
||
|
||
$productDomains = $licensedDomains[$fieldKey] ?? $licensedDomains[$productId] ?? [];
|
||
$normalizedDomains = [];
|
||
|
||
for ($i = 0; $i < $productData['quantity']; $i++) {
|
||
$domain = isset($productDomains[$i]) ? sanitize_text_field($productDomains[$i]) : '';
|
||
|
||
// Check if domain is empty
|
||
if (empty($domain)) {
|
||
wc_add_notice(
|
||
sprintf(
|
||
/* translators: 1: product name, 2: license number */
|
||
__('Please enter a domain for %1$s (License %2$d).', 'wc-licensed-product'),
|
||
$productData['name'],
|
||
$i + 1
|
||
),
|
||
'error'
|
||
);
|
||
continue;
|
||
}
|
||
|
||
// Validate domain format
|
||
$normalizedDomain = $this->licenseManager->normalizeDomain($domain);
|
||
if (!$this->isValidDomain($normalizedDomain)) {
|
||
wc_add_notice(
|
||
sprintf(
|
||
/* translators: 1: product name, 2: license number */
|
||
__('Please enter a valid domain for %1$s (License %2$d).', 'wc-licensed-product'),
|
||
$productData['name'],
|
||
$i + 1
|
||
),
|
||
'error'
|
||
);
|
||
continue;
|
||
}
|
||
|
||
// Check for duplicate domains within same product/variation
|
||
if (in_array($normalizedDomain, $normalizedDomains, true)) {
|
||
wc_add_notice(
|
||
sprintf(
|
||
/* translators: 1: domain name, 2: product name */
|
||
__('The domain "%1$s" is used multiple times for %2$s. Each license requires a unique domain.', 'wc-licensed-product'),
|
||
$normalizedDomain,
|
||
$productData['name']
|
||
),
|
||
'error'
|
||
);
|
||
} else {
|
||
$normalizedDomains[] = $normalizedDomain;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Save domain fields to order meta
|
||
*/
|
||
public function saveDomainField(int $orderId): void
|
||
{
|
||
$licensedProducts = $this->getLicensedProductsFromCart();
|
||
if (empty($licensedProducts)) {
|
||
return;
|
||
}
|
||
|
||
$order = wc_get_order($orderId);
|
||
if (!$order) {
|
||
return;
|
||
}
|
||
|
||
// Check if multi-domain licensing is enabled
|
||
if (SettingsController::isMultiDomainEnabled()) {
|
||
$this->saveMultiDomainFields($order, $licensedProducts);
|
||
} else {
|
||
$this->saveSingleDomainField($order);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Save single domain field to order meta (legacy format)
|
||
*/
|
||
private function saveSingleDomainField(\WC_Order $order): void
|
||
{
|
||
$domain = isset($_POST['licensed_product_domain']) ? sanitize_text_field($_POST['licensed_product_domain']) : '';
|
||
|
||
if (!empty($domain)) {
|
||
$normalizedDomain = $this->licenseManager->normalizeDomain($domain);
|
||
$order->update_meta_data('_licensed_product_domain', $normalizedDomain);
|
||
$order->save();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Save multi-domain fields to order meta
|
||
*/
|
||
private function saveMultiDomainFields(\WC_Order $order, array $licensedProducts): void
|
||
{
|
||
$licensedDomains = $_POST['licensed_domains'] ?? [];
|
||
$licensedVariationIds = $_POST['licensed_variation_ids'] ?? [];
|
||
$domainData = [];
|
||
|
||
foreach ($licensedProducts as $key => $productData) {
|
||
$productId = $productData['product_id'];
|
||
$variationId = $productData['variation_id'] ?? 0;
|
||
$fieldKey = $variationId > 0 ? "{$productId}_{$variationId}" : (string) $productId;
|
||
|
||
$productDomains = $licensedDomains[$fieldKey] ?? $licensedDomains[$productId] ?? [];
|
||
$normalizedDomains = [];
|
||
|
||
for ($i = 0; $i < $productData['quantity']; $i++) {
|
||
$domain = isset($productDomains[$i]) ? sanitize_text_field($productDomains[$i]) : '';
|
||
if (!empty($domain)) {
|
||
$normalizedDomains[] = $this->licenseManager->normalizeDomain($domain);
|
||
}
|
||
}
|
||
|
||
if (!empty($normalizedDomains)) {
|
||
$entry = [
|
||
'product_id' => $productId,
|
||
'domains' => $normalizedDomains,
|
||
];
|
||
|
||
// Include variation_id if present
|
||
if ($variationId > 0) {
|
||
$entry['variation_id'] = $variationId;
|
||
}
|
||
|
||
$domainData[] = $entry;
|
||
}
|
||
}
|
||
|
||
if (!empty($domainData)) {
|
||
$order->update_meta_data('_licensed_product_domains', $domainData);
|
||
$order->save();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Display domains in admin order view
|
||
*/
|
||
public function displayDomainInAdmin(\WC_Order $order): void
|
||
{
|
||
// Try new multi-domain format first
|
||
$domainData = $order->get_meta('_licensed_product_domains');
|
||
if (!empty($domainData) && is_array($domainData)) {
|
||
$this->displayMultiDomainsInAdmin($domainData);
|
||
return;
|
||
}
|
||
|
||
// Fall back to legacy single domain
|
||
$domain = $order->get_meta('_licensed_product_domain');
|
||
if (!$domain) {
|
||
return;
|
||
}
|
||
|
||
?>
|
||
<p>
|
||
<strong><?php esc_html_e('License Domain:', 'wc-licensed-product'); ?></strong>
|
||
<?php echo esc_html($domain); ?>
|
||
</p>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Display multi-domain data in admin
|
||
*/
|
||
private function displayMultiDomainsInAdmin(array $domainData): void
|
||
{
|
||
?>
|
||
<div class="wclp-order-domains">
|
||
<strong><?php esc_html_e('License Domains:', 'wc-licensed-product'); ?></strong>
|
||
<?php foreach ($domainData as $item): ?>
|
||
<?php
|
||
$productId = $item['product_id'];
|
||
$variationId = $item['variation_id'] ?? 0;
|
||
|
||
// Get product name
|
||
if ($variationId > 0) {
|
||
$variation = wc_get_product($variationId);
|
||
$productName = $variation ? $variation->get_name() : __('Unknown Variation', 'wc-licensed-product');
|
||
|
||
// Add duration label if available
|
||
if ($variation instanceof LicensedProductVariation) {
|
||
$productName .= ' (' . $variation->get_license_duration_label() . ')';
|
||
}
|
||
} else {
|
||
$product = wc_get_product($productId);
|
||
$productName = $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product');
|
||
}
|
||
?>
|
||
<p style="margin: 5px 0 5px 15px;">
|
||
<em><?php echo esc_html($productName); ?>:</em><br>
|
||
<?php echo esc_html(implode(', ', $item['domains'])); ?>
|
||
</p>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Display domains in order emails
|
||
*/
|
||
public function displayDomainInEmail(\WC_Order $order, bool $sentToAdmin, bool $plainText): void
|
||
{
|
||
// Try new multi-domain format first
|
||
$domainData = $order->get_meta('_licensed_product_domains');
|
||
if (!empty($domainData) && is_array($domainData)) {
|
||
$this->displayMultiDomainsInEmail($domainData, $plainText);
|
||
return;
|
||
}
|
||
|
||
// Fall back to legacy single domain
|
||
$domain = $order->get_meta('_licensed_product_domain');
|
||
if (!$domain) {
|
||
return;
|
||
}
|
||
|
||
if ($plainText) {
|
||
echo "\n" . esc_html__('License Domain:', 'wc-licensed-product') . ' ' . esc_html($domain) . "\n";
|
||
} else {
|
||
?>
|
||
<p>
|
||
<strong><?php esc_html_e('License Domain:', 'wc-licensed-product'); ?></strong>
|
||
<?php echo esc_html($domain); ?>
|
||
</p>
|
||
<?php
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Display multi-domain data in email
|
||
*/
|
||
private function displayMultiDomainsInEmail(array $domainData, bool $plainText): void
|
||
{
|
||
if ($plainText) {
|
||
echo "\n" . esc_html__('License Domains:', 'wc-licensed-product') . "\n";
|
||
foreach ($domainData as $item) {
|
||
$productId = $item['product_id'];
|
||
$variationId = $item['variation_id'] ?? 0;
|
||
|
||
if ($variationId > 0) {
|
||
$variation = wc_get_product($variationId);
|
||
$productName = $variation ? $variation->get_name() : __('Unknown Variation', 'wc-licensed-product');
|
||
if ($variation instanceof LicensedProductVariation) {
|
||
$productName .= ' (' . $variation->get_license_duration_label() . ')';
|
||
}
|
||
} else {
|
||
$product = wc_get_product($productId);
|
||
$productName = $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product');
|
||
}
|
||
|
||
echo ' ' . esc_html($productName) . ': ' . esc_html(implode(', ', $item['domains'])) . "\n";
|
||
}
|
||
} else {
|
||
?>
|
||
<div style="margin-bottom: 15px;">
|
||
<strong><?php esc_html_e('License Domains:', 'wc-licensed-product'); ?></strong>
|
||
<?php foreach ($domainData as $item): ?>
|
||
<?php
|
||
$productId = $item['product_id'];
|
||
$variationId = $item['variation_id'] ?? 0;
|
||
|
||
if ($variationId > 0) {
|
||
$variation = wc_get_product($variationId);
|
||
$productName = $variation ? $variation->get_name() : __('Unknown Variation', 'wc-licensed-product');
|
||
if ($variation instanceof LicensedProductVariation) {
|
||
$productName .= ' (' . $variation->get_license_duration_label() . ')';
|
||
}
|
||
} else {
|
||
$product = wc_get_product($productId);
|
||
$productName = $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product');
|
||
}
|
||
?>
|
||
<p style="margin: 5px 0 5px 15px;">
|
||
<em><?php echo esc_html($productName); ?>:</em><br>
|
||
<?php echo esc_html(implode(', ', $item['domains'])); ?>
|
||
</p>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Validate domain format
|
||
*/
|
||
private function isValidDomain(string $domain): bool
|
||
{
|
||
// Basic domain validation
|
||
if (strlen($domain) > 255) {
|
||
return false;
|
||
}
|
||
|
||
// Check for valid domain pattern
|
||
$pattern = '/^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/';
|
||
|
||
return (bool) preg_match($pattern, $domain);
|
||
}
|
||
}
|