feat: Affiliate program enrichment (Link Builder, Curated Collections, Smart Links)

This commit is contained in:
Dwindi Ramadhana
2026-06-03 14:04:17 +07:00
parent fd8eb38512
commit f8c733832e
22 changed files with 1348 additions and 10 deletions

View File

@@ -243,6 +243,19 @@ class Assets
$base_path = '';
}
// Also force empty base path for /collection/ routes since they are global
$spa_path_var = get_query_var('woonoow_spa_path');
if (!empty($spa_path_var) && strpos($spa_path_var, 'collection/') === 0) {
$base_path = '';
}
// Handle serve_spa_for_frontpage_routes which bypasses WP queries
$request_uri = $_SERVER['REQUEST_URI'] ?? '/';
$path = parse_url($request_uri, PHP_URL_PATH);
if (strpos($path, '/collection/') === 0) {
$base_path = '';
}
// Check if BrowserRouter is enabled (default: true for SEO)
$use_browser_router = $appearance_settings['general']['use_browser_router'] ?? true;
@@ -263,6 +276,9 @@ class Assets
'useBrowserRouter' => $use_browser_router,
'frontPageSlug' => $front_page_slug,
'spaMode' => $appearance_settings['general']['spa_mode'] ?? 'full',
'affiliateSettings' => [
'enableCuratedCollections' => class_exists('\WooNooW\Modules\Affiliate\AffiliateSettings') ? \WooNooW\Modules\Affiliate\AffiliateSettings::get_setting('woonoow_affiliate_enable_curated_collections', true) : false,
],
'security' => \WooNooW\Compat\SecuritySettingsProvider::get_public_settings(),
];
@@ -460,7 +476,7 @@ class Assets
}
// Check path prefixes
$prefix_routes = ['/shop/', '/my-account/', '/product/'];
$prefix_routes = ['/shop/', '/my-account/', '/product/', '/collection/'];
foreach ($prefix_routes as $prefix) {
if (strpos($path, $prefix) === 0) {
return true;

View File

@@ -98,6 +98,13 @@ class ShopController
],
],
]);
// Get affiliate collection (public)
register_rest_route($namespace, '/shop/collections/(?P<slug>[a-zA-Z0-9-]+)', [
'methods' => 'GET',
'callback' => [__CLASS__, 'get_collection'],
'permission_callback' => '__return_true',
]);
}
/**
@@ -290,6 +297,57 @@ class ShopController
return new WP_REST_Response($products, 200);
}
/**
* Get affiliate collection by slug
*/
public static function get_collection(WP_REST_Request $request)
{
global $wpdb;
$slug = sanitize_title($request->get_param('slug'));
$collections_table = $wpdb->prefix . 'woonoow_affiliate_collections';
$collection = $wpdb->get_row($wpdb->prepare("SELECT * FROM $collections_table WHERE slug = %s", $slug));
if (!$collection) {
return new WP_Error('not_found', 'Collection not found', ['status' => 404]);
}
$product_ids = $collection->product_ids ? json_decode($collection->product_ids, true) : [];
if (!is_array($product_ids)) $product_ids = [];
$products = [];
if (!empty($product_ids)) {
$args = [
'post_type' => 'product',
'post_status' => 'publish',
'posts_per_page' => -1,
'post__in' => $product_ids,
'orderby' => 'post__in',
];
$query = new \WP_Query($args);
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
$product = wc_get_product(get_the_ID());
if ($product) {
$products[] = self::format_product($product);
}
}
wp_reset_postdata();
}
}
$data = [
'id' => $collection->id,
'title' => $collection->title,
'description' => $collection->description,
'products' => $products
];
return new WP_REST_Response($data, 200);
}
/**
* Format product data for API response
*/

View File

@@ -0,0 +1,83 @@
<?php
namespace WooNooW\Frontend;
if (!defined('ABSPATH')) exit;
class SmartRotator
{
public static function init()
{
add_action('init', [__CLASS__, 'register_rewrite_rules']);
add_filter('query_vars', [__CLASS__, 'register_query_vars']);
add_action('template_redirect', [__CLASS__, 'handle_redirect']);
}
public static function register_rewrite_rules()
{
// Match /go/collection-slug
add_rewrite_rule(
'^go/([^/]+)/?$',
'index.php?woonoow_go_slug=$matches[1]',
'top'
);
}
public static function register_query_vars($vars)
{
$vars[] = 'woonoow_go_slug';
return $vars;
}
public static function handle_redirect()
{
$slug = get_query_var('woonoow_go_slug');
if (empty($slug)) {
return;
}
global $wpdb;
$collections_table = $wpdb->prefix . 'woonoow_affiliate_collections';
$affiliates_table = $wpdb->prefix . 'woonoow_affiliates';
// Lookup collection and affiliate in one query
$query = $wpdb->prepare(
"SELECT c.product_ids, a.referral_code
FROM {$collections_table} c
JOIN {$affiliates_table} a ON c.affiliate_id = a.id
WHERE c.slug = %s LIMIT 1",
$slug
);
$result = $wpdb->get_row($query);
if (!$result || empty($result->product_ids)) {
// Fallback: 404 or redirect to shop
wp_safe_redirect(site_url('/shop/'));
exit;
}
$product_ids = json_decode($result->product_ids, true);
if (!is_array($product_ids) || empty($product_ids)) {
wp_safe_redirect(site_url('/shop/'));
exit;
}
// Randomly pick a product
$random_product_id = $product_ids[array_rand($product_ids)];
// Get the permalink for the product
$target_url = get_permalink($random_product_id);
if (!$target_url) {
wp_safe_redirect(site_url('/shop/'));
exit;
}
// Append the affiliate referral code
$target_url = add_query_arg('ref', $result->referral_code, $target_url);
// Redirect to the product page
wp_redirect($target_url, 302);
exit;
}
}

View File

@@ -211,6 +211,13 @@ class TemplateOverride
);
}
// /collection/* → SPA page (global, independent of frontpage setting)
add_rewrite_rule(
'^collection/(.*)$',
'index.php?page_id=' . $spa_page_id . '&woonoow_spa_path=collection/$matches[1]',
'top'
);
// Register query var for the SPA path
add_filter('query_vars', function ($vars) {
$vars[] = 'woonoow_spa_path';
@@ -430,7 +437,7 @@ class TemplateOverride
}
// Check path prefixes (for sub-routes)
$prefix_routes = ['/shop/', '/my-account/', '/product/', '/checkout/'];
$prefix_routes = ['/shop/', '/my-account/', '/product/', '/checkout/', '/collection/'];
foreach ($prefix_routes as $prefix) {
if (strpos($path, $prefix) === 0) {
$should_serve_spa = true;
@@ -500,7 +507,7 @@ class TemplateOverride
// Check if this is a SPA route
// We include /product/ and standard endpoints
$spa_routes = ['/product/', '/cart', '/checkout', '/my-account'];
$spa_routes = ['/product/', '/cart', '/checkout', '/my-account', '/go/'];
foreach ($spa_routes as $route) {
if (strpos($requested_url, $route) !== false) {