fix: WP-Admin CSS conflicts and add-to-cart redirect
- Fix CSS conflicts between WP-Admin and SPA (radio buttons, chart text) - Add Tailwind important selector scoped to #woonoow-admin-app - Remove overly aggressive inline SVG styles from Assets.php - Add targeted WordPress admin CSS overrides in index.css - Fix add-to-cart redirect to use woocommerce_add_to_cart_redirect filter - Let WooCommerce handle cart operations natively for proper session management - Remove duplicate tailwind.config.cjs
This commit is contained in:
@@ -6,17 +6,20 @@ use WooNooW\Compat\AddonRegistry;
|
||||
use WooNooW\Compat\RouteRegistry;
|
||||
use WooNooW\Compat\NavigationRegistry;
|
||||
|
||||
class Assets {
|
||||
public static function init() {
|
||||
class Assets
|
||||
{
|
||||
public static function init()
|
||||
{
|
||||
add_action('admin_enqueue_scripts', [__CLASS__, 'enqueue']);
|
||||
}
|
||||
|
||||
public static function enqueue($hook) {
|
||||
public static function enqueue($hook)
|
||||
{
|
||||
// Debug logging
|
||||
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||
error_log('[WooNooW Assets] Hook: ' . $hook);
|
||||
}
|
||||
|
||||
|
||||
if ($hook !== 'toplevel_page_woonoow') {
|
||||
return;
|
||||
}
|
||||
@@ -26,12 +29,12 @@ class Assets {
|
||||
|
||||
// Decide dev vs prod
|
||||
$is_dev = self::is_dev_mode();
|
||||
|
||||
|
||||
// Debug logging
|
||||
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||
error_log('[WooNooW Assets] Dev mode: ' . ($is_dev ? 'true' : 'false'));
|
||||
}
|
||||
|
||||
|
||||
if ($is_dev) {
|
||||
self::enqueue_dev();
|
||||
} else {
|
||||
@@ -42,49 +45,50 @@ class Assets {
|
||||
/** ----------------------------------------
|
||||
* DEV MODE (Vite dev server)
|
||||
* -------------------------------------- */
|
||||
private static function enqueue_dev(): void {
|
||||
private static function enqueue_dev(): void
|
||||
{
|
||||
$dev_url = self::dev_server_url(); // e.g. http://localhost:5173
|
||||
|
||||
|
||||
// 1) Create a small handle to attach config (window.WNW_API)
|
||||
$handle = 'wnw-admin-dev-config';
|
||||
wp_register_script($handle, '', [], null, true);
|
||||
wp_enqueue_script($handle);
|
||||
|
||||
|
||||
// Attach runtime config (before module loader runs)
|
||||
// If you prefer, keep using self::localize_runtime($handle)
|
||||
wp_localize_script($handle, 'WNW_API', [
|
||||
'root' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'isDev' => true,
|
||||
'devServer' => $dev_url,
|
||||
'root' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'isDev' => true,
|
||||
'devServer' => $dev_url,
|
||||
'adminScreen' => 'woonoow',
|
||||
'adminUrl' => admin_url('admin.php'),
|
||||
'adminUrl' => admin_url('admin.php'),
|
||||
]);
|
||||
wp_add_inline_script($handle, 'window.WNW_API = window.WNW_API || WNW_API;', 'after');
|
||||
|
||||
|
||||
// WNW_CONFIG for compatibility with standalone mode code
|
||||
wp_localize_script($handle, 'WNW_CONFIG', [
|
||||
'restUrl' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'standaloneMode' => false,
|
||||
'wpAdminUrl' => admin_url('admin.php?page=woonoow'),
|
||||
'restUrl' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'standaloneMode' => false,
|
||||
'wpAdminUrl' => admin_url('admin.php?page=woonoow'),
|
||||
'isAuthenticated' => is_user_logged_in(),
|
||||
'pluginUrl' => trailingslashit(plugins_url('/', dirname(__DIR__))),
|
||||
'pluginUrl' => trailingslashit(plugins_url('/', dirname(__DIR__))),
|
||||
]);
|
||||
wp_add_inline_script($handle, 'window.WNW_CONFIG = window.WNW_CONFIG || WNW_CONFIG;', 'after');
|
||||
|
||||
|
||||
// WordPress REST API settings (for media upload compatibility)
|
||||
wp_localize_script($handle, 'wpApiSettings', [
|
||||
'root' => untrailingslashit(esc_url_raw(rest_url())),
|
||||
'root' => untrailingslashit(esc_url_raw(rest_url())),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
]);
|
||||
wp_add_inline_script($handle, 'window.wpApiSettings = window.wpApiSettings || wpApiSettings;', 'after');
|
||||
|
||||
// Also expose compact global for convenience
|
||||
wp_localize_script($handle, 'wnw', [
|
||||
'isDev' => true,
|
||||
'isDev' => true,
|
||||
'devServer' => $dev_url,
|
||||
'adminUrl' => admin_url('admin.php'),
|
||||
'adminUrl' => admin_url('admin.php'),
|
||||
'siteTitle' => get_bloginfo('name') ?: 'WooNooW',
|
||||
]);
|
||||
wp_add_inline_script($handle, 'window.wnw = window.wnw || wnw;', 'after');
|
||||
@@ -97,37 +101,37 @@ class Assets {
|
||||
$menus_snapshot = class_exists(MenuProvider::class) ? MenuProvider::get_snapshot() : [];
|
||||
wp_localize_script($handle, 'WNW_WC_MENUS', ['items' => $menus_snapshot]);
|
||||
wp_add_inline_script($handle, 'window.WNW_WC_MENUS = window.WNW_WC_MENUS || WNW_WC_MENUS;', 'after');
|
||||
|
||||
|
||||
// Addon system data
|
||||
wp_localize_script($handle, 'WNW_ADDONS', AddonRegistry::get_frontend_registry());
|
||||
wp_add_inline_script($handle, 'window.WNW_ADDONS = window.WNW_ADDONS || WNW_ADDONS;', 'after');
|
||||
|
||||
|
||||
wp_localize_script($handle, 'WNW_ADDON_ROUTES', RouteRegistry::get_frontend_routes());
|
||||
wp_add_inline_script($handle, 'window.WNW_ADDON_ROUTES = window.WNW_ADDON_ROUTES || WNW_ADDON_ROUTES;', 'after');
|
||||
|
||||
|
||||
wp_localize_script($handle, 'WNW_NAV_TREE', NavigationRegistry::get_frontend_nav_tree());
|
||||
wp_add_inline_script($handle, 'window.WNW_NAV_TREE = window.WNW_NAV_TREE || WNW_NAV_TREE;', 'after');
|
||||
|
||||
|
||||
// Temporary compat aliases for old WNM_*
|
||||
wp_add_inline_script($handle, 'window.WNM_API = window.WNM_API || window.WNW_API;', 'after');
|
||||
wp_add_inline_script($handle, 'window.WNM_WC_MENUS = window.WNM_WC_MENUS || window.WNW_WC_MENUS;', 'after');
|
||||
|
||||
|
||||
// 2) Print a real module tag in the footer to load Vite client + app
|
||||
add_action('admin_print_footer_scripts', function () use ($dev_url) {
|
||||
// 1) React Refresh preamble (required by @vitejs/plugin-react)
|
||||
?>
|
||||
<script type="module">
|
||||
import RefreshRuntime from "<?php echo esc_url( $dev_url ); ?>/@react-refresh";
|
||||
RefreshRuntime.injectIntoGlobalHook(window);
|
||||
window.$RefreshReg$ = () => {};
|
||||
window.$RefreshSig$ = () => (type) => type;
|
||||
window.__vite_plugin_react_preamble_installed__ = true;
|
||||
import RefreshRuntime from "<?php echo esc_url($dev_url); ?>/@react-refresh";
|
||||
RefreshRuntime.injectIntoGlobalHook(window);
|
||||
window.$RefreshReg$ = () => { };
|
||||
window.$RefreshSig$ = () => (type) => type;
|
||||
window.__vite_plugin_react_preamble_installed__ = true;
|
||||
</script>
|
||||
<?php
|
||||
|
||||
|
||||
// 2) Vite client (HMR)
|
||||
printf('<script type="module" src="%s/@vite/client"></script>' . "\n", esc_url($dev_url));
|
||||
|
||||
|
||||
// 3) Your app entry
|
||||
printf('<script type="module">import "%s/src/main.tsx";</script>' . "\n", esc_url($dev_url));
|
||||
}, 1);
|
||||
@@ -136,17 +140,18 @@ class Assets {
|
||||
/** ----------------------------------------
|
||||
* PROD MODE (built assets in admin-spa/dist)
|
||||
* -------------------------------------- */
|
||||
private static function enqueue_prod(): void {
|
||||
private static function enqueue_prod(): void
|
||||
{
|
||||
// Get plugin root directory (2 levels up from includes/Admin/)
|
||||
$plugin_dir = dirname(dirname(__DIR__));
|
||||
$dist_dir = $plugin_dir . '/admin-spa/dist/';
|
||||
$base_url = plugins_url('admin-spa/dist/', $plugin_dir . '/woonoow.php');
|
||||
|
||||
$css = 'app.css';
|
||||
$js = 'app.js';
|
||||
$js = 'app.js';
|
||||
|
||||
$ver_css = file_exists($dist_dir . $css) ? (string) filemtime($dist_dir . $css) : self::asset_version();
|
||||
$ver_js = file_exists($dist_dir . $js) ? (string) filemtime($dist_dir . $js) : self::asset_version();
|
||||
$ver_js = file_exists($dist_dir . $js) ? (string) filemtime($dist_dir . $js) : self::asset_version();
|
||||
|
||||
// Debug logging
|
||||
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||
@@ -159,71 +164,60 @@ class Assets {
|
||||
|
||||
if (file_exists($dist_dir . $css)) {
|
||||
wp_enqueue_style('wnw-admin', $base_url . $css, [], $ver_css);
|
||||
|
||||
// Fix icon rendering in WP-Admin (prevent WordPress admin styles from overriding)
|
||||
$icon_fix_css = '
|
||||
/* Fix Lucide icons in WP-Admin - force outlined style */
|
||||
#woonoow-admin-app svg {
|
||||
fill: none !important;
|
||||
stroke: currentColor !important;
|
||||
stroke-width: 2 !important;
|
||||
stroke-linecap: round !important;
|
||||
stroke-linejoin: round !important;
|
||||
}
|
||||
';
|
||||
wp_add_inline_style('wnw-admin', $icon_fix_css);
|
||||
// Note: Icon fixes are now in index.css with proper specificity
|
||||
}
|
||||
|
||||
if (file_exists($dist_dir . $js)) {
|
||||
wp_enqueue_script('wnw-admin', $base_url . $js, ['wp-element'], $ver_js, true);
|
||||
|
||||
|
||||
// Add type="module" attribute for Vite build
|
||||
add_filter('script_loader_tag', function($tag, $handle, $src) {
|
||||
add_filter('script_loader_tag', function ($tag, $handle, $src) {
|
||||
if ($handle === 'wnw-admin') {
|
||||
$tag = str_replace('<script ', '<script type="module" ', $tag);
|
||||
}
|
||||
return $tag;
|
||||
}, 10, 3);
|
||||
|
||||
|
||||
self::localize_runtime('wnw-admin');
|
||||
}
|
||||
}
|
||||
|
||||
/** Attach runtime config to a handle */
|
||||
private static function localize_runtime(string $handle): void {
|
||||
private static function localize_runtime(string $handle): void
|
||||
{
|
||||
wp_localize_script($handle, 'WNW_API', [
|
||||
'root' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'isDev' => self::is_dev_mode(),
|
||||
'devServer' => self::dev_server_url(),
|
||||
'root' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'isDev' => self::is_dev_mode(),
|
||||
'devServer' => self::dev_server_url(),
|
||||
'adminScreen' => 'woonoow',
|
||||
'adminUrl' => admin_url('admin.php'),
|
||||
'adminUrl' => admin_url('admin.php'),
|
||||
]);
|
||||
|
||||
|
||||
// WNW_CONFIG for compatibility with standalone mode code
|
||||
wp_localize_script($handle, 'WNW_CONFIG', [
|
||||
'restUrl' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'standaloneMode' => false,
|
||||
'wpAdminUrl' => admin_url('admin.php?page=woonoow'),
|
||||
'restUrl' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'standaloneMode' => false,
|
||||
'wpAdminUrl' => admin_url('admin.php?page=woonoow'),
|
||||
'isAuthenticated' => is_user_logged_in(),
|
||||
'pluginUrl' => trailingslashit(plugins_url('/', dirname(__DIR__))),
|
||||
'pluginUrl' => trailingslashit(plugins_url('/', dirname(__DIR__))),
|
||||
]);
|
||||
|
||||
|
||||
// WordPress REST API settings (for media upload compatibility)
|
||||
wp_localize_script($handle, 'wpApiSettings', [
|
||||
'root' => untrailingslashit(esc_url_raw(rest_url())),
|
||||
'root' => untrailingslashit(esc_url_raw(rest_url())),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
]);
|
||||
|
||||
|
||||
wp_localize_script($handle, 'WNW_STORE', self::store_runtime());
|
||||
wp_add_inline_script($handle, 'window.WNW_STORE = window.WNW_STORE || WNW_STORE;', 'after');
|
||||
|
||||
// Compact global (prod)
|
||||
wp_localize_script($handle, 'wnw', [
|
||||
'isDev' => (bool) self::is_dev_mode(),
|
||||
'isDev' => (bool) self::is_dev_mode(),
|
||||
'devServer' => (string) self::dev_server_url(),
|
||||
'adminUrl' => admin_url('admin.php'),
|
||||
'adminUrl' => admin_url('admin.php'),
|
||||
'siteTitle' => get_bloginfo('name') ?: 'WooNooW',
|
||||
]);
|
||||
wp_add_inline_script($handle, 'window.wnw = window.wnw || wnw;', 'after');
|
||||
@@ -232,39 +226,40 @@ class Assets {
|
||||
$menus_snapshot = class_exists(MenuProvider::class) ? MenuProvider::get_snapshot() : [];
|
||||
wp_localize_script($handle, 'WNW_WC_MENUS', ['items' => $menus_snapshot]);
|
||||
wp_add_inline_script($handle, 'window.WNW_WC_MENUS = window.WNW_WC_MENUS || WNW_WC_MENUS;', 'after');
|
||||
|
||||
|
||||
// Addon system data (prod)
|
||||
wp_localize_script($handle, 'WNW_ADDONS', AddonRegistry::get_frontend_registry());
|
||||
wp_add_inline_script($handle, 'window.WNW_ADDONS = window.WNW_ADDONS || WNW_ADDONS;', 'after');
|
||||
|
||||
|
||||
wp_localize_script($handle, 'WNW_ADDON_ROUTES', RouteRegistry::get_frontend_routes());
|
||||
wp_add_inline_script($handle, 'window.WNW_ADDON_ROUTES = window.WNW_ADDON_ROUTES || WNW_ADDON_ROUTES;', 'after');
|
||||
|
||||
|
||||
wp_localize_script($handle, 'WNW_NAV_TREE', NavigationRegistry::get_frontend_nav_tree());
|
||||
wp_add_inline_script($handle, 'window.WNW_NAV_TREE = window.WNW_NAV_TREE || WNW_NAV_TREE;', 'after');
|
||||
|
||||
|
||||
// Temporary compat aliases for old WNM_*
|
||||
wp_add_inline_script($handle, 'window.WNM_API = window.WNM_API || window.WNW_API;', 'after');
|
||||
wp_add_inline_script($handle, 'window.WNM_WC_MENUS = window.WNM_WC_MENUS || window.WNW_WC_MENUS;', 'after');
|
||||
}
|
||||
|
||||
/** Runtime store meta for frontend (currency, decimals, separators, position). */
|
||||
private static function store_runtime(): array {
|
||||
private static function store_runtime(): array
|
||||
{
|
||||
// WooCommerce helpers may not exist in some contexts; guard with defaults
|
||||
$currency = function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : 'USD';
|
||||
$currency_sym = function_exists('get_woocommerce_currency_symbol') ? get_woocommerce_currency_symbol($currency) : '$';
|
||||
$decimals = function_exists('wc_get_price_decimals') ? wc_get_price_decimals() : 2;
|
||||
$thousand_sep = function_exists('wc_get_price_thousand_separator') ? wc_get_price_thousand_separator() : ',';
|
||||
$decimal_sep = function_exists('wc_get_price_decimal_separator') ? wc_get_price_decimal_separator() : '.';
|
||||
$currency_pos = function_exists('get_option') ? get_option('woocommerce_currency_pos', 'left') : 'left';
|
||||
$currency = function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : 'USD';
|
||||
$currency_sym = function_exists('get_woocommerce_currency_symbol') ? get_woocommerce_currency_symbol($currency) : '$';
|
||||
$decimals = function_exists('wc_get_price_decimals') ? wc_get_price_decimals() : 2;
|
||||
$thousand_sep = function_exists('wc_get_price_thousand_separator') ? wc_get_price_thousand_separator() : ',';
|
||||
$decimal_sep = function_exists('wc_get_price_decimal_separator') ? wc_get_price_decimal_separator() : '.';
|
||||
$currency_pos = function_exists('get_option') ? get_option('woocommerce_currency_pos', 'left') : 'left';
|
||||
|
||||
return [
|
||||
'currency' => $currency,
|
||||
'currency_symbol' => $currency_sym,
|
||||
'decimals' => (int) $decimals,
|
||||
'thousand_sep' => (string) $thousand_sep,
|
||||
'decimal_sep' => (string) $decimal_sep,
|
||||
'currency_pos' => (string) $currency_pos,
|
||||
'currency' => $currency,
|
||||
'currency_symbol' => $currency_sym,
|
||||
'decimals' => (int) $decimals,
|
||||
'thousand_sep' => (string) $thousand_sep,
|
||||
'decimal_sep' => (string) $decimal_sep,
|
||||
'currency_pos' => (string) $currency_pos,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -275,9 +270,10 @@ class Assets {
|
||||
* Note: We don't check WP_ENV to avoid accidentally enabling dev mode
|
||||
* in Local by Flywheel or other local dev environments.
|
||||
*/
|
||||
private static function is_dev_mode(): bool {
|
||||
private static function is_dev_mode(): bool
|
||||
{
|
||||
// Only enable dev mode if explicitly set via constant
|
||||
$const_dev = defined('WOONOOW_ADMIN_DEV') && WOONOOW_ADMIN_DEV === true;
|
||||
$const_dev = defined('WOONOOW_ADMIN_DEV') && WOONOOW_ADMIN_DEV === true;
|
||||
|
||||
/**
|
||||
* Filter: force dev/prod mode for WooNooW admin assets.
|
||||
@@ -287,34 +283,36 @@ class Assets {
|
||||
* Only use it during development.
|
||||
*/
|
||||
$filtered = apply_filters('woonoow/admin_is_dev', $const_dev);
|
||||
|
||||
|
||||
// Debug logging (only if WP_DEBUG is enabled)
|
||||
if (defined('WP_DEBUG') && WP_DEBUG && $filtered !== $const_dev) {
|
||||
error_log('[WooNooW Assets] Dev mode changed by filter: ' . ($filtered ? 'true' : 'false'));
|
||||
}
|
||||
|
||||
|
||||
return (bool) $filtered;
|
||||
}
|
||||
|
||||
/** Dev server URL (filterable) */
|
||||
private static function dev_server_url(): string {
|
||||
private static function dev_server_url(): string
|
||||
{
|
||||
// Auto-detect based on current host (for Local by Flywheel compatibility)
|
||||
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||
$protocol = is_ssl() ? 'https' : 'http';
|
||||
|
||||
|
||||
// If using *.local domain (Local by Flywheel), use HTTPS
|
||||
if (strpos($host, '.local') !== false) {
|
||||
$protocol = 'https';
|
||||
}
|
||||
|
||||
|
||||
$default = $protocol . '://' . $host . ':5173';
|
||||
|
||||
|
||||
/** Filter: change dev server URL if needed */
|
||||
return (string) apply_filters('woonoow/admin_dev_server', $default);
|
||||
}
|
||||
|
||||
/** Basic asset versioning */
|
||||
private static function asset_version(): string {
|
||||
private static function asset_version(): string
|
||||
{
|
||||
// Bump when releasing; in dev we don't cache-bust
|
||||
return defined('WOONOOW_VERSION') ? WOONOOW_VERSION : '0.1.0';
|
||||
}
|
||||
|
||||
@@ -38,11 +38,6 @@ class Permissions {
|
||||
$has_wc = current_user_can('manage_woocommerce');
|
||||
$has_opts = current_user_can('manage_options');
|
||||
$result = $has_wc || $has_opts;
|
||||
error_log(sprintf('WooNooW Permissions: check_admin_permission() - WC:%s Options:%s Result:%s',
|
||||
$has_wc ? 'YES' : 'NO',
|
||||
$has_opts ? 'YES' : 'NO',
|
||||
$result ? 'ALLOWED' : 'DENIED'
|
||||
));
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -447,6 +447,7 @@ class ProductsController {
|
||||
if (isset($data['description'])) $product->set_description(self::sanitize_textarea($data['description']));
|
||||
if (isset($data['short_description'])) $product->set_short_description(self::sanitize_textarea($data['short_description']));
|
||||
if (isset($data['sku'])) $product->set_sku(self::sanitize_text($data['sku']));
|
||||
|
||||
if (isset($data['regular_price'])) $product->set_regular_price(self::sanitize_number($data['regular_price']));
|
||||
if (isset($data['sale_price'])) $product->set_sale_price(self::sanitize_number($data['sale_price']));
|
||||
|
||||
@@ -800,15 +801,18 @@ class ProductsController {
|
||||
$value = $term ? $term->name : $value;
|
||||
}
|
||||
} else {
|
||||
// Custom attribute - WooCommerce stores as 'attribute_' + exact attribute name
|
||||
$meta_key = 'attribute_' . $attr_name;
|
||||
// Custom attribute - stored as lowercase in meta
|
||||
$meta_key = 'attribute_' . strtolower($attr_name);
|
||||
$value = get_post_meta($variation_id, $meta_key, true);
|
||||
|
||||
// Capitalize the attribute name for display
|
||||
// Capitalize the attribute name for display to match admin SPA
|
||||
$clean_name = ucfirst($attr_name);
|
||||
}
|
||||
|
||||
$formatted_attributes[$clean_name] = $value;
|
||||
// Only add if value exists
|
||||
if (!empty($value)) {
|
||||
$formatted_attributes[$clean_name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$image_url = $image ? $image[0] : '';
|
||||
@@ -857,36 +861,106 @@ class ProductsController {
|
||||
* Save product variations
|
||||
*/
|
||||
private static function save_product_variations($product, $variations_data) {
|
||||
// Get existing variation IDs
|
||||
$existing_variation_ids = $product->get_children();
|
||||
$variations_to_keep = [];
|
||||
|
||||
foreach ($variations_data as $var_data) {
|
||||
if (isset($var_data['id']) && $var_data['id']) {
|
||||
// Update existing variation
|
||||
$variation = wc_get_product($var_data['id']);
|
||||
if (!$variation) continue;
|
||||
$variations_to_keep[] = $var_data['id'];
|
||||
} else {
|
||||
// Create new variation
|
||||
$variation = new WC_Product_Variation();
|
||||
$variation->set_parent_id($product->get_id());
|
||||
}
|
||||
|
||||
if ($variation) {
|
||||
if (isset($var_data['sku'])) $variation->set_sku($var_data['sku']);
|
||||
if (isset($var_data['regular_price'])) $variation->set_regular_price($var_data['regular_price']);
|
||||
if (isset($var_data['sale_price'])) $variation->set_sale_price($var_data['sale_price']);
|
||||
if (isset($var_data['stock_status'])) $variation->set_stock_status($var_data['stock_status']);
|
||||
if (isset($var_data['manage_stock'])) $variation->set_manage_stock($var_data['manage_stock']);
|
||||
if (isset($var_data['stock_quantity'])) $variation->set_stock_quantity($var_data['stock_quantity']);
|
||||
if (isset($var_data['attributes'])) $variation->set_attributes($var_data['attributes']);
|
||||
// Build attributes array
|
||||
$wc_attributes = [];
|
||||
if (isset($var_data['attributes']) && is_array($var_data['attributes'])) {
|
||||
$parent_attributes = $product->get_attributes();
|
||||
|
||||
// Handle image - support both image_id and image URL
|
||||
if (isset($var_data['image']) && !empty($var_data['image'])) {
|
||||
$image_id = attachment_url_to_postid($var_data['image']);
|
||||
if ($image_id) {
|
||||
$variation->set_image_id($image_id);
|
||||
foreach ($var_data['attributes'] as $display_name => $value) {
|
||||
if (empty($value)) continue;
|
||||
|
||||
foreach ($parent_attributes as $attr_name => $parent_attr) {
|
||||
if (!$parent_attr->get_variation()) continue;
|
||||
if (strcasecmp($display_name, $attr_name) === 0 || strcasecmp($display_name, ucfirst($attr_name)) === 0) {
|
||||
$wc_attributes[strtolower($attr_name)] = strtolower($value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} elseif (isset($var_data['image_id'])) {
|
||||
$variation->set_image_id($var_data['image_id']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($wc_attributes)) {
|
||||
$variation->set_attributes($wc_attributes);
|
||||
}
|
||||
|
||||
if (isset($var_data['sku'])) $variation->set_sku($var_data['sku']);
|
||||
|
||||
// Set prices - if not provided, use parent's price as fallback
|
||||
if (isset($var_data['regular_price']) && $var_data['regular_price'] !== '') {
|
||||
$variation->set_regular_price($var_data['regular_price']);
|
||||
} elseif (!$variation->get_regular_price()) {
|
||||
// Fallback to parent price if variation has no price
|
||||
$parent_price = $product->get_regular_price();
|
||||
if ($parent_price) {
|
||||
$variation->set_regular_price($parent_price);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($var_data['sale_price']) && $var_data['sale_price'] !== '') {
|
||||
$variation->set_sale_price($var_data['sale_price']);
|
||||
}
|
||||
|
||||
if (isset($var_data['stock_status'])) $variation->set_stock_status($var_data['stock_status']);
|
||||
if (isset($var_data['manage_stock'])) $variation->set_manage_stock($var_data['manage_stock']);
|
||||
if (isset($var_data['stock_quantity'])) $variation->set_stock_quantity($var_data['stock_quantity']);
|
||||
|
||||
if (isset($var_data['image']) && !empty($var_data['image'])) {
|
||||
$image_id = attachment_url_to_postid($var_data['image']);
|
||||
if ($image_id) $variation->set_image_id($image_id);
|
||||
} elseif (isset($var_data['image_id'])) {
|
||||
$variation->set_image_id($var_data['image_id']);
|
||||
}
|
||||
|
||||
// Save variation first
|
||||
$saved_id = $variation->save();
|
||||
$variations_to_keep[] = $saved_id;
|
||||
|
||||
// Manually save attributes using direct database insert
|
||||
if (!empty($wc_attributes)) {
|
||||
global $wpdb;
|
||||
|
||||
$variation->save();
|
||||
foreach ($wc_attributes as $attr_name => $attr_value) {
|
||||
$meta_key = 'attribute_' . $attr_name;
|
||||
|
||||
$wpdb->delete(
|
||||
$wpdb->postmeta,
|
||||
['post_id' => $saved_id, 'meta_key' => $meta_key],
|
||||
['%d', '%s']
|
||||
);
|
||||
|
||||
$wpdb->insert(
|
||||
$wpdb->postmeta,
|
||||
[
|
||||
'post_id' => $saved_id,
|
||||
'meta_key' => $meta_key,
|
||||
'meta_value' => $attr_value
|
||||
],
|
||||
['%d', '%s', '%s']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete variations that are no longer in the list
|
||||
$variations_to_delete = array_diff($existing_variation_ids, $variations_to_keep);
|
||||
foreach ($variations_to_delete as $variation_id) {
|
||||
$variation_to_delete = wc_get_product($variation_id);
|
||||
if ($variation_to_delete) {
|
||||
$variation_to_delete->delete(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,5 +66,64 @@ class Bootstrap {
|
||||
MailQueue::init();
|
||||
WooEmailOverride::init();
|
||||
OrderStore::init();
|
||||
|
||||
// Initialize cart for REST API requests
|
||||
add_action('woocommerce_init', [self::class, 'init_cart_for_rest_api']);
|
||||
|
||||
// Load custom variation attributes for WooCommerce admin
|
||||
add_action('woocommerce_product_variation_object_read', [self::class, 'load_variation_attributes']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Properly initialize WooCommerce cart for REST API requests
|
||||
* This is the recommended approach per WooCommerce core team
|
||||
*/
|
||||
public static function init_cart_for_rest_api() {
|
||||
// Only load cart for REST API requests
|
||||
if (!WC()->is_rest_api_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load frontend includes (required for cart)
|
||||
WC()->frontend_includes();
|
||||
|
||||
// Load cart using WooCommerce's official method
|
||||
if (null === WC()->cart && function_exists('wc_load_cart')) {
|
||||
wc_load_cart();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load custom variation attributes from post meta for WooCommerce admin
|
||||
* This ensures WooCommerce's native admin displays custom attributes correctly
|
||||
*/
|
||||
public static function load_variation_attributes($variation) {
|
||||
if (!$variation instanceof \WC_Product_Variation) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parent = wc_get_product($variation->get_parent_id());
|
||||
if (!$parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
foreach ($parent->get_attributes() as $attr_name => $attribute) {
|
||||
if (!$attribute->get_variation()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read from post meta (stored as lowercase)
|
||||
$meta_key = 'attribute_' . strtolower($attr_name);
|
||||
$value = get_post_meta($variation->get_id(), $meta_key, true);
|
||||
|
||||
if (!empty($value)) {
|
||||
$attributes[strtolower($attr_name)] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($attributes)) {
|
||||
$variation->set_attributes($attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,15 +35,11 @@ class Assets {
|
||||
public static function enqueue_assets() {
|
||||
// Only load on pages with WooNooW shortcodes or in full SPA mode
|
||||
if (!self::should_load_assets()) {
|
||||
error_log('[WooNooW Customer] should_load_assets returned false - not loading');
|
||||
return;
|
||||
}
|
||||
|
||||
error_log('[WooNooW Customer] should_load_assets returned true - loading assets');
|
||||
|
||||
// Check if dev mode is enabled
|
||||
$is_dev = defined('WOONOOW_CUSTOMER_DEV') && WOONOOW_CUSTOMER_DEV;
|
||||
error_log('[WooNooW Customer] Dev mode: ' . ($is_dev ? 'true' : 'false'));
|
||||
|
||||
if ($is_dev) {
|
||||
// Dev mode: Load from Vite dev server
|
||||
@@ -66,9 +62,6 @@ class Assets {
|
||||
null,
|
||||
false // Load in header
|
||||
);
|
||||
|
||||
error_log('WooNooW Customer: Loading from Vite dev server at ' . $dev_server);
|
||||
error_log('WooNooW Customer: Scripts enqueued - vite client and main.tsx');
|
||||
} else {
|
||||
// Production mode: Load from build
|
||||
$plugin_url = plugin_dir_url(dirname(dirname(__FILE__)));
|
||||
@@ -76,7 +69,6 @@ class Assets {
|
||||
|
||||
// Check if build exists
|
||||
if (!file_exists($dist_path)) {
|
||||
error_log('WooNooW: customer-spa build not found. Run: cd customer-spa && npm run build');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,9 +76,6 @@ class Assets {
|
||||
$js_url = $plugin_url . 'customer-spa/dist/app.js';
|
||||
$css_url = $plugin_url . 'customer-spa/dist/app.css';
|
||||
|
||||
error_log('[WooNooW Customer] Enqueuing JS: ' . $js_url);
|
||||
error_log('[WooNooW Customer] Enqueuing CSS: ' . $css_url);
|
||||
|
||||
wp_enqueue_script(
|
||||
'woonoow-customer-spa',
|
||||
$js_url,
|
||||
@@ -109,8 +98,6 @@ class Assets {
|
||||
[],
|
||||
null
|
||||
);
|
||||
|
||||
error_log('[WooNooW Customer] Assets enqueued successfully');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +229,6 @@ class Assets {
|
||||
<script type="module" crossorigin src="<?php echo $dev_server; ?>/@vite/client"></script>
|
||||
<script type="module" crossorigin src="<?php echo $dev_server; ?>/src/main.tsx"></script>
|
||||
<?php
|
||||
error_log('WooNooW Customer: Scripts output directly in head with React Refresh preamble');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,11 +238,8 @@ class Assets {
|
||||
private static function should_load_assets() {
|
||||
global $post;
|
||||
|
||||
error_log('[WooNooW Customer] should_load_assets check - Post ID: ' . ($post ? $post->ID : 'none'));
|
||||
|
||||
// First check: Is this a designated SPA page?
|
||||
if (self::is_spa_page()) {
|
||||
error_log('[WooNooW Customer] Designated SPA page detected - loading assets');
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -264,8 +247,6 @@ class Assets {
|
||||
$spa_settings = get_option('woonoow_customer_spa_settings', []);
|
||||
$mode = isset($spa_settings['mode']) ? $spa_settings['mode'] : 'disabled';
|
||||
|
||||
error_log('[WooNooW Customer] SPA mode: ' . $mode);
|
||||
|
||||
// If disabled, don't load
|
||||
if ($mode === 'disabled') {
|
||||
// Special handling for WooCommerce Shop page (it's an archive, not a regular post)
|
||||
@@ -274,7 +255,6 @@ class Assets {
|
||||
if ($shop_page_id) {
|
||||
$shop_page = get_post($shop_page_id);
|
||||
if ($shop_page && has_shortcode($shop_page->post_content, 'woonoow_shop')) {
|
||||
error_log('[WooNooW Customer] Found woonoow_shop shortcode on Shop page (ID: ' . $shop_page_id . ')');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -282,27 +262,19 @@ class Assets {
|
||||
|
||||
// Check for shortcodes on regular pages
|
||||
if ($post) {
|
||||
error_log('[WooNooW Customer] Checking post content for shortcodes');
|
||||
error_log('[WooNooW Customer] Post content: ' . substr($post->post_content, 0, 200));
|
||||
|
||||
if (has_shortcode($post->post_content, 'woonoow_shop')) {
|
||||
error_log('[WooNooW Customer] Found woonoow_shop shortcode');
|
||||
return true;
|
||||
}
|
||||
if (has_shortcode($post->post_content, 'woonoow_cart')) {
|
||||
error_log('[WooNooW Customer] Found woonoow_cart shortcode');
|
||||
return true;
|
||||
}
|
||||
if (has_shortcode($post->post_content, 'woonoow_checkout')) {
|
||||
error_log('[WooNooW Customer] Found woonoow_checkout shortcode');
|
||||
return true;
|
||||
}
|
||||
if (has_shortcode($post->post_content, 'woonoow_account')) {
|
||||
error_log('[WooNooW Customer] Found woonoow_account shortcode');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
error_log('[WooNooW Customer] No shortcodes found, not loading');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,413 +9,465 @@ use WP_Error;
|
||||
* Cart Controller - Customer-facing cart API
|
||||
* Handles cart operations for customer-spa
|
||||
*/
|
||||
class CartController {
|
||||
|
||||
class CartController
|
||||
{
|
||||
|
||||
/**
|
||||
* Initialize controller
|
||||
*/
|
||||
public static function init() {
|
||||
public static function init()
|
||||
{
|
||||
// Bypass cookie authentication for cart endpoints to allow guest users
|
||||
add_filter('rest_authentication_errors', function($result) {
|
||||
add_filter('rest_authentication_errors', function ($result) {
|
||||
// If already authenticated or error, return as is
|
||||
if (!empty($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
// Check if this is a cart endpoint
|
||||
$request_uri = $_SERVER['REQUEST_URI'] ?? '';
|
||||
if (strpos($request_uri, '/woonoow/v1/cart') !== false) {
|
||||
return true; // Allow access
|
||||
}
|
||||
|
||||
|
||||
return $result;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register REST API routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
public static function register_routes()
|
||||
{
|
||||
$namespace = 'woonoow/v1';
|
||||
|
||||
|
||||
// Get cart
|
||||
$result = register_rest_route($namespace, '/cart', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_cart'],
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_cart'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
|
||||
// Add to cart
|
||||
$result = register_rest_route($namespace, '/cart/add', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'add_to_cart'],
|
||||
'permission_callback' => function() {
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'add_to_cart'],
|
||||
'permission_callback' => function () {
|
||||
// Allow both logged-in and guest users
|
||||
return true;
|
||||
},
|
||||
'args' => [
|
||||
'product_id' => [
|
||||
'required' => true,
|
||||
'validate_callback' => function($param) {
|
||||
'args' => [
|
||||
'product_id' => [
|
||||
'required' => true,
|
||||
'validate_callback' => function ($param) {
|
||||
return is_numeric($param);
|
||||
},
|
||||
],
|
||||
'quantity' => [
|
||||
'default' => 1,
|
||||
'quantity' => [
|
||||
'default' => 1,
|
||||
'sanitize_callback' => 'absint',
|
||||
],
|
||||
'variation_id' => [
|
||||
'default' => 0,
|
||||
'default' => 0,
|
||||
'sanitize_callback' => 'absint',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
// Update cart item
|
||||
register_rest_route($namespace, '/cart/update', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'update_cart'],
|
||||
'permission_callback' => function() { return true; },
|
||||
'args' => [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'update_cart'],
|
||||
'permission_callback' => function () {
|
||||
return true; },
|
||||
'args' => [
|
||||
'cart_item_key' => [
|
||||
'required' => true,
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
'quantity' => [
|
||||
'required' => true,
|
||||
'quantity' => [
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'absint',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
// Remove from cart
|
||||
register_rest_route($namespace, '/cart/remove', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'remove_from_cart'],
|
||||
'permission_callback' => function() { return true; },
|
||||
'args' => [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'remove_from_cart'],
|
||||
'permission_callback' => function () {
|
||||
return true; },
|
||||
'args' => [
|
||||
'cart_item_key' => [
|
||||
'required' => true,
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
// Apply coupon
|
||||
register_rest_route($namespace, '/cart/apply-coupon', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'apply_coupon'],
|
||||
'permission_callback' => function() { return true; },
|
||||
'args' => [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'apply_coupon'],
|
||||
'permission_callback' => function () {
|
||||
return true; },
|
||||
'args' => [
|
||||
'coupon_code' => [
|
||||
'required' => true,
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
// Clear cart
|
||||
register_rest_route($namespace, '/cart/clear', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'clear_cart'],
|
||||
'permission_callback' => function () {
|
||||
return true; },
|
||||
]);
|
||||
|
||||
// Remove coupon
|
||||
register_rest_route($namespace, '/cart/remove-coupon', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'remove_coupon'],
|
||||
'permission_callback' => function() { return true; },
|
||||
'args' => [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'remove_coupon'],
|
||||
'permission_callback' => function () {
|
||||
return true; },
|
||||
'args' => [
|
||||
'coupon_code' => [
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get cart contents
|
||||
*/
|
||||
public static function get_cart(WP_REST_Request $request) {
|
||||
if (!WC()->cart) {
|
||||
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
|
||||
public static function get_cart(WP_REST_Request $request)
|
||||
{
|
||||
// Initialize WooCommerce session and cart for REST API requests
|
||||
if (!WC()->session) {
|
||||
WC()->initialize_session();
|
||||
}
|
||||
|
||||
if (!WC()->cart) {
|
||||
WC()->initialize_cart();
|
||||
}
|
||||
// Set session cookie for guest users to persist cart
|
||||
if (!WC()->session->has_session()) {
|
||||
WC()->session->set_customer_session_cookie(true);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(self::format_cart(), 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add item to cart
|
||||
*/
|
||||
public static function add_to_cart(WP_REST_Request $request) {
|
||||
$product_id = $request->get_param('product_id');
|
||||
$quantity = $request->get_param('quantity');
|
||||
public static function add_to_cart(WP_REST_Request $request)
|
||||
{
|
||||
$product_id = $request->get_param('product_id');
|
||||
$quantity = $request->get_param('quantity') ?: 1; // Default to 1
|
||||
$variation_id = $request->get_param('variation_id');
|
||||
|
||||
error_log("WooNooW Cart: Adding product {$product_id} (variation: {$variation_id}) qty: {$quantity}");
|
||||
|
||||
// Check if WooCommerce is available
|
||||
if (!function_exists('WC')) {
|
||||
error_log('WooNooW Cart Error: WooCommerce not loaded');
|
||||
return new WP_Error('wc_not_loaded', 'WooCommerce is not loaded', ['status' => 500]);
|
||||
}
|
||||
|
||||
|
||||
// Initialize WooCommerce session and cart for REST API requests
|
||||
// WooCommerce doesn't auto-initialize these for REST API calls
|
||||
if (!WC()->session) {
|
||||
error_log('WooNooW Cart: Initializing WC session for REST API');
|
||||
WC()->initialize_session();
|
||||
}
|
||||
|
||||
if (!WC()->cart) {
|
||||
error_log('WooNooW Cart: Initializing WC cart for REST API');
|
||||
WC()->initialize_cart();
|
||||
}
|
||||
|
||||
// Set session cookie for guest users
|
||||
// CRITICAL: Set session cookie for guest users to persist cart
|
||||
if (!WC()->session->has_session()) {
|
||||
WC()->session->set_customer_session_cookie(true);
|
||||
error_log('WooNooW Cart: Session cookie set for guest user');
|
||||
}
|
||||
|
||||
error_log('WooNooW Cart: WC Session and Cart initialized successfully');
|
||||
|
||||
|
||||
// Validate product
|
||||
$product = wc_get_product($product_id);
|
||||
if (!$product) {
|
||||
error_log("WooNooW Cart Error: Product {$product_id} not found");
|
||||
return new WP_Error('invalid_product', 'Product not found', ['status' => 404]);
|
||||
}
|
||||
|
||||
error_log("WooNooW Cart: Product validated - {$product->get_name()} (Type: {$product->get_type()})");
|
||||
|
||||
// For variable products, validate the variation and get attributes
|
||||
|
||||
// For variable products, get attributes from request or variation
|
||||
$variation_attributes = [];
|
||||
if ($variation_id > 0) {
|
||||
$variation = wc_get_product($variation_id);
|
||||
if (!$variation) {
|
||||
error_log("WooNooW Cart Error: Variation {$variation_id} not found");
|
||||
return new WP_Error('invalid_variation', "Variation {$variation_id} not found", ['status' => 404]);
|
||||
return new WP_Error('invalid_variation', "Variation not found", ['status' => 404]);
|
||||
}
|
||||
|
||||
|
||||
if ($variation->get_parent_id() != $product_id) {
|
||||
error_log("WooNooW Cart Error: Variation {$variation_id} does not belong to product {$product_id}");
|
||||
return new WP_Error('invalid_variation', "Variation does not belong to this product", ['status' => 400]);
|
||||
}
|
||||
|
||||
if (!$variation->is_purchasable() || !$variation->is_in_stock()) {
|
||||
error_log("WooNooW Cart Error: Variation {$variation_id} is not purchasable or out of stock");
|
||||
return new WP_Error('variation_not_available', "This variation is not available for purchase", ['status' => 400]);
|
||||
|
||||
if (!$variation->is_in_stock()) {
|
||||
return new WP_Error('variation_not_available', "This variation is out of stock", ['status' => 400]);
|
||||
}
|
||||
|
||||
// Get variation attributes from post meta
|
||||
// WooCommerce stores variation attributes as post meta with 'attribute_' prefix
|
||||
$variation_attributes = [];
|
||||
|
||||
// Get parent product to know which attributes to look for
|
||||
$parent_product = wc_get_product($product_id);
|
||||
$parent_attributes = $parent_product->get_attributes();
|
||||
|
||||
error_log("WooNooW Cart: Parent product attributes: " . print_r(array_keys($parent_attributes), true));
|
||||
|
||||
// For each parent attribute, get the value from variation post meta
|
||||
foreach ($parent_attributes as $attribute) {
|
||||
if ($attribute->get_variation()) {
|
||||
$attribute_name = $attribute->get_name();
|
||||
$meta_key = 'attribute_' . $attribute_name;
|
||||
|
||||
// Get the value from post meta
|
||||
$attribute_value = get_post_meta($variation_id, $meta_key, true);
|
||||
|
||||
error_log("WooNooW Cart: Checking attribute {$attribute_name} (meta key: {$meta_key}): {$attribute_value}");
|
||||
|
||||
if (!empty($attribute_value)) {
|
||||
// WooCommerce expects lowercase attribute names
|
||||
$wc_attribute_key = 'attribute_' . strtolower($attribute_name);
|
||||
$variation_attributes[$wc_attribute_key] = $attribute_value;
|
||||
|
||||
// Build attributes from request parameters (like WooCommerce does)
|
||||
// Check for attribute_* parameters in the request
|
||||
$params = $request->get_params();
|
||||
foreach ($params as $key => $value) {
|
||||
if (strpos($key, 'attribute_') === 0) {
|
||||
$variation_attributes[sanitize_title($key)] = wc_clean($value);
|
||||
}
|
||||
}
|
||||
|
||||
// If no attributes in request, get from variation meta directly
|
||||
if (empty($variation_attributes)) {
|
||||
$parent = wc_get_product($product_id);
|
||||
foreach ($parent->get_attributes() as $attr_name => $attribute) {
|
||||
if (!$attribute->get_variation())
|
||||
continue;
|
||||
|
||||
$meta_key = 'attribute_' . $attr_name;
|
||||
$value = get_post_meta($variation_id, $meta_key, true);
|
||||
|
||||
if (!empty($value)) {
|
||||
$variation_attributes[$meta_key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error_log("WooNooW Cart: Variation validated - {$variation->get_name()}");
|
||||
error_log("WooNooW Cart: Variation attributes extracted: " . print_r($variation_attributes, true));
|
||||
}
|
||||
|
||||
|
||||
// Clear any existing notices before adding to cart
|
||||
wc_clear_notices();
|
||||
|
||||
|
||||
// Add to cart with variation attributes
|
||||
error_log("WooNooW Cart: Calling WC()->cart->add_to_cart({$product_id}, {$quantity}, {$variation_id}, attributes)");
|
||||
$cart_item_key = WC()->cart->add_to_cart($product_id, $quantity, $variation_id, $variation_attributes);
|
||||
|
||||
|
||||
if (!$cart_item_key) {
|
||||
// Get WooCommerce notices to provide better error message
|
||||
$notices = wc_get_notices('error');
|
||||
$error_messages = [];
|
||||
foreach ($notices as $notice) {
|
||||
$error_messages[] = is_array($notice) ? $notice['notice'] : $notice;
|
||||
}
|
||||
$error_message = !empty($error_messages) ? implode(', ', $error_messages) : 'Failed to add product to cart';
|
||||
wc_clear_notices(); // Clear notices after reading
|
||||
|
||||
error_log("WooNooW Cart Error: add_to_cart returned false - {$error_message}");
|
||||
error_log("WooNooW Cart Error: All WC notices: " . print_r($notices, true));
|
||||
wc_clear_notices();
|
||||
|
||||
return new WP_Error('add_to_cart_failed', $error_message, ['status' => 400]);
|
||||
}
|
||||
|
||||
error_log("WooNooW Cart: Product added successfully - Key: {$cart_item_key}");
|
||||
|
||||
|
||||
return new WP_REST_Response([
|
||||
'message' => 'Product added to cart',
|
||||
'message' => 'Product added to cart',
|
||||
'cart_item_key' => $cart_item_key,
|
||||
'cart' => self::format_cart(),
|
||||
'cart' => self::format_cart(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update cart item quantity
|
||||
*/
|
||||
public static function update_cart(WP_REST_Request $request) {
|
||||
public static function update_cart(WP_REST_Request $request)
|
||||
{
|
||||
$cart_item_key = $request->get_param('cart_item_key');
|
||||
$quantity = $request->get_param('quantity');
|
||||
|
||||
if (!WC()->cart) {
|
||||
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
|
||||
$quantity = $request->get_param('quantity');
|
||||
|
||||
// Initialize WooCommerce session and cart for REST API requests
|
||||
if (!WC()->session) {
|
||||
WC()->initialize_session();
|
||||
}
|
||||
|
||||
if (!WC()->cart) {
|
||||
WC()->initialize_cart();
|
||||
}
|
||||
if (!WC()->session->has_session()) {
|
||||
WC()->session->set_customer_session_cookie(true);
|
||||
}
|
||||
|
||||
// Update quantity
|
||||
$updated = WC()->cart->set_quantity($cart_item_key, $quantity);
|
||||
|
||||
|
||||
if (!$updated) {
|
||||
return new WP_Error('update_failed', 'Failed to update cart item', ['status' => 400]);
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response([
|
||||
'message' => 'Cart updated',
|
||||
'cart' => self::format_cart(),
|
||||
'cart' => self::format_cart(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove item from cart
|
||||
*/
|
||||
public static function remove_from_cart(WP_REST_Request $request) {
|
||||
public static function remove_from_cart(WP_REST_Request $request)
|
||||
{
|
||||
$cart_item_key = $request->get_param('cart_item_key');
|
||||
|
||||
if (!WC()->cart) {
|
||||
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
|
||||
|
||||
// Initialize WooCommerce session and cart for REST API requests
|
||||
if (!WC()->session) {
|
||||
WC()->initialize_session();
|
||||
}
|
||||
|
||||
if (!WC()->cart) {
|
||||
WC()->initialize_cart();
|
||||
}
|
||||
if (!WC()->session->has_session()) {
|
||||
WC()->session->set_customer_session_cookie(true);
|
||||
}
|
||||
|
||||
// Check if item exists in cart
|
||||
$cart_contents = WC()->cart->get_cart();
|
||||
if (!isset($cart_contents[$cart_item_key])) {
|
||||
return new WP_Error('item_not_found', "Cart item not found", ['status' => 404]);
|
||||
}
|
||||
|
||||
// Remove item
|
||||
$removed = WC()->cart->remove_cart_item($cart_item_key);
|
||||
|
||||
|
||||
if (!$removed) {
|
||||
return new WP_Error('remove_failed', 'Failed to remove cart item', ['status' => 400]);
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response([
|
||||
'message' => 'Item removed from cart',
|
||||
'cart' => self::format_cart(),
|
||||
'cart' => self::format_cart(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear entire cart
|
||||
*/
|
||||
public static function clear_cart(WP_REST_Request $request)
|
||||
{
|
||||
// Initialize WooCommerce session and cart for REST API requests
|
||||
if (!WC()->session) {
|
||||
WC()->initialize_session();
|
||||
}
|
||||
if (!WC()->cart) {
|
||||
WC()->initialize_cart();
|
||||
}
|
||||
if (!WC()->session->has_session()) {
|
||||
WC()->session->set_customer_session_cookie(true);
|
||||
}
|
||||
|
||||
// Empty the cart
|
||||
WC()->cart->empty_cart();
|
||||
|
||||
return new WP_REST_Response([
|
||||
'message' => 'Cart cleared',
|
||||
'cart' => self::format_cart(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply coupon to cart
|
||||
*/
|
||||
public static function apply_coupon(WP_REST_Request $request) {
|
||||
public static function apply_coupon(WP_REST_Request $request)
|
||||
{
|
||||
$coupon_code = $request->get_param('coupon_code');
|
||||
|
||||
|
||||
if (!WC()->cart) {
|
||||
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
|
||||
}
|
||||
|
||||
|
||||
// Apply coupon
|
||||
$applied = WC()->cart->apply_coupon($coupon_code);
|
||||
|
||||
|
||||
if (!$applied) {
|
||||
return new WP_Error('coupon_failed', 'Failed to apply coupon', ['status' => 400]);
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response([
|
||||
'message' => 'Coupon applied',
|
||||
'cart' => self::format_cart(),
|
||||
'cart' => self::format_cart(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove coupon from cart
|
||||
*/
|
||||
public static function remove_coupon(WP_REST_Request $request) {
|
||||
public static function remove_coupon(WP_REST_Request $request)
|
||||
{
|
||||
$coupon_code = $request->get_param('coupon_code');
|
||||
|
||||
|
||||
if (!WC()->cart) {
|
||||
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
|
||||
}
|
||||
|
||||
|
||||
// Remove coupon
|
||||
$removed = WC()->cart->remove_coupon($coupon_code);
|
||||
|
||||
|
||||
if (!$removed) {
|
||||
return new WP_Error('remove_coupon_failed', 'Failed to remove coupon', ['status' => 400]);
|
||||
}
|
||||
|
||||
|
||||
return new WP_REST_Response([
|
||||
'message' => 'Coupon removed',
|
||||
'cart' => self::format_cart(),
|
||||
'cart' => self::format_cart(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format cart data for API response
|
||||
*/
|
||||
private static function format_cart() {
|
||||
private static function format_cart()
|
||||
{
|
||||
$cart = WC()->cart;
|
||||
|
||||
|
||||
if (!$cart) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
$items = [];
|
||||
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
|
||||
$product = $cart_item['data'];
|
||||
|
||||
|
||||
// Format variation attributes with clean names (Size instead of attribute_size)
|
||||
$formatted_attributes = [];
|
||||
if (!empty($cart_item['variation'])) {
|
||||
foreach ($cart_item['variation'] as $attr_key => $attr_value) {
|
||||
// Remove 'attribute_' prefix and capitalize
|
||||
$clean_key = str_replace('attribute_', '', $attr_key);
|
||||
$clean_key = ucfirst($clean_key);
|
||||
// Capitalize value
|
||||
$formatted_attributes[$clean_key] = ucfirst($attr_value);
|
||||
}
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'key' => $cart_item_key,
|
||||
'product_id' => $cart_item['product_id'],
|
||||
'key' => $cart_item_key,
|
||||
'product_id' => $cart_item['product_id'],
|
||||
'variation_id' => $cart_item['variation_id'] ?? 0,
|
||||
'quantity' => $cart_item['quantity'],
|
||||
'name' => $product->get_name(),
|
||||
'price' => $product->get_price(),
|
||||
'subtotal' => $cart_item['line_subtotal'],
|
||||
'total' => $cart_item['line_total'],
|
||||
'image' => wp_get_attachment_url($product->get_image_id()),
|
||||
'permalink' => get_permalink($cart_item['product_id']),
|
||||
'attributes' => $cart_item['variation'] ?? [],
|
||||
'quantity' => $cart_item['quantity'],
|
||||
'name' => $product->get_name(),
|
||||
'price' => $product->get_price(),
|
||||
'subtotal' => $cart_item['line_subtotal'],
|
||||
'total' => $cart_item['line_total'],
|
||||
'image' => wp_get_attachment_url($product->get_image_id()),
|
||||
'permalink' => get_permalink($cart_item['product_id']),
|
||||
'attributes' => $formatted_attributes,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Get applied coupons
|
||||
$coupons = [];
|
||||
foreach ($cart->get_applied_coupons() as $coupon_code) {
|
||||
$coupon = new \WC_Coupon($coupon_code);
|
||||
$coupons[] = [
|
||||
'code' => $coupon_code,
|
||||
'code' => $coupon_code,
|
||||
'discount' => $cart->get_coupon_discount_amount($coupon_code),
|
||||
'type' => $coupon->get_discount_type(),
|
||||
'type' => $coupon->get_discount_type(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'subtotal' => $cart->get_subtotal(),
|
||||
'subtotal_tax' => $cart->get_subtotal_tax(),
|
||||
'discount_total' => $cart->get_discount_total(),
|
||||
'discount_tax' => $cart->get_discount_tax(),
|
||||
'shipping_total' => $cart->get_shipping_total(),
|
||||
'shipping_tax' => $cart->get_shipping_tax(),
|
||||
'items' => $items,
|
||||
'subtotal' => $cart->get_subtotal(),
|
||||
'subtotal_tax' => $cart->get_subtotal_tax(),
|
||||
'discount_total' => $cart->get_discount_total(),
|
||||
'discount_tax' => $cart->get_discount_tax(),
|
||||
'shipping_total' => $cart->get_shipping_total(),
|
||||
'shipping_tax' => $cart->get_shipping_tax(),
|
||||
'cart_contents_tax' => $cart->get_cart_contents_tax(),
|
||||
'fee_total' => $cart->get_fee_total(),
|
||||
'fee_tax' => $cart->get_fee_tax(),
|
||||
'total' => $cart->get_total('edit'),
|
||||
'total_tax' => $cart->get_total_tax(),
|
||||
'coupons' => $coupons,
|
||||
'needs_shipping' => $cart->needs_shipping(),
|
||||
'needs_payment' => $cart->needs_payment(),
|
||||
'fee_total' => $cart->get_fee_total(),
|
||||
'fee_tax' => $cart->get_fee_tax(),
|
||||
'total' => $cart->get_total('edit'),
|
||||
'total_tax' => $cart->get_total_tax(),
|
||||
'coupons' => $coupons,
|
||||
'needs_shipping' => $cart->needs_shipping(),
|
||||
'needs_payment' => $cart->needs_payment(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,69 +5,126 @@ namespace WooNooW\Frontend;
|
||||
* Template Override
|
||||
* Overrides WooCommerce templates to use WooNooW SPA
|
||||
*/
|
||||
class TemplateOverride {
|
||||
|
||||
class TemplateOverride
|
||||
{
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
public static function init() {
|
||||
public static function init()
|
||||
{
|
||||
// Hook to wp_loaded with priority 10 (BEFORE WooCommerce's priority 20)
|
||||
// This ensures we process add-to-cart before WooCommerce does
|
||||
add_action('wp_loaded', [__CLASS__, 'intercept_add_to_cart'], 10);
|
||||
|
||||
// Use blank template for full-page SPA
|
||||
add_filter('template_include', [__CLASS__, 'use_spa_template'], 999);
|
||||
|
||||
|
||||
// Disable canonical redirects for SPA routes
|
||||
add_filter('redirect_canonical', [__CLASS__, 'disable_canonical_redirect'], 10, 2);
|
||||
|
||||
|
||||
// Override WooCommerce shop page
|
||||
add_filter('woocommerce_show_page_title', '__return_false');
|
||||
|
||||
|
||||
// Replace WooCommerce content with our SPA
|
||||
add_action('woocommerce_before_main_content', [__CLASS__, 'start_spa_wrapper'], 5);
|
||||
add_action('woocommerce_after_main_content', [__CLASS__, 'end_spa_wrapper'], 999);
|
||||
|
||||
|
||||
// Remove WooCommerce default content
|
||||
remove_action('woocommerce_before_shop_loop', 'woocommerce_result_count', 20);
|
||||
remove_action('woocommerce_before_shop_loop', 'woocommerce_catalog_ordering', 30);
|
||||
remove_action('woocommerce_before_main_content', 'woocommerce_output_content_wrapper', 10);
|
||||
remove_action('woocommerce_after_main_content', 'woocommerce_output_content_wrapper_end', 10);
|
||||
|
||||
|
||||
// Override single product template
|
||||
add_filter('woocommerce_locate_template', [__CLASS__, 'override_template'], 10, 3);
|
||||
|
||||
|
||||
// Remove theme header and footer when SPA is active
|
||||
add_action('get_header', [__CLASS__, 'remove_theme_header']);
|
||||
add_action('get_footer', [__CLASS__, 'remove_theme_footer']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Intercept add-to-cart redirect (NOT the add-to-cart itself)
|
||||
* Let WooCommerce handle the cart operation properly, we just redirect afterward
|
||||
*
|
||||
* This is the proper approach - WooCommerce manages sessions correctly,
|
||||
* we just customize where the redirect goes.
|
||||
*/
|
||||
public static function intercept_add_to_cart()
|
||||
{
|
||||
// Only act if add-to-cart is present
|
||||
if (!isset($_GET['add-to-cart'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get SPA page from appearance settings
|
||||
$appearance_settings = get_option('woonoow_appearance_settings', []);
|
||||
$spa_page_id = isset($appearance_settings['general']['spa_page']) ? $appearance_settings['general']['spa_page'] : 0;
|
||||
|
||||
if (!$spa_page_id) {
|
||||
return; // No SPA page configured, let WooCommerce handle everything
|
||||
}
|
||||
|
||||
// Hook into WooCommerce's redirect filter AFTER it adds to cart
|
||||
// This is the proper way to customize the redirect destination
|
||||
add_filter('woocommerce_add_to_cart_redirect', function ($url) use ($spa_page_id) {
|
||||
// Get redirect parameter from original request
|
||||
$redirect_to = isset($_GET['redirect']) ? sanitize_text_field($_GET['redirect']) : 'cart';
|
||||
|
||||
// Build redirect URL with hash route for SPA
|
||||
$redirect_url = get_permalink($spa_page_id);
|
||||
|
||||
// Determine hash route based on redirect parameter
|
||||
$hash_route = '/cart'; // Default
|
||||
if ($redirect_to === 'checkout') {
|
||||
$hash_route = '/checkout';
|
||||
} elseif ($redirect_to === 'shop') {
|
||||
$hash_route = '/shop';
|
||||
}
|
||||
|
||||
// Return the SPA URL with hash route
|
||||
return trailingslashit($redirect_url) . '#' . $hash_route;
|
||||
}, 999);
|
||||
|
||||
// Prevent caching
|
||||
add_action('template_redirect', function () {
|
||||
nocache_headers();
|
||||
}, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable canonical redirects for SPA routes
|
||||
* This prevents WordPress from redirecting /product/slug URLs
|
||||
*/
|
||||
public static function disable_canonical_redirect($redirect_url, $requested_url) {
|
||||
public static function disable_canonical_redirect($redirect_url, $requested_url)
|
||||
{
|
||||
$settings = get_option('woonoow_customer_spa_settings', []);
|
||||
$mode = isset($settings['mode']) ? $settings['mode'] : 'disabled';
|
||||
|
||||
|
||||
// Only disable redirects in full SPA mode
|
||||
if ($mode !== 'full') {
|
||||
return $redirect_url;
|
||||
}
|
||||
|
||||
|
||||
// Check if this is a SPA route
|
||||
$spa_routes = ['/product/', '/cart', '/checkout', '/my-account'];
|
||||
|
||||
|
||||
foreach ($spa_routes as $route) {
|
||||
if (strpos($requested_url, $route) !== false) {
|
||||
// This is a SPA route, disable WordPress redirect
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $redirect_url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Use SPA template (blank page)
|
||||
*/
|
||||
public static function use_spa_template($template) {
|
||||
public static function use_spa_template($template)
|
||||
{
|
||||
// Check if current page is a designated SPA page
|
||||
if (self::is_spa_page()) {
|
||||
$spa_template = plugin_dir_path(dirname(dirname(__FILE__))) . 'templates/spa-full-page.php';
|
||||
@@ -75,21 +132,23 @@ class TemplateOverride {
|
||||
return $spa_template;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Legacy: Check SPA mode settings
|
||||
$settings = get_option('woonoow_customer_spa_settings', []);
|
||||
$mode = isset($settings['mode']) ? $settings['mode'] : 'disabled';
|
||||
|
||||
|
||||
// Mode 1: Disabled - but still check for shortcodes (legacy)
|
||||
if ($mode === 'disabled') {
|
||||
// Check if page has woonoow shortcodes
|
||||
global $post;
|
||||
if ($post && (
|
||||
has_shortcode($post->post_content, 'woonoow_shop') ||
|
||||
has_shortcode($post->post_content, 'woonoow_cart') ||
|
||||
has_shortcode($post->post_content, 'woonoow_checkout') ||
|
||||
has_shortcode($post->post_content, 'woonoow_account')
|
||||
)) {
|
||||
if (
|
||||
$post && (
|
||||
has_shortcode($post->post_content, 'woonoow_shop') ||
|
||||
has_shortcode($post->post_content, 'woonoow_cart') ||
|
||||
has_shortcode($post->post_content, 'woonoow_checkout') ||
|
||||
has_shortcode($post->post_content, 'woonoow_account')
|
||||
)
|
||||
) {
|
||||
// Use blank template for shortcode pages too
|
||||
$spa_template = plugin_dir_path(dirname(dirname(__FILE__))) . 'templates/spa-full-page.php';
|
||||
if (file_exists($spa_template)) {
|
||||
@@ -98,19 +157,19 @@ class TemplateOverride {
|
||||
}
|
||||
return $template;
|
||||
}
|
||||
|
||||
|
||||
// Check if current URL is a SPA route (for direct access)
|
||||
$request_uri = $_SERVER['REQUEST_URI'];
|
||||
$spa_routes = ['/shop', '/product/', '/cart', '/checkout', '/my-account'];
|
||||
$is_spa_route = false;
|
||||
|
||||
|
||||
foreach ($spa_routes as $route) {
|
||||
if (strpos($request_uri, $route) !== false) {
|
||||
$is_spa_route = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If it's a SPA route in full mode, use SPA template
|
||||
if ($mode === 'full' && $is_spa_route) {
|
||||
$spa_template = plugin_dir_path(dirname(dirname(__FILE__))) . 'templates/spa-full-page.php';
|
||||
@@ -120,18 +179,18 @@ class TemplateOverride {
|
||||
return $spa_template;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Mode 3: Checkout-Only (partial SPA)
|
||||
if ($mode === 'checkout_only') {
|
||||
$checkout_pages = isset($settings['checkoutPages']) ? $settings['checkoutPages'] : [
|
||||
'checkout' => true,
|
||||
'thankyou' => true,
|
||||
'account' => true,
|
||||
'cart' => false,
|
||||
'account' => true,
|
||||
'cart' => false,
|
||||
];
|
||||
|
||||
|
||||
$should_override = false;
|
||||
|
||||
|
||||
if (!empty($checkout_pages['checkout']) && is_checkout() && !is_order_received_page()) {
|
||||
$should_override = true;
|
||||
}
|
||||
@@ -144,17 +203,17 @@ class TemplateOverride {
|
||||
if (!empty($checkout_pages['cart']) && is_cart()) {
|
||||
$should_override = true;
|
||||
}
|
||||
|
||||
|
||||
if ($should_override) {
|
||||
$spa_template = plugin_dir_path(dirname(dirname(__FILE__))) . 'templates/spa-full-page.php';
|
||||
if (file_exists($spa_template)) {
|
||||
return $spa_template;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
|
||||
// Mode 2: Full SPA
|
||||
if ($mode === 'full') {
|
||||
// Override all WooCommerce pages
|
||||
@@ -165,23 +224,24 @@ class TemplateOverride {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start SPA wrapper
|
||||
*/
|
||||
public static function start_spa_wrapper() {
|
||||
public static function start_spa_wrapper()
|
||||
{
|
||||
// Check if we should use SPA
|
||||
if (!self::should_use_spa()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Determine page type
|
||||
$page_type = 'shop';
|
||||
$data_attrs = 'data-page="shop"';
|
||||
|
||||
|
||||
if (is_product()) {
|
||||
$page_type = 'product';
|
||||
global $post;
|
||||
@@ -196,58 +256,61 @@ class TemplateOverride {
|
||||
$page_type = 'account';
|
||||
$data_attrs = 'data-page="account"';
|
||||
}
|
||||
|
||||
|
||||
// Output SPA mount point
|
||||
echo '<div id="woonoow-customer-app" ' . $data_attrs . '>';
|
||||
echo '<div class="woonoow-loading">';
|
||||
echo '<p>' . esc_html__('Loading...', 'woonoow') . '</p>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
|
||||
|
||||
// Hide WooCommerce content
|
||||
echo '<div style="display: none;">';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* End SPA wrapper
|
||||
*/
|
||||
public static function end_spa_wrapper() {
|
||||
public static function end_spa_wrapper()
|
||||
{
|
||||
if (!self::should_use_spa()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Close hidden wrapper
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if we should use SPA
|
||||
*/
|
||||
private static function should_use_spa() {
|
||||
private static function should_use_spa()
|
||||
{
|
||||
// Check if frontend mode is enabled
|
||||
$mode = get_option('woonoow_frontend_mode', 'shortcodes');
|
||||
|
||||
|
||||
if ($mode === 'disabled') {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// For full SPA mode, always use SPA
|
||||
if ($mode === 'full_spa') {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// For shortcode mode, check if we're on WooCommerce pages
|
||||
if (is_shop() || is_product() || is_cart() || is_checkout() || is_account_page()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove theme header when SPA is active
|
||||
*/
|
||||
public static function remove_theme_header() {
|
||||
public static function remove_theme_header()
|
||||
{
|
||||
if (self::should_remove_theme_elements()) {
|
||||
remove_all_actions('wp_head');
|
||||
// Re-add essential WordPress head actions
|
||||
@@ -258,69 +321,74 @@ class TemplateOverride {
|
||||
add_action('wp_head', 'wp_site_icon', 99);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove theme footer when SPA is active
|
||||
*/
|
||||
public static function remove_theme_footer() {
|
||||
public static function remove_theme_footer()
|
||||
{
|
||||
if (self::should_remove_theme_elements()) {
|
||||
remove_all_actions('wp_footer');
|
||||
// Re-add essential WordPress footer actions
|
||||
add_action('wp_footer', 'wp_print_footer_scripts', 20);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if current page is the designated SPA page
|
||||
*/
|
||||
private static function is_spa_page() {
|
||||
private static function is_spa_page()
|
||||
{
|
||||
global $post;
|
||||
if (!$post) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Get SPA page ID from appearance settings
|
||||
$appearance_settings = get_option('woonoow_appearance_settings', []);
|
||||
$spa_page_id = isset($appearance_settings['general']['spa_page']) ? $appearance_settings['general']['spa_page'] : 0;
|
||||
|
||||
|
||||
// Check if current page matches the SPA page
|
||||
if ($spa_page_id && $post->ID == $spa_page_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if we should remove theme header/footer
|
||||
*/
|
||||
private static function should_remove_theme_elements() {
|
||||
private static function should_remove_theme_elements()
|
||||
{
|
||||
// Remove for designated SPA pages
|
||||
if (self::is_spa_page()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
$settings = get_option('woonoow_customer_spa_settings', []);
|
||||
$mode = isset($settings['mode']) ? $settings['mode'] : 'disabled';
|
||||
|
||||
|
||||
// Check if we're on a WooCommerce page in full mode
|
||||
if ($mode === 'full') {
|
||||
if (is_shop() || is_product() || is_cart() || is_checkout() || is_account_page() || is_woocommerce()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Also remove for pages with shortcodes (even in disabled mode)
|
||||
global $post;
|
||||
if ($post && (
|
||||
has_shortcode($post->post_content, 'woonoow_shop') ||
|
||||
has_shortcode($post->post_content, 'woonoow_cart') ||
|
||||
has_shortcode($post->post_content, 'woonoow_checkout') ||
|
||||
has_shortcode($post->post_content, 'woonoow_account')
|
||||
)) {
|
||||
if (
|
||||
$post && (
|
||||
has_shortcode($post->post_content, 'woonoow_shop') ||
|
||||
has_shortcode($post->post_content, 'woonoow_cart') ||
|
||||
has_shortcode($post->post_content, 'woonoow_checkout') ||
|
||||
has_shortcode($post->post_content, 'woonoow_account')
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Special check for Shop page (archive)
|
||||
if (function_exists('is_shop') && is_shop()) {
|
||||
$shop_page_id = get_option('woocommerce_shop_page_id');
|
||||
@@ -331,19 +399,20 @@ class TemplateOverride {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Override WooCommerce templates
|
||||
*/
|
||||
public static function override_template($template, $template_name, $template_path) {
|
||||
public static function override_template($template, $template_name, $template_path)
|
||||
{
|
||||
// Only override if SPA is enabled
|
||||
if (!self::should_use_spa()) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
|
||||
// Templates to override
|
||||
$override_templates = [
|
||||
'archive-product.php',
|
||||
@@ -351,7 +420,7 @@ class TemplateOverride {
|
||||
'cart/cart.php',
|
||||
'checkout/form-checkout.php',
|
||||
];
|
||||
|
||||
|
||||
// Check if this template should be overridden
|
||||
foreach ($override_templates as $override) {
|
||||
if (strpos($template_name, $override) !== false) {
|
||||
@@ -359,7 +428,7 @@ class TemplateOverride {
|
||||
return plugin_dir_path(dirname(dirname(__FILE__))) . 'templates/spa-wrapper.php';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $template;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user