2026-01-21 21:58:54 +01:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* WooCommerce Store API Extension
|
|
|
|
|
*
|
|
|
|
|
* @package Jeremias\WcLicensedProduct\Checkout
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace Jeremias\WcLicensedProduct\Checkout;
|
|
|
|
|
|
|
|
|
|
use Automattic\WooCommerce\StoreApi\Schemas\V1\CheckoutSchema;
|
|
|
|
|
use Automattic\WooCommerce\StoreApi\StoreApi;
|
|
|
|
|
use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
|
2026-01-25 18:31:36 +01:00
|
|
|
use Jeremias\WcLicensedProduct\Admin\SettingsController;
|
2026-01-21 21:58:54 +01:00
|
|
|
use Jeremias\WcLicensedProduct\License\LicenseManager;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extends the Store API to handle licensed product domain data
|
|
|
|
|
*/
|
|
|
|
|
final class StoreApiExtension
|
|
|
|
|
{
|
|
|
|
|
private const IDENTIFIER = 'wc-licensed-product';
|
|
|
|
|
|
|
|
|
|
private LicenseManager $licenseManager;
|
|
|
|
|
|
|
|
|
|
public function __construct(LicenseManager $licenseManager)
|
|
|
|
|
{
|
|
|
|
|
$this->licenseManager = $licenseManager;
|
|
|
|
|
$this->registerHooks();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register Store API hooks
|
|
|
|
|
*/
|
|
|
|
|
private function registerHooks(): void
|
|
|
|
|
{
|
|
|
|
|
add_action('woocommerce_blocks_loaded', [$this, 'registerStoreApiExtension']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register the Store API extension
|
|
|
|
|
*/
|
|
|
|
|
public function registerStoreApiExtension(): void
|
|
|
|
|
{
|
|
|
|
|
if (!class_exists('Automattic\WooCommerce\StoreApi\StoreApi')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Register endpoint data extension
|
|
|
|
|
woocommerce_store_api_register_endpoint_data([
|
|
|
|
|
'endpoint' => CheckoutSchema::IDENTIFIER,
|
|
|
|
|
'namespace' => self::IDENTIFIER,
|
|
|
|
|
'data_callback' => [$this, 'getExtensionData'],
|
|
|
|
|
'schema_callback' => [$this, 'getExtensionSchema'],
|
|
|
|
|
'schema_type' => ARRAY_A,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Register update callback for the domain field
|
|
|
|
|
woocommerce_store_api_register_update_callback([
|
|
|
|
|
'namespace' => self::IDENTIFIER,
|
|
|
|
|
'callback' => [$this, 'handleExtensionUpdate'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Hook into checkout order processing
|
|
|
|
|
add_action('woocommerce_store_api_checkout_order_processed', [$this, 'processCheckoutOrder']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get extension data for the checkout endpoint
|
|
|
|
|
*/
|
|
|
|
|
public function getExtensionData(): array
|
|
|
|
|
{
|
2026-01-25 18:31:36 +01:00
|
|
|
if (SettingsController::isMultiDomainEnabled()) {
|
|
|
|
|
return [
|
|
|
|
|
'licensed_product_domains' => WC()->session ? WC()->session->get('licensed_product_domains', []) : [],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 21:58:54 +01:00
|
|
|
return [
|
|
|
|
|
'licensed_product_domain' => WC()->session ? WC()->session->get('licensed_product_domain', '') : '',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get extension schema
|
|
|
|
|
*/
|
|
|
|
|
public function getExtensionSchema(): array
|
|
|
|
|
{
|
2026-01-25 18:31:36 +01:00
|
|
|
if (SettingsController::isMultiDomainEnabled()) {
|
|
|
|
|
return [
|
|
|
|
|
'licensed_product_domains' => [
|
|
|
|
|
'description' => __('Domains for license activation by product', 'wc-licensed-product'),
|
|
|
|
|
'type' => 'array',
|
|
|
|
|
'context' => ['view', 'edit'],
|
|
|
|
|
'readonly' => false,
|
|
|
|
|
'items' => [
|
|
|
|
|
'type' => 'object',
|
|
|
|
|
'properties' => [
|
|
|
|
|
'product_id' => [
|
|
|
|
|
'type' => 'integer',
|
|
|
|
|
],
|
2026-01-26 16:14:15 +01:00
|
|
|
'variation_id' => [
|
|
|
|
|
'type' => 'integer',
|
|
|
|
|
],
|
2026-01-25 18:31:36 +01:00
|
|
|
'domains' => [
|
|
|
|
|
'type' => 'array',
|
|
|
|
|
'items' => [
|
|
|
|
|
'type' => 'string',
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 21:58:54 +01:00
|
|
|
return [
|
|
|
|
|
'licensed_product_domain' => [
|
|
|
|
|
'description' => __('Domain for license activation', 'wc-licensed-product'),
|
|
|
|
|
'type' => 'string',
|
|
|
|
|
'context' => ['view', 'edit'],
|
|
|
|
|
'readonly' => false,
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle extension data updates from the frontend
|
|
|
|
|
*/
|
|
|
|
|
public function handleExtensionUpdate(array $data): void
|
|
|
|
|
{
|
2026-01-25 18:31:36 +01:00
|
|
|
if (SettingsController::isMultiDomainEnabled()) {
|
|
|
|
|
// Multi-domain mode
|
|
|
|
|
if (isset($data['licensed_product_domains']) && is_array($data['licensed_product_domains'])) {
|
|
|
|
|
$normalizedData = $this->normalizeDomainsData($data['licensed_product_domains']);
|
2026-01-21 21:58:54 +01:00
|
|
|
|
2026-01-25 18:31:36 +01:00
|
|
|
if (WC()->session) {
|
|
|
|
|
WC()->session->set('licensed_product_domains', $normalizedData);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Single domain mode
|
|
|
|
|
if (isset($data['licensed_product_domain'])) {
|
|
|
|
|
$sanitized = sanitize_text_field($data['licensed_product_domain']);
|
|
|
|
|
$normalized = $this->licenseManager->normalizeDomain($sanitized);
|
|
|
|
|
|
|
|
|
|
if (WC()->session) {
|
|
|
|
|
WC()->session->set('licensed_product_domain', $normalized);
|
|
|
|
|
}
|
2026-01-21 21:58:54 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-25 18:31:36 +01:00
|
|
|
* Normalize domains data from frontend
|
2026-01-21 21:58:54 +01:00
|
|
|
*/
|
2026-01-25 18:31:36 +01:00
|
|
|
private function normalizeDomainsData(array $domainsData): array
|
2026-01-21 21:58:54 +01:00
|
|
|
{
|
2026-01-25 18:31:36 +01:00
|
|
|
$normalized = [];
|
|
|
|
|
|
|
|
|
|
foreach ($domainsData as $item) {
|
|
|
|
|
if (!isset($item['product_id']) || !isset($item['domains']) || !is_array($item['domains'])) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$productId = (int) $item['product_id'];
|
2026-01-26 16:14:15 +01:00
|
|
|
$variationId = isset($item['variation_id']) ? (int) $item['variation_id'] : 0;
|
2026-01-25 18:31:36 +01:00
|
|
|
$domains = [];
|
|
|
|
|
|
|
|
|
|
foreach ($item['domains'] as $domain) {
|
|
|
|
|
$sanitized = sanitize_text_field($domain);
|
|
|
|
|
if (!empty($sanitized)) {
|
|
|
|
|
$domains[] = $this->licenseManager->normalizeDomain($sanitized);
|
|
|
|
|
}
|
2026-01-21 21:58:54 +01:00
|
|
|
}
|
2026-01-25 18:31:36 +01:00
|
|
|
|
|
|
|
|
if (!empty($domains)) {
|
2026-01-26 16:14:15 +01:00
|
|
|
$entry = [
|
2026-01-25 18:31:36 +01:00
|
|
|
'product_id' => $productId,
|
|
|
|
|
'domains' => $domains,
|
|
|
|
|
];
|
2026-01-26 16:14:15 +01:00
|
|
|
|
|
|
|
|
// Include variation_id if present
|
|
|
|
|
if ($variationId > 0) {
|
|
|
|
|
$entry['variation_id'] = $variationId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$normalized[] = $entry;
|
2026-01-25 18:31:36 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $normalized;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process the checkout order - save domains to order meta
|
|
|
|
|
*/
|
|
|
|
|
public function processCheckoutOrder(\WC_Order $order): void
|
|
|
|
|
{
|
|
|
|
|
$requestData = json_decode(file_get_contents('php://input'), true);
|
|
|
|
|
|
|
|
|
|
if (SettingsController::isMultiDomainEnabled()) {
|
|
|
|
|
$this->processMultiDomainOrder($order, $requestData);
|
|
|
|
|
} else {
|
|
|
|
|
$this->processSingleDomainOrder($order, $requestData);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process order in single domain mode (legacy)
|
|
|
|
|
*/
|
|
|
|
|
private function processSingleDomainOrder(\WC_Order $order, ?array $requestData): void
|
|
|
|
|
{
|
|
|
|
|
$domain = '';
|
|
|
|
|
|
|
|
|
|
// Check session first
|
|
|
|
|
if (WC()->session) {
|
|
|
|
|
$domain = WC()->session->get('licensed_product_domain', '');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check in the request data for block checkout (extension data)
|
|
|
|
|
if (empty($domain) && isset($requestData['extensions'][self::IDENTIFIER]['licensed_product_domain'])) {
|
|
|
|
|
$sanitized = sanitize_text_field($requestData['extensions'][self::IDENTIFIER]['licensed_product_domain']);
|
|
|
|
|
$domain = $this->licenseManager->normalizeDomain($sanitized);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for wclp_license_domain (from our hidden input)
|
|
|
|
|
if (empty($domain) && isset($requestData['wclp_license_domain'])) {
|
|
|
|
|
$sanitized = sanitize_text_field($requestData['wclp_license_domain']);
|
|
|
|
|
$domain = $this->licenseManager->normalizeDomain($sanitized);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for additional_fields (WC Blocks API)
|
|
|
|
|
if (empty($domain) && isset($requestData['additional_fields']['wc-licensed-product/domain'])) {
|
|
|
|
|
$sanitized = sanitize_text_field($requestData['additional_fields']['wc-licensed-product/domain']);
|
|
|
|
|
$domain = $this->licenseManager->normalizeDomain($sanitized);
|
2026-01-21 21:58:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!empty($domain)) {
|
|
|
|
|
$order->update_meta_data('_licensed_product_domain', $domain);
|
|
|
|
|
$order->save();
|
|
|
|
|
|
|
|
|
|
// Clear session data
|
|
|
|
|
if (WC()->session) {
|
|
|
|
|
WC()->session->set('licensed_product_domain', '');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-25 18:31:36 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process order in multi-domain mode
|
|
|
|
|
*/
|
|
|
|
|
private function processMultiDomainOrder(\WC_Order $order, ?array $requestData): void
|
|
|
|
|
{
|
|
|
|
|
$domainData = [];
|
|
|
|
|
|
|
|
|
|
// Check session first
|
|
|
|
|
if (WC()->session) {
|
|
|
|
|
$domainData = WC()->session->get('licensed_product_domains', []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check in the request data for block checkout (extension data)
|
|
|
|
|
if (empty($domainData) && isset($requestData['extensions'][self::IDENTIFIER]['licensed_product_domains'])) {
|
|
|
|
|
$domainData = $this->normalizeDomainsData(
|
|
|
|
|
$requestData['extensions'][self::IDENTIFIER]['licensed_product_domains']
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for wclp_license_domains (from our hidden input - JSON string)
|
|
|
|
|
if (empty($domainData) && isset($requestData['wclp_license_domains'])) {
|
|
|
|
|
$parsed = json_decode($requestData['wclp_license_domains'], true);
|
|
|
|
|
if (is_array($parsed)) {
|
|
|
|
|
$domainData = $this->normalizeDomainsData($parsed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for licensed_domains in classic format (from DOM injection)
|
|
|
|
|
if (empty($domainData) && isset($requestData['licensed_domains']) && is_array($requestData['licensed_domains'])) {
|
|
|
|
|
$domainData = [];
|
2026-01-26 16:14:15 +01:00
|
|
|
$variationIds = $requestData['licensed_variation_ids'] ?? [];
|
|
|
|
|
|
|
|
|
|
foreach ($requestData['licensed_domains'] as $key => $domains) {
|
2026-01-25 18:31:36 +01:00
|
|
|
if (!is_array($domains)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-01-26 16:14:15 +01:00
|
|
|
|
|
|
|
|
// Parse key - could be "productId" or "productId_variationId"
|
|
|
|
|
$parts = explode('_', (string) $key);
|
|
|
|
|
$productId = (int) $parts[0];
|
|
|
|
|
$variationId = isset($parts[1]) ? (int) $parts[1] : 0;
|
|
|
|
|
|
|
|
|
|
// Also check for hidden variation ID field
|
|
|
|
|
if ($variationId === 0 && isset($variationIds[$key])) {
|
|
|
|
|
$variationId = (int) $variationIds[$key];
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 18:31:36 +01:00
|
|
|
$normalizedDomains = [];
|
|
|
|
|
foreach ($domains as $domain) {
|
|
|
|
|
$sanitized = sanitize_text_field($domain);
|
|
|
|
|
if (!empty($sanitized)) {
|
|
|
|
|
$normalizedDomains[] = $this->licenseManager->normalizeDomain($sanitized);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!empty($normalizedDomains)) {
|
2026-01-26 16:14:15 +01:00
|
|
|
$entry = [
|
|
|
|
|
'product_id' => $productId,
|
2026-01-25 18:31:36 +01:00
|
|
|
'domains' => $normalizedDomains,
|
|
|
|
|
];
|
2026-01-26 16:14:15 +01:00
|
|
|
|
|
|
|
|
if ($variationId > 0) {
|
|
|
|
|
$entry['variation_id'] = $variationId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$domainData[] = $entry;
|
2026-01-25 18:31:36 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!empty($domainData)) {
|
|
|
|
|
$order->update_meta_data('_licensed_product_domains', $domainData);
|
|
|
|
|
$order->save();
|
|
|
|
|
|
|
|
|
|
// Clear session data
|
|
|
|
|
if (WC()->session) {
|
|
|
|
|
WC()->session->set('licensed_product_domains', []);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-21 21:58:54 +01:00
|
|
|
}
|